0

I am using Visual Studio 2017. I added two projects in a solution. One project is in C# with WPF. The other is in VC++ with ATL. From C#, I call a function in the VC++ project, which sets low level mouse hook. A part of the code in the low level mouse proc is as follows:

MSLLHOOKSTRUCT stMouse = *(MSLLHOOKSTRUCT*)(lParam);
POINT pt = stMouse.pt;
IAccessible* pAcc;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromPoint(pt, &pAcc, &varChild);
VARIANT varRole;
hr = pAcc->get_accRole(varChild, &varRole);

When I test by clicking on a check box under View tab in MS Word 2013, then I get the role as client for WM_LBUTTONDOWN and WM_LBUTTONUP messages. But I should get the role as check box. I checked with Inspect.exe that comes with Windows 10 SDK. Inspect.exe shows role correctly as check box. Inspect.exe has two options - one to see UI Automation properties and the other to see MSAA properties. I am seeing the MSAA properties. How can I get the correct role? What method deos Inspect.exe uses?

Avinash
  • 385
  • 3
  • 11
  • 26

1 Answers1

3

Microsoft Active Accessibility / MSAA (based on the IAccessible interface) is a legacy API. You should now use Windows Automation. UIA is based on COM, and uses interfaces, the most important one being IUIAutomationElement. Inspect.exe uses UIA or MSAA.

Note .NET is compatible with UIA and WPF can expose its UI Elements to UIA pretty easily, through the AutomationPeer class and UIElement.OnCreateAutomationPeer Method method. WPF provides a default implementation that can be tweaked if needed.

Here is a similar example to yours in C++, using the UIA API, instead of MSAA (we could write the same using C#):

#include "stdafx.h" // needs <uiautomation.h>

class CCoInitialize { // https://blogs.msdn.microsoft.com/oldnewthing/20040520-00/?p=39243
public:
  CCoInitialize() : m_hr(CoInitialize(NULL)) { }
  ~CCoInitialize() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
  operator HRESULT() const { return m_hr; }
  HRESULT m_hr;
};

// this is a poor-man COM object class
class CHandler : public IUIAutomationEventHandler
{
public:
  HRESULT QueryInterface(REFIID riid, LPVOID * ppv)
  {
    if (!ppv) return E_INVALIDARG;

    *ppv = NULL;
    if (riid == IID_IUnknown || riid == IID_IUIAutomationEventHandler)
    {
      *ppv = this;
      return NOERROR;
    }
    return E_NOINTERFACE;
  }
  ULONG AddRef() { return 1; }
  ULONG Release() { return 1; }

  // this will be called by UIA
  HRESULT HandleAutomationEvent(IUIAutomationElement *sender, EVENTID eventId)
  {
    wprintf(L"Event id: %u\n", eventId);
    return S_OK;
  }
};

int main()
{
  CCoInitialize init;

  // this sample uses Visual Studio's ATL smart pointers, but it's not mandatory at all
  CComPtr<IUIAutomation> uia;
  if (SUCCEEDED(uia.CoCreateInstance(CLSID_CUIAutomation)))
  {
    // get mouse pos now
    POINT pt;
    GetCursorPos(&pt);

    // find what type of "control" was under the mouse
    CComPtr<IUIAutomationElement> element;
    if (SUCCEEDED(uia->ElementFromPoint(pt, &element)))
    {
      CComBSTR type;
      element->get_CurrentLocalizedControlType(&type);
      wprintf(L"type at %u,%u: %s\n", pt.x, pt.y, type.m_str);
    }

    // get root
    CComPtr<IUIAutomationElement> root;
    if (SUCCEEDED(uia->GetRootElement(&root)))
    {
      // add a handler that will trigger every time you open any window on the desktop
      // in the real world, you'll need to delete this pointer to avoid memory leaks of course
      CHandler *pHandler = new CHandler();
      if (SUCCEEDED(uia->AddAutomationEventHandler(UIA_Window_WindowOpenedEventId, root, TreeScope::TreeScope_Children, NULL, pHandler)))
      {
        // since this is a console app, we need to run the Windows message loop otherwise, you'll never get any UIA events
        // for this sample, just press CTRL-C to stop the program. in the real world, you'll need to get out properly
        // if you have a standard windows app, the loop will be already there
        while (TRUE)
        {
          MSG msg;
          while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
          {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
        }
      }
    }
  }
  return 0;
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Thank you. Is UIA supported by all those applications, which support MSAA? If no, then I think I should write code for both MSAA and UIA. Does UIA give lowest level element when we use ElementFromPoint? – Avinash Jun 19 '17 at 10:07
  • Read the links. MSAA is "legacy", and there is an implicit bridge between MSAA and UIA so you can forget about MSAA. – Simon Mourier Jun 19 '17 at 14:51
  • I am using UI Automation now. There is a global variable: – Avinash Jun 20 '17 at 15:48
  • Ignore the previous comment. I am facing two issues. (1) In some cases, I do not get the control with which I interact, but the control behind it. For example, in Notepad, when I click File and then Open menu item, then I get information about File click correctly. But when I click Open menu item, then I do not get information about Open menu item but of Text Editor of the Notepad, which is behind the Open menu item. – Avinash Jun 20 '17 at 15:57
  • (2) Target application becomes very slow. For example, if I set hook on Notepad, then Notepad becomes very slow. I found that this is because of ElementFromPoint method. I searched Internet to find solutions to these issues but could not find. Any idea? Thanks. – Avinash Jun 20 '17 at 15:58
  • UIA like MSAA operate out-of-process, so they're not 100% in sync with the automated app. Sometimes, you'll have to refresh the (elements) tree they have in cache. As for the "hook" thing, I've no idea what you're talking about. There's no need to hook anything with UIA or MSAA, or provide a repro case. – Simon Mourier Jun 20 '17 at 16:34
  • I am seeing hook because I want to record what I, as a user, do in a target application. For example, let Notepad be the target application. If I click on File->Open, I want to record that File->Open was clicked. That is why I set low level mouse hook and I am getting ElementFromPoint from within the hook proc. Regarding cache, I am not calling get_CachedControlType. Instead, I am calling get_CurrentControlType. Do I still need to refresh the elements tree? – Avinash Jun 20 '17 at 16:40
  • The purpose of the application I am making is to create tutorial. So, if an expert wants to teach how to do certain tasks in a target application, then he will do the actions on the target application. The actions will be recorded in a file and from that file, tutorials will be generated. – Avinash Jun 20 '17 at 16:42
  • There's probably an issue between your hook and UIA/MSAA – Simon Mourier Jun 20 '17 at 16:45
  • Can you give some sample code in ATL VC++ on how to add UI Automation event handler? – Avinash Jun 21 '17 at 05:02
  • @Avinash - I've added some additional sample code for handing events. – Simon Mourier Jun 21 '17 at 08:38