1

我正在用 C++ 编写一个 Node.js 原生插件(使用node-addon-api)来与Microsoft 的 UIAutomation API交互。我正在尝试收听焦点事件,包装IUIAutomationElement导致事件的事件并将包装的元素传递给 javascript。

我可以附加一个事件侦听器(按照处理焦点事件的示例),它成功接收焦点事件和IUIAutomationElement. 但是,所有 UIAutomation 事件侦听器都在单独的线程中运行

在 UI 自动化事件处理程序中进行 UI 自动化调用是安全的,因为事件处理程序总是在非 UI 线程上调用。(参见: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. El并且passedEl具有相同的内存地址,所以我相信IUIAutomationElement当非主线程停止时它会失效。

  2. callback.NonBlockingCall与其他变量(int, string, 自定义类)完美配合

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

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

4

1 回答 1

1

为了使来自 的实例IUIAutomation API能够跨线程传递,您需要保持强引用。IUIAutomationElement基于IUnknown此,因此可以使用IUnknown::AddRefhttps://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拥有该对象及其内存。为了将它传递给另一个线程,您需要增加引用计数,否则线程一旦停止就会释放对象/内存。

于 2020-11-06T10:40:30.803 回答