如何在线程之间传递 IUIAutomationElement

How to pass an IUIAutomationElement between threads

我正在用 C++ 编写 Node.js 本机插件(使用 node-addon-api) to interact with Microsofts UIAutomation API。我正在尝试收听 Focus Events,包装导致事件的 IUIAutomationElement 并传递包装的元素到 javascript.

我可以附加一个成功接收焦点事件和 IUIAutomationElement 的事件侦听器(以 Handling Focus Events 为例)。但是,所有 UIAutomation 事件侦听器 运行 在单独的线程中

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://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-threading).

例如,我将 lambda 函数传递给 IUIAutomation::AddFocusChangedEventHandler 方法的包装器。

this->automation_->SubscribeToFocusChange([callback, this](IUIAutomationElement* el){
    // This code here runs in a non-main thread
    // It gets the correct IUIAutomationElemenet
}

为了将 IUIAutomationElement 传递回 Javascript,我需要将它传递给主线程。 node-addon-api 提供了 Napi::ThreadSafeFunction 是为了在线程之间传递变量。

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
    }
}

注:这里info[0]是一个函数参数,表示一个Javascript函数。

问题是,虽然 el 有效,但现在 运行 在 passedEl 上的任何函数都会抛出异常。

例如:

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);

我试过的

  1. ElpassedEl 具有相同的内存地址所以我相信 IUIAutomationElement 在非主线程停止时无效。

  2. callback.NonBlockingCall 与其他变量完美配合(intstring、自定义 类)

我的问题是在线程之间传递 IUIAutomationElement 的正确方法是什么?

根据我的阅读,我需要阻止 Microsoft 在非主线程停止时回收该对象。我相信要做到这一点,我需要获取并存储对该对象的引用,但还没有找到有关如何操作的任何文档。

为了跨线程传递来自 IUIAutomation API 的实例,您需要保持强引用。 IUIAutomationElement 基于 IUnknown 因此可以使用 IUnknown::AddRef (https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref).

来完成

这会添加对引用计数的引用,这意味着一旦创建它的线程停止并因此停止持有该对象,该对象就不会失效。

最终释放对象及其内存也很重要,这可以通过 IUnknown::Release (https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release) 来完成。

这可以通过制作像 std::shared_ptr 这样的包装器来推广,这将有助于管理引用,但是我还没有弄清楚如何这样做。

TL;DR:创建 IUIAutomationElement 的线程拥有该对象及其内存。为了将它传递给另一个线程,您需要增加引用计数,否则线程将在停止后释放对象/内存。