如何使用.NET 枚举属于特定进程的所有窗口?

Posted

技术标签:

【中文标题】如何使用.NET 枚举属于特定进程的所有窗口?【英文标题】:How to enumerate all windows belonging to a particular process using .NET? 【发布时间】:2011-02-01 16:12:11 【问题描述】:

如何使用 c# 找到由特定进程创建的所有窗口?

更新

我需要使用应用程序的PID(进程 ID)枚举属于特定进程的所有窗口。

【问题讨论】:

重复***.com/questions/2281429/… @Brian - 与枚举所有打开的窗口相反,不会从 Process.MainWindowHandle 和 EnumChildWindows 中关闭? @Gishu:不,但您可以在 Win32 API FindWindowEx 中使用 MainWindowHandle 【参考方案1】:
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn,
    IntPtr lParam);

static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)

    var handles = new List<IntPtr>();

    foreach (ProcessThread thread in Process.GetProcessById(processId).Threads)
        EnumThreadWindows(thread.Id, 
            (hWnd, lParam) =>  handles.Add(hWnd); return true; , IntPtr.Zero);

    return handles;

和示例用法:

private const uint WM_GETTEXT = 0x000D;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, 
    StringBuilder lParam);

[STAThread]
static void Main(string[] args)

    foreach (var handle in EnumerateProcessWindowHandles(
        Process.GetProcessesByName("explorer").First().Id))
    
        StringBuilder message = new StringBuilder(1000);
        SendMessage(handle, WM_GETTEXT, message.Capacity, message);
        Console.WriteLine(message);
    

【讨论】:

感谢您发布这篇文章!我看到这种方法的性能更好(“扫描进程”->“扫描线程”->“扫描窗口”而不是“扫描窗口”->“检查进程 ID”) 当您的应用程序因基于堆栈的缓冲区溢出而崩溃时,这段代码会给您带来极大的调试痛苦。在将句柄列表传递给非托管回调之前,您必须使用 GCHandle 固定对象。如果您不这样做并且会发生竞争条件,您的列表将被移动 - 应用程序将默默地粉碎 @Toddams 在这个句柄代码列表中没有被传递给非托管回调。 EnumThreadDelegate 回调的实例将在每个 EnumThreadWindows 调用期间自动固定,之后将不需要此回调,因此我认为 GC 在这种特殊情况下不会造成任何伤害。您的评论通常仅对真正的异步场景有效。 docs.microsoft.com/en-us/dotnet/framework/interop/… 我很确定你应该处理那些 ProcessProcessThread 对象。 托管对象不需要处理。垃圾收集器独自完成这项工作。仅当您想立即释放大量内存时才需要 Dispose()。例如,如果您正在使用消耗兆字节内存的巨大位图,并且您想立即释放内存。或者,如果您想确保 FileStream 或 COM 端口立即关闭,您可以使用 Dispose()。否则,垃圾收集器会在空闲时间执行此操作。【参考方案2】:

使用 Win32 API EnumWindows(如果您想要子窗口 EnumChildWindows)),或者您也可以使用 EnumThreadWindows 。

[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData);

然后通过Win32 APIGetWindowThreadProcessId检查每个窗口属于哪个进程

[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);

【讨论】:

嗯,这会枚举每个线程的窗口。它需要更多的工作才能找到每个进程的窗口。见Konstantin's answer below。 更好地使用康斯坦丁的答案!【参考方案3】:

古老的线程,但它让我开始了,所以这里有一个小实用函数,它将找到一个与 lambda(谓词)匹配的子窗口。易于更改返回列表。在谓词中处理多个条件。

    public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.Dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam);

    /// <summary>
    /// Find a child window that matches a set of conditions specified as a Predicate that receives hWnd.  Returns IntPtr.Zero
    /// if the target window not found.  Typical search criteria would be some combination of window attributes such as
    /// ClassName, Title, etc., all of which can be obtained using API functions you will find on pinvoke.net
    /// </summary>
    /// <remarks>
    ///     <para>Example: Find a window with specific title (use Regex.IsMatch for more sophisticated search)</para>
    ///     <code lang="C#"><![CDATA[var foundHandle = Win32.FindWindow(IntPtr.Zero, ptr => Win32.GetWindowText(ptr) == "Dashboard");]]></code>
    /// </remarks>
    /// <param name="parentHandle">Handle to window at the start of the chain.  Passing IntPtr.Zero gives you the top level
    /// window for the current process.  To get windows for other processes, do something similar for the FindWindow
    /// API.</param>
    /// <param name="target">Predicate that takes an hWnd as an IntPtr parameter, and returns True if the window matches.  The
    /// first match is returned, and no further windows are scanned.</param>
    /// <returns> hWnd of the first found window, or IntPtr.Zero on failure </returns>
    public static IntPtr FindWindow(IntPtr parentHandle, Predicate<IntPtr> target) 
        var result = IntPtr.Zero;
        if (parentHandle == IntPtr.Zero)
            parentHandle = Process.GetCurrentProcess().MainWindowHandle;
        EnumChildWindows(parentHandle, (hwnd, param) => 
            if (target(hwnd)) 
                result = hwnd;
                return false;
            
            return true;
        , IntPtr.Zero);
        return result;
    

例子

var foundHandle = Win32.FindWindow(IntPtr.Zero, ptr => Win32.GetWindowText(ptr) == "Dashboard");

【讨论】:

有了这个你可以在委托中完成你的工作,不再需要返回 HWND。

以上是关于如何使用.NET 枚举属于特定进程的所有窗口?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过进程id枚举到所有的窗口

如何使用 MDbg 以编程方式枚举正在运行的 .NET 进程中的类型?

c ++如何获得系统所有窗口名称 包括隐藏窗口

枚举所有 .NET 4.0 进程的应用程序域及其属性 [MonitoringTotalProcessorTime.....]

C#如何关闭一个窗口的同时打开另一个窗口

在 Windows 中,如何使用 C++ 或 C# 在属于另一个进程的窗口中突出显示文本?