I am writing a Node.js native Addon in C++ (using node-addon-api) to interact with Microsofts UIAutomation API. I am trying to listen to Focus Events, wrap the IUIAutomationElement
which caused the event and pass the wrapped element to javascript.
I can attach an Event Listener (following this example for Handling Focus Events) which successful receives focus events and the IUIAutomationElement
. However all UIAutomation Event Listeners run in a seperate thread
It is safe to make UI Automation calls in a UI Automation event handler, because the event handler is always called on a non-UI thread. (see: https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-threading).
For example here I pass a lambda function to a wrapper around the IUIAutomation::AddFocusChangedEventHandler
method.
this->automation_->SubscribeToFocusChange([callback, this](IUIAutomationElement* el){
// This code here runs in a non-main thread
// It gets the correct IUIAutomationElemenet
}
In order to pass the IUIAutomationElement
back to Javascript I need to pass it to the main thread. node-addon-api
provides Napi::ThreadSafeFunction
which is meant to pass variables between threads.
Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(
env,
info[0].As<Napi::Function>(),
"Callback",
0,
1
);
this->automation_->SubscribeToFocusChange([callback, this](IUIAutomationElement* el){
// Code running in non-main thread
// el works here
callback.BlockingCall(el, [this](Napi::Env env, Napi::Function jsCallback, IUIAutomationElement* passedEl){
// Code running in main thread
// passedEl should be the same as el
}
}
Note: Here info[0]
is a function argument representing a Javascript function.
The problem is that while el
works, any functions now run on passedEl
throw exceptions.
For example:
BSTR elControlType;
BSTR passedElcontrolType;
// Following works perfectly
HRESULT hr = this->el->get_CurrentLocalizedControlType(&controlType);
// This throws an exception and stops the program
HRESULT hr = this->passedEl->get_CurrentLocalizedControlType(&controlType);
What I've tried
El
andpassedEl
have the same memory address so I believe theIUIAutomationElement
is being invalidated when the non-main thread stops.callback.NonBlockingCall
works perfectly with other variables (int
,string
, custom classes)
My question is what's the correct way of passing an IUIAutomationElement
between threads?
From what I've read I need to stop Microsoft from reclaiming the object when the non-main thread stops. I believe to do this I need to get and store a reference to the object but havn't found any documentation for how.