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

C++ 調用 JScript 腳本引擎:基於 msscript.ocx 的 COM 互操作完整實現

     編輯:關於C語言

前言

Windows 提供 msscript.ocx (Microsoft Script Control) 組件,允許 C++ 應用程序直接嵌入 JScript/VBScript 引擎,動態執行腳本代碼、調用腳本函數、實現宿主與腳本雙向互操作。本文詳細講解從導入類型庫、創建腳本控件、加載腳本代碼、調用函數、參數封送、結果獲取、資源清理的完整流程。

一、環境準備與類型庫導入


// 導入 msscript.ocx 生成 .tlh/.tli 文件
#import "msscript.ocx" no_namespace

// 生成的關鍵接口:
// IScriptControl - 腳本控件主接口
// IScriptProcedureCollection - 過程集合
// IScriptProcedure - 單個過程/函數

二、核心實現步驟

2.1 聲明與創建腳本控件對象


// 智能指針管理 COM 對象生命週期
CComPtr m_pScriptControl;

// 創建實例
HRESULT hr = m_pScriptControl.CoCreateInstance(__uuidof(ScriptControl));
if (FAILED(hr)) {
    // 處理錯誤:組件未註冊、版本不匹配等
    return hr;
}

2.2 設置腳本語言屬性


// 設置腳本語言為 JScript
m_pScriptControl->put_Language(L"JScript");
// 禁用 UI 交互(防止腳本彈出對話框阻塞)
m_pScriptControl->put_AllowUI(VARIANT_FALSE);
// 可選:設置超時、啟用/禁用模塊等
// m_pScriptControl->put_Timeout(5000); // 5秒超時

2.3 添加腳本代碼


// 添加 JScript 函數定義
// 注意:雙引號在寬字符串中需轉義,或使用單引號
BSTR bstrCode = SysAllocString(L"function test(str1, str2) { return str1 + '-ok-' + str2; }");
m_pScriptControl->AddCode(bstrCode);
SysFreeString(bstrCode);

// 也可加載整個 .js 文件內容
// m_pScriptControl->AddCode(bstrFileContent);

2.4 獲取函數對象


// 獲取所有過程集合
CComPtr pProcColl;
m_pScriptControl->get_Procedures(&pProcColl);

// 獲取過程數量
long nCount = 0;
pProcColl->get_Count(&nCount);

// 關鍵:GetItem 參數為 1-based 索引(非 0-based)
CComPtr pProc;
pProcColl->get_Item(_variant_t(nCount), &pProc); // 獲取最後添加的函數

// 獲取函數名
BSTR bstrName;
pProc->get_Name(&bstrName);
_bstr_t strFuncName(bstrName, false); // 智能管理 BSTR

2.5 準備調用參數 (SAFEARRAY)


// JScript 函數參數通過 SAFEARRAY(VARIANT) 傳遞
// 假設 test 函數接受 2 個字符串參數
const int nArgs = 2;

VARIANT vArg; VariantInit(&vArg);
vArg.vt = VT_BSTR;

// 創建一維 SAFEARRAY
SAFEARRAYBOUND bound = { nArgs, 0 }; // 元素數, 下界
SAFEARRAY* psa = SafeArrayCreate(VT_VARIANT, 1, &bound);
if (!psa) return E_OUTOFMEMORY;

// 填充參數
for (long i = 0; i < nArgs; i++) {
    vArg.bstrVal = SysAllocString(L"test"); // 參數值
    SafeArrayPutElement(psa, &i, &vArg);
    // 注意:此處未釋放 vArg.bstrVal,SafeArrayDestroy 會自動處理
}

2.6 執行函數調用並獲取結果


// Run 方法直接接受函數名和參數數組
_variant_t vResult;
HRESULT hr = m_pScriptControl->Run(strFuncName, &psa, &vResult);
if (SUCCEEDED(hr)) {
    // 處理返回值
    if (vResult.vt == VT_BSTR) {
        _bstr_t bstrResult(vResult.bstrVal, false);
        printf("Result: %s\n", (const char*)bstrResult); // 輸出: test-ok-test
    }
    // 其他類型轉換:VT_I4, VT_R8, VT_BOOL, VT_DISPATCH 等
    VariantClear(&vResult);
}

2.7 清理參數資源


// 手動清理 SAFEARRAY 中的 BSTR(Run 內部可能不會釋放輸入參數)
for (long i = 0; i < nArgs; i++) {
    VARIANT v; VariantInit(&v);
    SafeArrayGetElement(psa, &i, &v);
    if (v.vt == VT_BSTR) SysFreeString(v.bstrVal);
    VariantClear(&v);
}
SafeArrayDestroy(psa);

三、完整封裝類示例


class CJScriptEngine {
    CComPtr m_pEngine;

public:
    HRESULT Initialize() {
        HRESULT hr = m_pEngine.CoCreateInstance(__uuidof(ScriptControl));
        if (SUCCEEDED(hr)) {
            m_pEngine->put_Language(L"JScript");
            m_pEngine->put_AllowUI(VARIANT_FALSE);
        }
        return hr;
    }

    HRESULT AddScript(const wchar_t* script) {
        CComBSTR bstr(script);
        return m_pEngine->AddCode(bstr);
    }

    HRESULT CallFunction(const wchar_t* funcName, VARIANT* args, int argCount, VARIANT* pResult) {
        // 構建 SAFEARRAY
        SAFEARRAYBOUND bound = { argCount, 0 };
        CComSafeArray sa(1, &bound);
        for (int i = 0; i < argCount; i++) {
            sa.SetAt(i, args[i]);
        }
        
        CComBSTR bstrFunc(funcName);
        return m_pEngine->Run(bstrFunc, &sa, pResult);
    }

    void Uninitialize() { m_pEngine.Release(); }
};

// 使用示例
CJScriptEngine engine;
engine.Initialize();
engine.AddScript(L"function add(a,b) { return a+b; }");

VARIANT args[2];
args[0].vt = VT_I4; args[0].lVal = 10;
args[1].vt = VT_I4; args[1].lVal = 20;
VARIANT result; VariantInit(&result);
engine.CallFunction(L"add", args, 2, &result);
// result.lVal == 30
VariantClear(&result);
engine.Uninitialize();

四、進階場景與技巧

4.1 雙向互操作:腳本調用 C++ 對象


// 1. 定義 COM 可見接口
__interface IMyObject : IDispatch {
    HRESULT DoSomething([in] BSTR msg, [out, retval] long* result);
};

// 2. C++ 類實現接口
class CMyObject : public IDispatchImpl {
    STDMETHOD(DoSomething)(BSTR msg, long* result) {
        // C++ 實現
        *result = 42;
        return S_OK;
    }
};

// 3. 向腳本引擎添加命名項
CComPtr pObj = new CMyObject();
m_pEngine->AddObject(L"myObj", pObj, TRUE); // TRUE: 允許腳本訪問

// 4. JScript 中調用
// var result = myObj.DoSomething("Hello from JS");

4.2 錯誤處理與調試


// 捕獲腳本運行時錯誤
// 在 AddCode/Run 後檢查 m_pEngine->get_Error() 
CComPtr pError;
m_pEngine->get_Error(&pError);
if (pError) {
    long line, col; BSTR text, desc, src;
    pError->get_Line(&line);
    pError->get_Column(&col);
    pError->get_Text(&text);
    pError->get_Description(&desc);
    pError->get_Source(&src);
    // 顯示詳細錯誤信息
}

4.3 狀態持久化


// 保存腳本引擎狀態(全局變量、函數定義)
IPersistStreamInit* pPersist;
m_pEngine->QueryInterface(IID_IPersistStreamInit, (void**)&pPersist);
IStream* pStream;
CreateStreamOnHGlobal(NULL, TRUE, &pStream);
pPersist->Save(pStream, TRUE);
// 將 pStream 寫入文件/註冊表...
pStream->Release();
pPersist->Release();

// 恢復
pPersist->Load(pStream);

五、常見問題與解決

問題原因解決
CoCreateInstance 失敗 (0x80040154)msscript.ocx 未註冊管理員運行 regsvr32 msscript.ocx,確認 SysWOW64/System32 下存在
AddCode 後函數不可用語法錯誤、語言不匹配檢查 get_Error,確認 put_Language(L"JScript")
Run 返回 DISP_E_EXCEPTION腳本運行時拋出異常調用 get_Error 獲取行號、描述
參數類型不匹配VT 類型與 JScript 期望不符數字用 VT_I4/VT_R8,字符串用 VT_BSTR,布爾用 VT_BOOL,對象用 VT_DISPATCH
SAFEARRAY 內存洩漏未正確釋放 BSTR/變體使用 CComSafeArray/CComVariant 智能指針,或嚴格 VariantClear/SysFreeString/SafeArrayDestroy

六、總結

基於 msscript.ocx 的 JScript 嵌入方案,通過 COM 互操作實現了 C++ 與動態腳本的無縫集成。核心流程:導入類型庫 → 創建 IScriptControl → 設置語言 → AddCode 加載腳本 → get_Procedures/get_Item 獲取函數 → 構建 SAFEARRAY(VARIANT) 參數 → Run 執行 → 處理 _variant_t 結果 → 清理資源。掌握智能指針(CComPtr/CComBSTR/CComSafeArray/CComVariant)管理 COM 對象生命週期,可避免絕大多數內存洩漏與崩潰。進階場景下,雙向互操作、錯誤捕獲、狀態持久化進一步擴展了應用邊界。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved