You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron调用C++插件周期性崩溃(crashpad_client_win.cc(867))求助

问题:Electron调用C++插件getOperaURL函数偶尔崩溃

我在Electron的JS代码中每秒调用一次C++插件的getOperaURL函数,多数场景正常,但偶尔触发崩溃,报错信息:

ERROR:crashpad_client_win.cc(867)] not connected

JS调用代码:

const addon = require("addon.node");

addon.getOperaURL()

C++原实现代码:

std::wstring getOperaURL(HWND windowHandle) {
    // Checking if the address bar for the given window handle is already cached
    for (const auto& cachedAddressBar : cachedAddressBars) {
        if (IsWindow(cachedAddressBar.windowHandle) && IsWindow(windowHandle) &&
            (cachedAddressBar.windowHandle == windowHandle)) {
            std::wstring cachedUrl = retrieveUrlFromAddressBar(cachedAddressBar.addressBarElement);
            return cachedUrl;
        }
    }

    HRESULT hr = CoInitialize(nullptr);
    if (FAILED(hr)) {
        return L"Error initializing COM";
    }

    // Initialize pointers to nullptr for proper resource management
    IUIAutomation* automation = nullptr;
    IUIAutomationElement* desktop = nullptr;
    IUIAutomationElement* operaWindow = nullptr;
    IUIAutomationElement* addressBar = nullptr;
    IUIAutomationCondition* addressBarCondition = nullptr;
    IUIAutomationCondition* editControlCondition = nullptr;
    IUIAutomationCondition* titleCondition = nullptr;

    do {
        // Create UI Automation instance
        hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&automation);
        if (FAILED(hr)) {
            break;
        }

        // Get the desktop element
        hr = automation->GetRootElement(&desktop);
        if (FAILED(hr)) {
            break;
        }

        // Find the Opera window using "Pane" as the control type
        hr = automation->CreatePropertyCondition(UIA_NativeWindowHandlePropertyId, CComVariant(reinterpret_cast<LONG>(windowHandle)), &addressBarCondition);
        if (FAILED(hr)) {
            break;
        }

        hr = desktop->FindFirst(TreeScope_Descendants, addressBarCondition, &operaWindow);
        if (FAILED(hr)) {
            break;
        }

        // Find the address bar using "Edit" as the control type
        hr = automation->CreatePropertyCondition(UIA_NamePropertyId, CComVariant(L"Address field"), &titleCondition);
        if (FAILED(hr)) {
            break;
        }
        hr = automation->CreatePropertyCondition(UIA_ControlTypePropertyId, CComVariant(UIA_EditControlTypeId), &editControlCondition);
        if (FAILED(hr)) {
            break;
        }
        hr = automation->CreateAndCondition(titleCondition, editControlCondition, &addressBarCondition);
        if (FAILED(hr)) {
            break;
        }
        // Find the address bar using the combined condition
        hr = operaWindow->FindFirst(TreeScope_Subtree, addressBarCondition, &addressBar);
        if (FAILED(hr)) {
            break;
        }
        // Cache the address bar element along with the window handle
        // cachedAddressBars.emplace_back(addressBar, windowHandle);

        // Retrieve and return the URL
        VARIANT addressBarValue;
        hr = addressBar->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &addressBarValue);
        if (FAILED(hr)) {
            break;
        }

        // Convert VARIANT to wstring
        std::wstring url(addressBarValue.bstrVal);
        VariantClear(&addressBarValue);

        return url;

    } while (false);

    // Cleanup resources in case of an error
    if (addressBarCondition) addressBarCondition->Release();
    if (editControlCondition) editControlCondition->Release();
    if (titleCondition) titleCondition->Release();
    if (addressBar) addressBar->Release();
    if (operaWindow) operaWindow->Release();
    if (desktop) desktop->Release();
    if (automation) automation->Release();

    CoUninitialize();
    return L"Error in UI Automation";
}

崩溃原因与修复方案

核心问题分析

  1. COM初始化/卸载不匹配:函数正常返回时直接return url,跳过CoUninitialize()调用,导致线程COM引用计数失衡,多次调用后触发异常。
  2. 资源泄漏:正常返回时未释放do-while块中创建的COM对象,资源泄漏积累引发崩溃。
  3. 缓存逻辑风险:缓存的IUIAutomationElement可能已随Opera窗口销毁失效,调用retrieveUrlFromAddressBar时访问失效对象导致崩溃。

修复后的完整代码

// 缓存结构改用智能指针自动管理COM对象生命周期
struct CachedAddressBar {
    HWND windowHandle;
    CComPtr<IUIAutomationElement> addressBarElement;

    CachedAddressBar(HWND hwnd, IUIAutomationElement* elem) : windowHandle(hwnd), addressBarElement(elem) {}
};

std::vector<CachedAddressBar> cachedAddressBars;

std::wstring retrieveUrlFromAddressBar(IUIAutomationElement* addressBar) {
    if (!addressBar) return L"";
    VARIANT val;
    HRESULT hr = addressBar->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &val);
    if (FAILED(hr)) return L"";
    std::wstring url(val.bstrVal);
    VariantClear(&val);
    return url;
}

std::wstring getOperaURL(HWND windowHandle) {
    // 先清理失效窗口的缓存项
    cachedAddressBars.erase(
        std::remove_if(cachedAddressBars.begin(), cachedAddressBars.end(),
            [](const CachedAddressBar& item) { return !IsWindow(item.windowHandle); }),
        cachedAddressBars.end()
    );

    // 检查有效缓存
    for (auto it = cachedAddressBars.begin(); it != cachedAddressBars.end(); ++it) {
        if (it->windowHandle == windowHandle && IsWindow(windowHandle)) {
            std::wstring cachedUrl = retrieveUrlFromAddressBar(it->addressBarElement);
            if (!cachedUrl.empty()) {
                return cachedUrl;
            }
            // 缓存失效,移除后重新查找
            cachedAddressBars.erase(it);
            break;
        }
    }

    HRESULT hr = CoInitialize(nullptr);
    bool comInitialized = SUCCEEDED(hr);
    // 若COM已初始化(线程之前调用过),hr返回S_FALSE,仍可继续执行
    if (!comInitialized && hr != S_FALSE) {
        return L"Error initializing COM";
    }

    // 用CComPtr自动管理COM对象,无需手动Release
    CComPtr<IUIAutomation> automation;
    CComPtr<IUIAutomationElement> desktop;
    CComPtr<IUIAutomationElement> operaWindow;
    CComPtr<IUIAutomationElement> addressBar;
    CComPtr<IUIAutomationCondition> addressBarCondition;
    CComPtr<IUIAutomationCondition> editControlCondition;
    CComPtr<IUIAutomationCondition> titleCondition;

    std::wstring result = L"Error in UI Automation";

    do {
        hr = automation.CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER);
        if (FAILED(hr)) {
            break;
        }

        hr = automation->GetRootElement(&desktop);
        if (FAILED(hr)) {
            break;
        }

        hr = automation->CreatePropertyCondition(UIA_NativeWindowHandlePropertyId, CComVariant(reinterpret_cast<LONG>(windowHandle)), &addressBarCondition);
        if (FAILED(hr)) {
            break;
        }

        hr = desktop->FindFirst(TreeScope_Descendants, addressBarCondition, &operaWindow);
        if (FAILED(hr) || !operaWindow) {
            break;
        }

        hr = automation->CreatePropertyCondition(UIA_NamePropertyId, CComVariant(L"Address field"), &titleCondition);
        if (FAILED(hr)) {
            break;
        }
        hr = automation->CreatePropertyCondition(UIA_ControlTypePropertyId, CComVariant(UIA_EditControlTypeId), &editControlCondition);
        if (FAILED(hr)) {
            break;
        }
        hr = automation->CreateAndCondition(titleCondition, editControlCondition, &addressBarCondition);
        if (FAILED(hr)) {
            break;
        }

        hr = operaWindow->FindFirst(TreeScope_Subtree, addressBarCondition, &addressBar);
        if (FAILED(hr) || !addressBar) {
            break;
        }

        // 缓存有效元素
        cachedAddressBars.emplace_back(windowHandle, addressBar);

        VARIANT addressBarValue;
        hr = addressBar->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &addressBarValue);
        if (FAILED(hr)) {
            break;
        }

        result = std::wstring(addressBarValue.bstrVal);
        VariantClear(&addressBarValue);

    } while (false);

    // 仅当当前函数成功初始化COM时才卸载
    if (comInitialized) {
        CoUninitialize();
    }

    return result;
}

关键修复点说明

  • COM生命周期管理:
    • 检查CoInitialize返回值,仅在函数自身初始化COM时调用CoUninitialize,避免重复卸载。
    • 使用CComPtr自动管理COM对象引用,彻底避免手动Release遗漏导致的资源泄漏。
  • 缓存逻辑加固:
    • 每次检查缓存前清理失效窗口的缓存项。
    • 缓存失效时自动移除并重新查找,避免访问已销毁的UI元素。
  • 错误处理增强:
    • 对所有COM调用增加空指针检查,防止后续访问空对象。
    • 统一结果返回逻辑,确保所有执行路径都能正确清理资源。

内容的提问来源于stack exchange,提问作者Samin Jilani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:49:50