Получить текст всплывающих подсказок из C# с помощью PInvoke

Я использую PInvoke в C#, пытаясь прочитать всплывающие подсказки, видимые в окне с известным обработчиком, но приложения, окна которых я пытаюсь проверить таким образом, аварийно завершают работу с ошибками нарушения доступа к памяти или просто не отображают текст всплывающей подсказки в член 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);

Итак, есть ли очевидная ошибка, которую мне не хватает в моем коде, что я должен изменить, чтобы эта ситуация разрешилась?

Изменить: здесь приведен весь код, чтобы вы могли протестировать его.


person luvieere    schedule 22.09.2011    source источник
comment
codereview подходит для этого вопроса.   -  person Renatas M.    schedule 22.09.2011
comment
Вы должны предоставить весь свой код. Отсутствуют многие функции P/Invoke (WindowFromPoint и т. д.).   -  person Simon Mourier    schedule 22.09.2011
comment
@Simon Mourier Они отсутствуют, потому что не имеют отношения к моей проблеме; отсутствующие работают, как и предполагалось, и я изолировал точное подмножество кода, которое вызывает у меня проблемы.   -  person luvieere    schedule 22.09.2011
comment
@luvieere - они не имеют значения, потому что мы не можем проверить ваш код, чтобы помочь вам. Хотя бы поставьте кадр стека исключений.   -  person Simon Mourier    schedule 22.09.2011
comment
@Simon Mourier Вы правы с этой точки зрения, я отредактировал вопрос, добавив в конце ссылку на весь код.   -  person luvieere    schedule 22.09.2011
comment
@luvieere - Это нужно для входа в цикл TOOLINFO на моем ПК.   -  person Simon Mourier    schedule 22.09.2011


Ответы (2)


Вы отправляете личное сообщение между процессами, что требует ручного маршалинга. Вот еще один вопрос из stackoverflow на ту же тему. Лучше было бы полностью изменить направление и использовать Active Accessibility и/или UI Automation, которые предназначены для такого рода вещей.

person Raymond Chen    schedule 22.09.2011
comment
Автоматизация пользовательского интерфейса требует сотрудничества с целевым приложением, которого, к сожалению, у меня нет. Active Accessibility, по-видимому, является предшественником текущей платформы автоматизации пользовательского интерфейса в Windows, поэтому мне это тоже может не помочь. - person luvieere; 22.09.2011
comment
Помимо этого, код, который я написал, основан на предполагаемом функционирующем решении, написанном на C++, которое я нашел в ответе на вопрос здесь: stackoverflow.com/questions/1333770/ - person luvieere; 22.09.2011
comment
Это решение предполагает, что целевое окно принадлежит тому же процессу. (Что очевидно, если вы понимаете, как нужно маршалировать буфер.) И неясно, какой сценарий требует от вас чтения всплывающих подсказок других приложений. - person Raymond Chen; 23.09.2011
comment
Вы были правы насчет использования автоматизации пользовательского интерфейса, см. мой ответ для полученного кода. Детали низкого уровня о процессе маршалинга действительно незнакомы мне, и на данный момент они больше не представляют собой барьера, который нужно преодолеть, чтобы заставить это работать. Спасибо за вашу помощь. - person luvieere; 23.09.2011

В итоге я использовал автоматизацию пользовательского интерфейса, как предложил Рэймонд. AutomationElement, чье значение свойства Name содержит текст в случае всплывающих подсказок, оказалось именно тем, что требовал код. Я просматриваю все дочерние окна рабочего стола, где находятся все всплывающие подсказки, и отображаю только те, которые принадлежат процессу, которому принадлежит окно под курсором мыши:

    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);
    }
person luvieere    schedule 23.09.2011