I'm using the MS UIA (COM) through a C++/CLI interface, and my C# application is using that C++/CLI interface (let's call this interface/dll as uiacpp)
I created the event handling mechanism in uiacpp mostly following the examples in https://msdn.microsoft.com/en-us/library/windows/desktop/ff625914(v=vs.85).aspx
The problem I'm facing is, the event handler I register to UIA is only called after I unregister the same event (same everytime / different events / event types & tests). When I register the event, I can see that the QueryInterface method of my event class gets called twice, obviously from UIA, so UIA does something with it. Then I fire the event in my test, but nothing happens. And the moment I unregister the event the QueryInterface is called a couple of more times, then event handler is called, then the release method gets called for the remaining references (about 6 more of them at this point) made by UIA to cleanup things.
Here is the code:
The C++/CLI class:
class CppUIAutomationEventHandler :
public ::IUIAutomationEventHandler
{
private:
LONG _refCount;
public:
int _eventCount;
gcroot<UIAMan::IUIAutomationEventHandler^> myHandler;
static std::list<IUIAutomationEventHandler*> *EventRegister;
// Constructor.
CppUIAutomationEventHandler() : _refCount(1), _eventCount(0)
{
}
// Constructor.
CppUIAutomationEventHandler(
UIAMan::IUIAutomationEventHandler^ aHandler)
: _refCount(1)
, _eventCount(0)
, myHandler(aHandler)
{
}
// IUnknown methods.
ULONG STDMETHODCALLTYPE AddRef()
{
ULONG ret = InterlockedIncrement(&_refCount);
return ret;
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ret = InterlockedDecrement(&_refCount);
if (ret == 0)
{
delete this;
return 0;
}
return ret;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface)
{
if (riid == __uuidof(IUnknown) || riid == __uuidof(IUIAutomationEventHandler))
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
this->AddRef();
return S_OK;
}
// IUIAutomationEventHandler methods
HRESULT STDMETHODCALLTYPE HandleAutomationEvent(::IUIAutomationElement * pSender, EVENTID eventID)
{
_eventCount++;
myHandler->HandleAutomationEvent(gcnew CUIAutomationElement(pSender, false), eventID);
return S_OK;
}
};
and here is a ref (managed c++) class method that C# calls to register the event (by using the last code at the end):
void CUIAutomation::AddAutomationEventHandler(
int eventId
, IUIAutomationElement^ element
, TreeScope scope
, IUIAutomationCacheRequest^ cacheRequest
, IUIAutomationEventHandler^ handler)
{
::IUIAutomationElement* el = safe_cast<CUIAutomationElement^>(element)->getElement();
::IUIAutomationEventHandler* _handler = new CppUIAutomationEventHandler(handler);
LastHResult = puia->AddAutomationEventHandler(
eventId
, el
, (::TreeScope)(int)scope
, (cacheRequest != nullptr) ? ((CUIAutomationCacheRequest^)cacheRequest)->getElement() : NULL
, _handler);
CppUIAutomationEventHandler::EventRegister->push_back(_handler);
};
I'm using a list of handlers to use while unregistering them. Also puia is a COM pointer that was created by:
CUIAutomation::CUIAutomation()
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
::IUIAutomation* _puia;
HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, NULL,
CLSCTX_INPROC_SERVER, IID_IUIAutomation,
(void**)&_puia);
if (SUCCEEDED(hr))
puia = _puia;
}
And finally, this is the C# calls:
automationhandler class implementation using the uiacpp:
class AutomationHandler : IUIAutomationEventHandler
{
public AutomationHandler()
{
}
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
{
Console.WriteLine("IUIAutomationEventHandler called");
}
}
and the C# register / unregister lines:
var aHandler = new AutomationHandler();
uia.AddAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), TreeScope.TreeScope_Subtree, null, aHandler);
// for debugging
bool loop = true;
while(loop)
{
Thread.Sleep(500);
}
uia.RemoveAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), aHandler);
These COM events are dispatched via the windows message loop.
That combined with the fact that you don't pump messages between register and unregister, results in the events being delayed until you unregister and return to the main message loop.
One solution would be to use await Task.Delay
instead of a blocking sleep.