如何从进程 ID 获取主窗口句柄?

Posted

技术标签:

【中文标题】如何从进程 ID 获取主窗口句柄?【英文标题】:How to get main window handle from process id? 【发布时间】:2009-12-11 15:34:36 【问题描述】:

如何从进程id获取ma​​in窗口句柄?

我想把这个窗口放在前面。

它在“进程资源管理器”中运行良好。

【问题讨论】:

如果您打开了两个 Firefox 窗口,哪一个是“主”窗口?他们是平等的。 Process Explorer 似乎会选择最近关注的那个。 Windows 不保留“主窗口” 的概念。有***窗口、窗口和拥有窗口。任何进程都可以有零个或多个***窗口。除非你提供一个简洁的说明是什么决定了“主窗口”,否则这个问题是无法回答的。 【参考方案1】:

我检查了 .NET 如何确定主窗口。

我的发现表明它也使用了EnumWindows()

这段代码应该类似于 .NET 的方式:

struct handle_data 
    unsigned long process_id;
    HWND window_handle;
;

HWND find_main_window(unsigned long process_id)

    handle_data data;
    data.process_id = process_id;
    data.window_handle = 0;
    EnumWindows(enum_windows_callback, (LPARAM)&data);
    return data.window_handle;


BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam)

    handle_data& data = *(handle_data*)lParam;
    unsigned long process_id = 0;
    GetWindowThreadProcessId(handle, &process_id);
    if (data.process_id != process_id || !is_main_window(handle))
        return TRUE;
    data.window_handle = handle;
    return FALSE;   


BOOL is_main_window(HWND handle)
   
    return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);

【讨论】:

你能解释一下is_main_window背后的逻辑吗?我没有看到使用if (data.process_id != process_id || !IsWindowVisible(handle)) 的区别(在我的测试中)。此外,这种方法需要调整以支持具有多个主窗口(例如 Web 浏览器)的进程 ID。 @CamelCase GetWindow(handle, GW_OWNER) == 0 检查窗口不是owned window(例如对话框或其他东西)。 IsWindowVisible(handle) 检查窗口是否可见且未隐藏(相当多的没有 GUI 的应用程序仍然有一个隐藏的窗口,或者甚至具有隐藏 GUI 的应用程序,例如在托盘中运行的配置应用程序)。因此,如果窗口可见且没有所有者,则该窗口被视为“主窗口”,这对大多数“主窗口”来说已经足够了。 这是对父母和所有者的一个很好的解释,可能会为很多人解决很多问题,以帮助添加或删除此代码中的逻辑:blogs.msdn.microsoft.com/oldnewthing/20100315-00/?p=14613 在简而言之,没有父窗口的窗口仍然可以拥有所有者并且不是***窗口。 这适用于某些应用程序,但它可能会因更复杂的事情而崩溃。根据此定义,某些应用程序(例如 MSO)具有许多“主”窗口,并且无法首先安全关闭。我发现检查窗口类名是获得大多数应用程序的“主”窗口的好方法。另外,请注意比赛条件。用户可能会在您循环并破坏您的代码时关闭应用程序【参考方案2】:

我不相信 Windows(相对于 .NET)提供了一种直接的方式来实现这一点。

我知道的唯一方法是用EnumWindows() 枚举所有***窗口,然后找到每个属于GetWindowThreadProcessID() 的进程。这听起来间接且效率低下,但并没有您想象的那么糟糕——在典型情况下,您可能需要穿过十几个***窗口...

【讨论】:

我怎么知道主窗口? +1。您准确地描述了 msdn 文章链接所建议的内容。但在大约 1000 字以内。 @Alexey:来自 MSDN:“EnumWindows 函数不枚举子窗口。” @hometoast:经验造就简洁。我在那篇文章之前四年发表了这种方法。 作为参考,这是 .NET 检索主窗口句柄的方式:System.Diagnostics.MainWindowFinder.FindMainWindow 和 EnumWindowsCallback。所以可以说,.NET 也没有提供“直接方式”【参考方案3】:

这里有误会的可能。 .Net 中的 WinForms 框架自动将创建的第一个窗口(例如,Application.Run(new SomeForm()))指定为MainWindow。然而,win32 API 不承认每个进程都有一个“主窗口”的概念。消息循环完全能够处理系统和进程资源允许您创建的尽可能多的“主”窗口。因此,您的进程没有“主窗口”。在一般情况下,您可以做的最好的事情是使用EnumWindows() 让所有非子窗口在给定进程上处于活动状态,并尝试使用一些启发式方法来确定哪个是您想要的。幸运的是,大多数进程在大多数情况下可能只运行一个“主”窗口,因此在大多数情况下您应该会获得良好的结果。

【讨论】:

实际上,.NET 会在第一次访问System.Diagnostics.Process.MainWindowHandle 属性时缓存窗口句柄。窗口句柄没有预先存储。它使用 Jerry Coffin 在his answer 中概述的相同算法进行评估。 是的,你是对的。我不知道我从哪里得到这个说法 - 可能只是根据经验假设,即使窗口的枚举顺序不太可能得到保证。【参考方案4】:

这是我基于最佳答案使用纯 Win32/C++ 的解决方案。这个想法是将所有需要的东西包装到一个函数中,而不需要外部回调函数或结构:

#include <utility>

HWND FindTopWindow(DWORD pid)

    std::pair<HWND, DWORD> params =  0, pid ;

    // Enumerate the windows using a lambda to process each window
    BOOL bResult = EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL 
    
        auto pParams = (std::pair<HWND, DWORD>*)(lParam);

        DWORD processId;
        if (GetWindowThreadProcessId(hwnd, &processId) && processId == pParams->second)
        
            // Stop enumerating
            SetLastError(-1);
            pParams->first = hwnd;
            return FALSE;
        

        // Continue enumerating
        return TRUE;
    , (LPARAM)&params);

    if (!bResult && GetLastError() == -1 && params.first)
    
        return params.first;
    

    return 0;

【讨论】:

如果应用程序有两个窗口,您可能需要仔细检查窗口是否没有所有者以避免过早退出循环:if (GetWindowThreadProcessId(hwnd, &amp;processId) &amp;&amp; processId == pParams-&gt;second &amp;&amp; GetWindow(hwnd, GW_OWNER) == 0)【参考方案5】:

虽然可能与您的问题无关,但请查看GetGUIThreadInfo Function。

【讨论】:

【参考方案6】:

作为 Hiale 解决方案的扩展,您可以提供不同的或修改的版本,以支持具有多个主窗口的进程。

首先,修改结构以允许存储多个句柄:

struct handle_data 
    unsigned long process_id;
    std::vector<HWND> handles;
;

二、修改回调函数:

BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam)

    handle_data& data = *(handle_data*)lParam;
    unsigned long process_id = 0;
    GetWindowThreadProcessId(handle, &process_id);
    if (data.process_id != process_id || !is_main_window(handle)) 
        return TRUE;
    
    // change these 2 lines to allow storing of handle and loop again
    data.handles.push_back(handle);
    return TRUE;   
 

最后,修改主函数的返回值:

std::vector<HWD> find_main_window(unsigned long process_id)

    handle_data data;
    data.process_id = process_id;
    EnumWindows(enum_windows_callback, (LPARAM)&data);
    return data.handles;

【讨论】:

When you transfer control across stack frames, all the frames in between need to be in on the joke.【参考方案7】:

只是为了确保您不会混淆 tid(线程 id)和 pid(进程 id):

DWORD pid;
DWORD tid = GetWindowThreadProcessId( this->m_hWnd, &pid);

【讨论】:

这是 OP 想要的反函数。

以上是关于如何从进程 ID 获取主窗口句柄?的主要内容,如果未能解决你的问题,请参考以下文章

delphi 知道路径和进程如何获取窗口句柄?

C# 根据进程ID获取进程主窗口句柄

C# 根据进程ID获取进程主窗口句柄

获取进程所有窗口的句柄

一个进程有很多窗口,怎么取这个进程的下所有窗口句柄

vb 获取窗口句柄