获取线程的可见窗口标题

时间:2012-02-16 19:48:52

标签: c# windows winapi user-interface pinvoke

我想知道我将如何做到这一点,因为我尝试了无数不同的user32功能并在网上进行了相当广泛的研究,但不幸的是我还没有能够提出解决方案。

有一个应用程序有5个线程。只要进程'PID,就可以通过.NET Process类GetProcessById方法轻松访问这些线程。但是,似乎没有一个函数可用于提供线程ID并枚举其窗口(父级或子级)。其中一个线程共有10个窗口,9个隐藏,1个可见。该可见线程的标题是我试图以编程方式获得的。

我的最新方法是获取进程句柄,将其放入EnumChildWindows,并尝试将每个窗口句柄添加到集合中,但我的集合始终为空。

以下是我在ProcessThreadsView工具中看到的截图:

enter image description here

我有什么遗失的吗?我通过电子邮件发送了该工具的作者,看看他是如何做到的,但我想我会问你们看看是否有一个既定的方法。

更新:我尝试过使用GetGUIThreadInfo,这就是我的称呼方式:

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[StructLayout(LayoutKind.Sequential)]
public struct GUITHREADINFO
{
    public uint cbSize;
    public uint flags;
    public IntPtr hwndActive;
    public IntPtr hwndFocus;
    public IntPtr hwndCapture;
    public IntPtr hwndMenuOwner;
    public IntPtr hwndMoveSize;
    public IntPtr hwndCaret;
    public Rect rcCaret;
}

static IEnumerable<IntPtr> EnumerateThreadWindowHandlesByProcessId(int processId)
{
    List<IntPtr> threadWindowHandles = new List<IntPtr>();

    foreach (ProcessThread thread in Process.GetProcessById(processId).Threads)
    {
        GUITHREADINFO threadInfo = new GUITHREADINFO();
        threadInfo.cbSize = (uint)Marshal.SizeOf(threadInfo);
        bool returnValue = GetGUIThreadInfo((uint)thread.Id, out threadInfo);
        threadWindowHandles.Add(threadInfo.hwndActive);
    }

    return threadWindowHandles;
}

更新2:

使用EnumThreadWindows,这就是我所拥有的:

public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);

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

private static bool ThreadWindows(IntPtr handle, IntPtr param)
{
    //get window from handle later, testing for now
    logger.Info("foo bar");

    return true;
}

[STAThread]
public void Execute()
{
    Process[] processes = Process.GetProcessesByName("MyProcessName");

    Process processOfInterest = processes[0];

    foreach (ProcessThread thread in processOfInterest.Threads)
    {
        EnumThreadWindows(thread.Id, new EnumThreadDelegate(ThreadWindows), IntPtr.Zero);
    }
}

3 个答案:

答案 0 :(得分:4)

(道歉,如果我之前的回答很严厉;我说“看似很明显”,因为它对我来说似乎很明显,所以我认为它对OP来说也是显而易见的,因此是错误的,但他没有提供任何关于什么的细节已经/未曾尝试过。)

EnumThreadWindows绝对是你想要的功能,它应该有效。对于我系统上运行的每个应用程序,我只能使用EnumThreadWindowsIsWindowVisible从ProcessThreadsView复制信息。 (另请注意,“可见”与“有效”不同。)

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
private delegate bool EnumThreadWindowsProc(IntPtr handle, int param);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumThreadWindows(uint threadId, 
    EnumThreadWindowsProc callback, int param);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr handle);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int GetWindowText(
    IntPtr handle, 
    [MarshalAs(UnmanagedType.LPWStr)] StringBuilder caption,
    int count);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int GetWindowTextLength(IntPtr handle);

static void Main(string[] args)
{
    var callback = new EnumThreadWindowsProc(Program.ThreadWindows);

    foreach (var proc in Process.GetProcesses())
    {
        foreach (ProcessThread thread in proc.Threads)
        {
            Program.EnumThreadWindows((uint)thread.Id, callback, 0);
        }
    }

    Console.ReadLine();
}

private static bool ThreadWindows(IntPtr handle, int param)
{           
    if (Program.IsWindowVisible(handle))
    {
        var length = Program.GetWindowTextLength(handle);
        var caption = new StringBuilder(length + 1);
        Program.GetWindowText(handle, caption, caption.Capacity);

        Console.WriteLine("Got a visible window: {0}", caption);
    }

    return true;
}

尝试使用EnumThreadWindows时,您到底得到了什么?你的回调有没有发生?

答案 1 :(得分:4)

我相信您正在寻找GetGUIThreadInfo

更新:您的p / invoke存在一些问题。

rect字段是错误的(这并不重要)。使用此:

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

而不是System.Drawing.Rectangle

其他字段的类型声明也已关闭。它应该是这样的:

[StructLayout(LayoutKind.Sequential)]
public struct GUITHREADINFO
{
    public uint cbSize;
    public uint flags;
    public IntPtr hwndActive;
    public IntPtr hwndFocus;
    public IntPtr hwndCapture;
    public IntPtr hwndMenuOwner;
    public IntPtr hwndMoveSize;
    public IntPtr hwndCaret;
    public Rect rcCaret;
}

最好检查一下您的返回值并在SetLastError = true属性中使用DllImport,这样就可以看出函数调用失败的原因。还有,你 应该通过ref传递结构,因为你传入了结构大小。

[DllImport("user32.dll", SetLastError=true)]
static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);

使用Marshal.GetLastWin32Error()获取错误代码。但只有GetGUIThreadInfo返回false才会检查此内容。

通过上述更改,当给定有效的线程ID时,对GetGUIThreadInfo的调用将起作用。还要注意,其他进程需要具有输入焦点,以便GetGUIThreadInfo返回任何有用的内容。

  

如果指定的线程不存在或具有输入队列,则该函数将失败。

答案 2 :(得分:1)

您在Update 2中的代码很好(假设eraAccessProcess实际上是processOfInterest)。该代码有效。我能看到的唯一可能的失败原因是:

  1. processOfInterest不是您真正感兴趣的过程。
  2. 该进程实际上没有与windows关联的线程。