如何从进程 ID 获取主窗口句柄?
Posted
技术标签:
【中文标题】如何从进程 ID 获取主窗口句柄?【英文标题】:How to get main window handle from process id? 【发布时间】:2010-12-25 17:20:09 【问题描述】:如何从进程id获取main窗口句柄?
我想把这个窗口放在前面。
它在“进程资源管理器”中运行良好。
【问题讨论】:
如果您打开了两个 Firefox 窗口,哪一个是“主”窗口?他们是平等的。 Process Explorer 似乎会选择最近关注的那个。 Windows 不保留“主窗口” 的概念。有***窗口、子窗口和拥有窗口。任何进程都可以有零个或多个***窗口。除非你提供一个简洁的说明是什么决定了“主窗口”,否则这个问题是无法回答的。 【参考方案1】:我不相信 Windows(相对于 .NET)提供了一种直接的方式来实现这一点。
我知道的唯一方法是用EnumWindows()
枚举所有***窗口,然后找到每个属于GetWindowThreadProcessID()
的进程。这听起来间接且效率低下,但并没有您想象的那么糟糕——在典型情况下,您可能需要穿过十几个***窗口...
【讨论】:
我怎么知道主窗口? +1。您准确地描述了 msdn 文章链接所建议的内容。但在大约 1000 字以内。 @Alexey:来自 MSDN:“EnumWindows 函数不枚举子窗口。” @hometoast:经验造就简洁。我在那篇文章之前四年发表了这种方法。 作为参考,这是 .NET 检索主窗口句柄的方式:System.Diagnostics.MainWindowFinder.FindMainWindow 和 EnumWindowsCallback。所以可以说,.NET 也没有提供“直接方式”。【参考方案2】:这里有误会的可能。 .Net 中的 WinForms 框架自动将创建的第一个窗口(例如,Application.Run(new SomeForm())
)指定为MainWindow
。然而,win32 API 不承认每个进程都有一个“主窗口”的概念。消息循环完全能够处理系统和进程资源允许您创建的尽可能多的“主”窗口。因此,您的进程没有“主窗口”。在一般情况下,您可以做的最好的事情是使用EnumWindows()
让所有非子窗口在给定进程上处于活动状态,并尝试使用一些启发式方法来确定哪个是您想要的。幸运的是,大多数进程在大多数情况下可能只运行一个“主”窗口,因此在大多数情况下您应该会获得良好的结果。
【讨论】:
实际上,.NET 会在第一次访问System.Diagnostics.Process.MainWindowHandle 属性时缓存窗口句柄。窗口句柄没有预先存储。它使用 Jerry Coffin 在his answer 中概述的相同算法进行评估。 是的,你是对的。我不知道我从哪里得到这个说法 - 可能只是根据经验假设,即使窗口的枚举顺序不太可能得到保证。【参考方案3】:虽然可能与您的问题无关,但请查看GetGUIThreadInfo Function。
【讨论】:
【参考方案4】:只是为了确保您不会混淆 tid(线程 id)和 pid(进程 id):
DWORD pid;
DWORD tid = GetWindowThreadProcessId( this->m_hWnd, &pid);
【讨论】:
这是 OP 想要的反函数。【参考方案5】:我检查了 .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)具有许多“主”窗口,并且无法首先安全关闭。我发现检查窗口类名是获得大多数应用程序的“主”窗口的好方法。另外,请注意比赛条件。用户可能会在您循环并破坏您的代码时关闭应用程序【参考方案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】:这是我基于最佳答案使用纯 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)¶ms);
if (!bResult && GetLastError() == -1 && params.first)
return params.first;
return 0;
【讨论】:
如果应用程序有两个窗口,您可能需要仔细检查窗口是否没有所有者以避免过早退出循环:if (GetWindowThreadProcessId(hwnd, &processId) && processId == pParams->second && GetWindow(hwnd, GW_OWNER) == 0)
以上是关于如何从进程 ID 获取主窗口句柄?的主要内容,如果未能解决你的问题,请参考以下文章