【问题标题】:How to get main window handle from process id?如何从进程 ID 获取主窗口句柄?
【发布时间】:2010-12-25 17:20:09
【问题描述】:

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

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

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

【问题讨论】:

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

标签: c++ windows winapi windows-7


【解决方案1】:

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

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

【讨论】:

  • 我怎么知道主窗口?
  • +1。您准确地描述了 msdn 文章链接所建议的内容。但在大约 1000 字以内。
  • @Alexey:来自 MSDN:“EnumWindows 函数不枚举子窗口。”
  • @hometoast:经验造就简洁。我在那篇文章之前四年发表了这种方法。
  • 作为参考,这是 .NET 检索主窗口句柄的方式:System.Diagnostics.MainWindowFinder.FindMainWindowEnumWindowsCallback。所以可以说,.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;
    }
    
    【解决方案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)&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)
    猜你喜欢
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多