在 COM 元件與 Web 前端互動場景中,C++ 呼叫 JavaScript 函式分為**同執行緒(IE 主執行緒)**與**跨執行緒**兩種情境。本文詳細說明兩種調用機制與完整程式碼實作。
JavaScript 函式在 COM 中以 IDispatch 介面暴露,C++ 透過 Invoke / InvokeN 調用。關鍵型別:VARIANT 承載參數與回傳值。
適用於 BHO、ActiveX 控制項等**運行於 IE 進程主執行緒**的元件。
STDMETHODIMP CJsInvoker::InvokeJsFunc(LONG para1, LONG para2,
VARIANT jsFunction, LONG* retValue) {
// 1. 從 VARIANT 取得 IDispatch 指標
CComPtr<IDispatch> jsCallback;
if (jsFunction.vt == VT_DISPATCH)
jsCallback = jsFunction.pdispVal;
else
return E_INVALIDARG;
// 2. 準備參數陣列(注意:IDispatch::Invoke 參數順序為反序)
VARIANT arg[2];
arg[0].vt = VT_I4; arg[0].lVal = para2; // 最後參數放索引 0
arg[1].vt = VT_I4; arg[1].lVal = para1; // 最先參數放索引 1
// 3. 呼叫 Invoke
DISPPARAMS dispParams = { arg, nullptr, 2, 0 };
VARIANT result;
VariantInit(&result);
HRESULT hr = jsCallback->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dispParams, &result, nullptr, nullptr);
if (SUCCEEDED(hr) && result.vt == VT_I4)
*retValue = result.lVal;
VariantClear(&result);
return hr;
}
<script type="text/javascript">
function jsCallbackFunc(a, b) { return a + b; }
var obj = new ActiveXObject("ComCallJsFunction.JsInvoker");
var ret = obj.InvokeJsFunc(1, 2, jsCallbackFunc);
alert(ret); // 顯示 3
</script>
背景執行緒無法直接存取 JS 物件(STA 單元執行緒模型),必須**編列介面指標**到目標執行緒,再**反編列**取得代理指標。
CoMarshalInterThreadInterfaceInStream:將介面指標編列為資料流CoGetInterfaceAndReleaseStream:從資料流反編列取得代理指標CoInitialize/CoUninitialize:執行緒 COM 單元初始化/清理// 主執行緒:編列介面並啟動背景執行緒
STDMETHODIMP CJsInvoker::InvokeJsFunc3(LONG para1, LONG para2,
VARIANT jsFunction, LONG* retValue) {
if (V_VT(&jsFunction) != VT_DISPATCH || jsFunction.pdispVal == NULL)
return E_INVALIDARG;
// 編列 IDispatch 到資料流
HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_IDispatch,
jsFunction.pdispVal,
&m_stream_jsfunc);
if (FAILED(hr)) return hr;
// 啟動背景執行緒
m_hThread = CreateThread(nullptr, 0, ThreadFunction, this, 0, nullptr);
return S_OK;
}
// 背景執行緒函式
DWORD WINAPI CJsInvoker::ThreadFunction(LPVOID pParam) {
CoInitialize(nullptr); // STA 初始化
CJsInvoker* pThis = static_cast<CJsInvoker*>(pParam);
CComPtr<IDispatch> script;
// 反編列取得代理指標
HRESULT hr = CoGetInterfaceAndReleaseStream(pThis->m_stream_jsfunc,
IID_IDispatch,
reinterpret_cast<LPVOID*>(&script));
if (SUCCEEDED(hr)) {
VARIANT arg[2] = { {VT_I4, 0, 0, 1}, {VT_I4, 0, 0, 2} };
DISPPARAMS dispParams = { arg, nullptr, 2, 0 };
VARIANT result;
VariantInit(&result);
script->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dispParams, &result, nullptr, nullptr);
VariantClear(&result);
}
CoUninitialize();
return 0;
}
注意:背景執行緒調用為**非同步**,JS 回傳值無法直接傳回 C++ 呼叫端,需透過回呼、事件或共享記憶體傳遞結果。
| 特性 | IE 主執行緒 | 背景執行緒 |
|---|---|---|
| COM 單元模型 | 已在 STA 內 | 需自行 CoInitialize |
| 介面指標 | 直接使用 | 需編列/反編列 |
| 同步性 | 同步阻塞 | 非同步 |
| 回傳值處理 | 直接取得 | 需額外機制 |
| 適用場景 | BHO、工具列、同步自動化 | 長時間運算、非阻塞 UI |
CoreWebView2.ExecuteScriptAsync 非同步執行 JS,支援 JSON 序列化參數/回傳值。CefV8Context::Eval 同步/非同步調用。