首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

ActiveX 控件調用網頁 JavaScript 函數完整實現 (COM 雙向通訊)

     編輯:VC 教學

前言

ActiveX 控件嵌入網頁時,常需雙向通訊:網頁 JS 調用控件方法,控件反向調用 JS 函數。本文詳解 IObjectSafety、IServiceProvider、IWebBrowser2、IDispatch 關鍵接口鏈,提供完整 SetClientSite 獲取 window/document、get_Script 獲取腳本引擎、GetIDsOfNames/Invoke 調用 JS 函數的全流程代碼。

一、核心接口鏈路圖

IOleClientSite (容器提供)
    → QueryInterface(IID_IServiceProvider)
        → QueryService(SID_STopLevelBrowser, IID_IServiceProvider)
            → QueryService(SID_SWebBrowserApp, IID_IWebBrowser2) → m_spBrowser
                → get_Document() → m_spDoc (IHTMLDocument2)
                    → get_Script() → pScript (IDispatch, 對應 window 對象)
                        → GetIDsOfNames("jsFuncName") → DISPID
                        → Invoke(DISPID, DISPATCH_METHOD, ...) → 執行 JS

二、關鍵步驟代碼實現 (ATL/C++)

1. 接口定義 (.idl / .h)

// 控件接口需包含 SetClientSite (IOleObject 標準方法)
interface IMyActiveX : IDispatch {
    [id(1)] HRESULT SetClientSite(IOleClientSite* pClientSite);
    // 其他業務方法...
};

2. SetClientSite 實現:獲取 Browser 與 Document

STDMETHODIMP CMyActiveX::SetClientSite(IOleClientSite* pClientSite) {
    HRESULT hr = S_OK;
    // 釋放舊引用
    m_spBrowser.Release();
    m_spDoc.Release();

    if (!pClientSite) return S_OK; // 容器釋放站點

    CComPtr spSP, spSP2;
    try {
        // 1. 從 ClientSite 獲取 IServiceProvider
        hr = pClientSite->QueryInterface(IID_IServiceProvider, (void**)&spSP);
        if (FAILED(hr)) throw hr;

        // 2. 獲取頂級瀏覽器的 ServiceProvider
        hr = spSP->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, (void**)&spSP2);
        if (FAILED(hr)) throw hr;

        // 3. 獲取 IWebBrowser2 (核心瀏覽器對象)
        hr = spSP2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (void**)&m_spBrowser);
        if (FAILED(hr)) throw hr;

        // 4. 獲取 Document (IHTMLDocument2)
        hr = m_spBrowser->get_Document((IDispatch**)&m_spDoc);
        if (FAILED(hr)) throw hr;

    } catch (HRESULT err) {
        hr = err;
        // 可選:記錄日誌或 MessageBox
    }

    return hr; // 即使失敗也返回 S_OK 避免容器報錯,內部標記失敗狀態
}

3. 調用 JavaScript 函數通用方法

HRESULT CMyActiveX::CallJScriptFunction(LPCOLESTR pszFuncName, VARIANT* pVarResult, int nArgs, ...) {
    if (!m_spDoc) return E_UNEXPECTED; // 未初始化

    CComPtr spScript;
    HRESULT hr = m_spDoc->get_Script(&spScript);
    if (FAILED(hr) || !spScript) return hr;

    // 1. 獲取函數 DISPID
    CComBSTR bstrName(pszFuncName);
    DISPID dispid;
    hr = spScript->GetIDsOfNames(IID_NULL, &bstrName, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if (FAILED(hr)) return hr; // 函數不存在

    // 2. 準備參數 (可變參數列表)
    DISPPARAMS dp = {0};
    dp.cArgs = nArgs;
    if (nArgs > 0) {
        dp.rgvarg = new VARIANT[nArgs];
        va_list vl;
        va_start(vl, nArgs);
        // 注意:COM 參數順序為逆序 (最右參數在 rgvarg[0])
        for (int i = nArgs - 1; i >= 0; --i) {
            VARIANTARG* pArg = &dp.rgvarg[nArgs - 1 - i];
            VariantInit(pArg);
            // 簡化:假設調用者傳入已初始化的 VARIANT*
            // 實際應根據類型複製: *pArg = *va_arg(vl, VARIANT*);
            // 此處演示傳 BSTR:
            CComBSTR bstrParam = va_arg(vl, BSTR);
            pArg->vt = VT_BSTR;
            pArg->bstrVal = bstrParam.Detach();
        }
        va_end(vl);
    }
    dp.cNamedArgs = 0;

    // 3. 執行調用
    EXCEPINFO excep = {0};
    UINT nArgErr = 0;
    CComVariant varResult;
    hr = spScript->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
                          DISPATCH_METHOD, &dp, &varResult, &excep, &nArgErr);

    // 4. 清理參數數組
    if (dp.rgvarg) {
        for (int i = 0; i < nArgs; ++i) VariantClear(&dp.rgvarg[i]);
        delete[] dp.rgvarg;
    }

    if (pVarResult) *pVarResult = varResult.Detach();
    return hr;
}

4. 使用示例:觸發 JS alert

// 控件內部某事件中 (如按鈕點擊)
void CMyActiveX::OnInternalButtonClick() {
    // 調用網頁中定義的 function myAlert(msg) { alert(msg); }
    CComVariant varResult;
    HRESULT hr = CallJScriptFunction(L"myAlert", &varResult, 1, CComBSTR(L"來自 ActiveX 的消息!"));
    if (FAILED(hr)) {
        ATLTRACE("調用 JS 失敗: 0x%08X\n", hr);
    }
}

三、網頁端配置 (HTML/JS)

<object id="myActiveX" classid="clsid:YOUR-CLSID-HERE" width="300" height="200">
    <param name="DataPath" value="data.xml" />
</object>

<script type="text/javascript">
    // 必須定義供控件調用的函數
    function myAlert(msg) {
        alert("JS 收到: " + msg);
        document.getElementById("log").innerText += msg + "\n";
    }

    // 調用控件方法 (正向)
    function testCallControl() {
        try {
            myActiveX.SomeMethod(123, "參數");
        } catch (e) {
            console.error("控件調用失敗", e);
        }
    }
</script>

<input type="button" value="測試調用控件" onclick="testCallControl()" />
<pre id="log"></pre>

四、關鍵問題與解決方案

問題原因解決方案
SetClientSite 中 QueryService 失敗容器不支持 SID_STopLevelBrowser (非 IE 宿主)檢查宿主類型;HTA/IE/WebBrowser 控件支持;Chrome/Firefox 不支持 ActiveX
get_Script 返回 NULL文檔未就緒或無腳本引擎在 DocumentComplete 事件後調用;確保頁面有
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved