使用 PInvoke 从 C# 获取工具提示文本

发布于 2024-12-06 04:55:59 字数 2514 浏览 2 评论 0原文

我在 C# 中使用 PInvoke,尝试使用已知处理程序读取窗口中可见的工具提示,但我尝试以这种方式检查的窗口应用程序会因内存访问冲突错误而崩溃,或者根本不显示工具提示文本lpszText TOOLINFO 成员。

我正在使用回调调用 EnumWindows ,然后向工具提示窗口发送消息在该函数中:

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

static void Main(string[] args)
{
  callBackPtr = new CallBackPtr(Report);

  IntPtr hWnd = WindowFromPoint(<mouse coordinates point>);

  if (hWnd != IntPtr.Zero)
  {
        Console.Out.WriteLine("Window with handle " + hWnd +
                              " and class name " +
                              getWindowClassName(hWnd));

        EnumWindows(callBackPtr, hWnd);

        Console.Out.WriteLine();
  }


  public static bool Report(IntPtr hWnd, IntPtr lParam)
  {
        String windowClassName = getWindowClassName(hWnd);

        if (windowClassName.Contains("tool") &&
             GetParent(hWnd) == lParam)
        {
            string szToolText = new string(' ', 250);

            TOOLINFO ti = new TOOLINFO();
            ti.cbSize = Marshal.SizeOf(typeof(TOOLINFO));
            ti.hwnd = GetParent(hWnd);
            ti.uId = hWnd;
            ti.lpszText = szToolText;

            SendMessage(hWnd, TTM_GETTEXT, (IntPtr)250, ref ti);

            Console.WriteLine("Child window handle is " + hWnd + " and class name " + getWindowClassName(hWnd) + " and value " + ti.lpszText);
        }

        return true;
    }

这是我定义 TOOLINFO 结构的方式:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    private int _Left;
    private int _Top;
    private int _Right;
    private int _Bottom;
}


struct TOOLINFO
{
    public int cbSize;
    public int uFlags;
    public IntPtr hwnd;
    public IntPtr uId;
    public RECT rect;
    public IntPtr hinst;

    [MarshalAs(UnmanagedType.LPTStr)]
    public string lpszText;

    public IntPtr lParam;
}

TTM_GETTEXT 值

private static UInt32 WM_USER = 0x0400;
private static UInt32 TTM_GETTEXT = (WM_USER + 56);

SendMessage 重载

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref TOOLINFO lParam);

那么,我的代码中是否缺少任何明显的错误,我应该更改什么才能解决这种情况?

编辑:这里是整个代码,以便您可以测试。

I'm using PInvoke in C#, trying to read tooltips visible in a window with a known handler, but the apps who's windows I try to inspect in this manner crash with memory access violation errors, or simply don't reveal the tooltip text in the lpszText TOOLINFO member.

I'm calling EnumWindows with a callback and then sending a message to the tooltip window in that function:

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

static void Main(string[] args)
{
  callBackPtr = new CallBackPtr(Report);

  IntPtr hWnd = WindowFromPoint(<mouse coordinates point>);

  if (hWnd != IntPtr.Zero)
  {
        Console.Out.WriteLine("Window with handle " + hWnd +
                              " and class name " +
                              getWindowClassName(hWnd));

        EnumWindows(callBackPtr, hWnd);

        Console.Out.WriteLine();
  }


  public static bool Report(IntPtr hWnd, IntPtr lParam)
  {
        String windowClassName = getWindowClassName(hWnd);

        if (windowClassName.Contains("tool") &&
             GetParent(hWnd) == lParam)
        {
            string szToolText = new string(' ', 250);

            TOOLINFO ti = new TOOLINFO();
            ti.cbSize = Marshal.SizeOf(typeof(TOOLINFO));
            ti.hwnd = GetParent(hWnd);
            ti.uId = hWnd;
            ti.lpszText = szToolText;

            SendMessage(hWnd, TTM_GETTEXT, (IntPtr)250, ref ti);

            Console.WriteLine("Child window handle is " + hWnd + " and class name " + getWindowClassName(hWnd) + " and value " + ti.lpszText);
        }

        return true;
    }

Here's how I defined the TOOLINFO structure:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    private int _Left;
    private int _Top;
    private int _Right;
    private int _Bottom;
}


struct TOOLINFO
{
    public int cbSize;
    public int uFlags;
    public IntPtr hwnd;
    public IntPtr uId;
    public RECT rect;
    public IntPtr hinst;

    [MarshalAs(UnmanagedType.LPTStr)]
    public string lpszText;

    public IntPtr lParam;
}

the TTM_GETTEXT value

private static UInt32 WM_USER = 0x0400;
private static UInt32 TTM_GETTEXT = (WM_USER + 56);

and the SendMessage overload

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref TOOLINFO lParam);

So, is there any obvious error that I'm missing in my code, what should I change so that this situation is resolved?

Edit: Here is the whole code, so you could test.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你爱我像她 2024-12-13 04:55:59

您正在跨进程发送私人消息,这需要手动封送。 这是关于同一主题的另一个 stackoverflow 问题。更好的方法是完全改变方向并使用 Active Accessibility 和/或 UI Automation,它们是为此类事情而设计的。

You are sending a private message across processes, which requires manual marshaling. Here's another stackoverflow question on the same topic. Better would be to change direction entirely and use Active Accessibility and/or UI Automation, which are designed for this sort of thing.

等风也等你 2024-12-13 04:55:59

正如雷蒙德建议的那样,我最终使用了 UI Automation。 AutomationElementName 属性值包含工具提示中的文本,事实证明正是代码所需的内容。我正在循环浏览桌面的所有子窗口,其中所有工具提示都驻留在其中,并且我仅显示属于拥有鼠标下窗口的进程的工具提示:

    public static bool Report(IntPtr hWnd, IntPtr lParam)
    {
        if (getWindowClassName(hWnd).Contains("tool"))
        {
            AutomationElement element = AutomationElement.FromHandle(hWnd);
            string value = element.Current.Name;

            if (value.Length > 0)
            {
                uint currentWindowProcessId = 0;
                GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);

                if (element.Current.ProcessId == currentWindowProcessId)
                    Console.WriteLine(value);
            }
        }

        return true;
    }


    static void Main(string[] args)
    {
        callBackPtr = new CallBackPtr(Report);

        do
        {
            System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF

            currentWindowHWnd = WindowFromPoint(mouse);
            if (currentWindowHWnd != IntPtr.Zero)
                EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);

            Thread.Sleep(1000);
        }
        while (true);
    }

I ended up using UI Automation, as Raymond suggested. AutomationElement, who's Name property value contains the text in case of tooltips, proved to be exactly what the code required. I'm cycling through all the Desktop's child windows, where all the tooltips reside and I only display those that belong to the process that owns the window under the mouse:

    public static bool Report(IntPtr hWnd, IntPtr lParam)
    {
        if (getWindowClassName(hWnd).Contains("tool"))
        {
            AutomationElement element = AutomationElement.FromHandle(hWnd);
            string value = element.Current.Name;

            if (value.Length > 0)
            {
                uint currentWindowProcessId = 0;
                GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);

                if (element.Current.ProcessId == currentWindowProcessId)
                    Console.WriteLine(value);
            }
        }

        return true;
    }


    static void Main(string[] args)
    {
        callBackPtr = new CallBackPtr(Report);

        do
        {
            System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF

            currentWindowHWnd = WindowFromPoint(mouse);
            if (currentWindowHWnd != IntPtr.Zero)
                EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);

            Thread.Sleep(1000);
        }
        while (true);
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文