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

40

我该如何使用C#找到特定进程创建的所有窗口?

更新

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


1
如何枚举进程内的所有窗口 - Peter Lillevold
@Brian - 与枚举所有打开的窗口相比,从Process.MainWindowHandle和EnumChildWindows开始是否更好? - Gishu
@Gishu:不过你可能可以在Win32 API的FindWindowEx中使用MainWindowHandle。 - Brian R. Bondy
3个回答

102
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);
    }
}

4
谢谢您发布这篇文章!我使用这种方法看到了更好的性能表现("扫描进程" -> "扫描线程" -> "扫描窗口",而不是"扫描窗口" -> "检查进程ID")。 - Marcus
1
这段代码会给你带来很多痛苦,当你的应用程序因基于堆栈的缓冲区溢出而崩溃时。在将句柄列表传递给非托管回调之前,您必须使用GCHandle固定对象。如果您不这样做并发生竞争条件,您的列表将被移动-应用程序将会静默崩溃。 - Toddams
2
@Toddams 在这段代码中,句柄列表不会传递给非托管回调函数。每次调用EnumThreadWindows时,EnumThreadDelegate回调函数的实例将自动固定,而且此回调函数在之后也不再需要,因此我认为GC在这种特殊情况下不会造成任何影响。您的评论通常仅适用于真正异步的情况。https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-a-delegate-as-a-callback-method - Konstantin Spirin
我非常确定你应该处理那些 ProcessProcessThread 对象。 - Miroslav Policki
垃圾回收器不会调用Dispose方法。如果存在终结器,它将调用终结器,而终结器通常会调用Dispose方法。但并非所有实现IDisposable接口的类都有终结器,因为拥有终结器会增加额外的开销。依赖GC调用终结器而不是自己调用Dispose方法会导致更多和更长的垃圾回收,从而降低性能。因此,一般来说,如果某个对象实现了IDisposable接口,你应该在不再需要它时立即释放它。 - Miroslav Policki
显示剩余3条评论

19

使用 Win32 API EnumWindows(如果需要子窗口,则使用EnumChildWindows)或者你也可以使用EnumThreadWindows

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

然后,使用Win32 API GetWindowThreadProcessId 检查每个窗口所属的进程。

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

3
这列举了每个线程的窗口。要找到每个进程的窗口需要进行更多的工作。请参见下面的Konstantin的答案 - Abel
2
最好使用Konstantin的答案! - Andrei Rînea

5

虽然这是一个古老的线程,但它启发了我。以下是一个小实用程序函数,它将查找匹配lambda(Predicate)的子窗口。很容易更改以返回一个列表。多个条件在谓词中处理。

    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。 - jw_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接