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

腳本引擎傳遞數組給 COM 組件:JScript/VBScript 差異深度解析與統一處理

     編輯:VC 教學

問題:同樣是「數組」,COM 看到的卻截然不同

Active Scripting (JScript/VBScript) 向 COM 組件傳遞數組時,封送機制完全不同:

  • VBScript:傳遞標準 SafeArray (VT_ARRAY | VT_VARIANT),元素類型由 Variant 承載。
  • JScript:數組實為 JS 物件,封送為 IDispatch (VT_DISPATCH),需枚舉成員提取值。

若 COM 組件僅處理一種,另一種腳本調用將失敗。本文提供統一處理邏輯,兼容兩者。

IDL 定義

[id(1), helpstring("方法InputArray")] HRESULT InputArray([in] VARIANT vData);

腳本端調用示例

// JScript: 大數組賦值極慢 (100k 元素需秒級),內存/CPU 暴增
var arr = new Array(100000);
for (var i = 0; i < 100000; i++) arr[i] = i % 256;
obj.InputArray(arr); // 傳遞 IDispatch

// VBScript: 高效,直接構建 SafeArray
Dim arr(99999)
For i = 0 To 99999: arr(i) = i Mod 256: Next
obj.InputArray arr // 傳遞 SafeArray

COM 端統一處理實現 (ATL)

STDMETHODIMP CMyComponent::InputArray(VARIANT vData) {
    LPBYTE pData = NULL;
    DWORD dwLen = 0;
    HRESULT hr;

    if (vData.vt == VT_DISPATCH) {
        // JScript 數組 -> IDispatch 枚舉
        hr = VariantEnumToBytes(vData.pdispVal, &pData, &dwLen);
    }
    else {
        // VBScript 數組 / 其他 -> SafeArray
        hr = VariantArrayToBytes(&vData, &pData, &dwLen);
    }

    if (SUCCEEDED(hr)) {
        // 處理字節數組 pData[dwLen] ...
        delete[] pData;
    }
    return hr;
}

// JScript: 獲取 _NewEnum 枚舉器 -> 逐個提取元素
HRESULT VariantEnumToBytes(IDispatch* pDisp, LPBYTE* ppBytes, DWORD* pdwBytes) {
    CComVariant varResult;
    DISPPARAMS noArgs = {0};
    HRESULT hr = pDisp->Invoke(DISPID_NEWENUM, IID_NULL, LOCALE_USER_DEFAULT,
                               DISPATCH_PROPERTYGET, &noArgs, &varResult, NULL, NULL);
    if (FAILED(hr)) return hr;
    if (FAILED(varResult.ChangeType(VT_UNKNOWN))) return E_FAIL;

    CComQIPtr pEnum(varResult.punkVal);
    if (!pEnum) return E_FAIL;

    // 1. 計算長度
    *pdwBytes = 0;
    while (pEnum->Skip(1) == S_OK) (*pdwBytes)++;

    // 2. 分配內存
    *ppBytes = new BYTE[*pdwBytes];

    // 3. 重置並填充
    pEnum->Reset();
    CComVariant varElem;
    DWORD idx = 0;
    while (pEnum->Next(1, &varElem, NULL) == S_OK) {
        if (varElem.vt != VT_I4) { hr = S_FALSE; break; } // 類型檢查
        (*ppBytes)[idx++] = (BYTE)varElem.lVal;
        varElem.Clear();
    }
    return S_OK;
}

// VBScript: 直接訪問 SafeArray 數據區
HRESULT VariantArrayToBytes(VARIANT* pVar, LPBYTE* ppBytes, DWORD* pdwBytes) {
    if (pVar->vt != (VT_VARIANT | VT_BYREF)) return E_INVALIDARG;
    VARIANT* pInner = pVar->pvarVal;
    if (!(pInner->vt & VT_ARRAY)) return E_INVALIDARG;

    SAFEARRAY* psa = (pInner->vt & VT_BYREF) ? *pInner->pparray : pInner->parray;
    if (SafeArrayGetDim(psa) != 1) return E_INVALIDARG;

    *pdwBytes = psa->rgsabound[0].cElements;
    *ppBytes = new BYTE[*pdwBytes];

    void* pvData;
    hr = SafeArrayAccessData(psa, &pvData);
    if (SUCCEEDED(hr)) {
        VARIANT* pElements = (VARIANT*)pvData;
        for (DWORD i = 0; i < *pdwBytes; i++) {
            _variant_t v = pElements[i];
            v.ChangeType(VT_UI1); // 強制轉 Byte
            (*ppBytes)[i] = v.bVal;
        }
        SafeArrayUnaccessData(psa);
    }
    SafeArrayDestroy(psa); // 注意:所有權轉移,調用者不再使用
    return hr;
}

關鍵技術點解析

  1. VT_DISPATCH 識別 JScript:vData.vt == VT_DISPATCH 是判斷依據。
  2. DISPID_NEWENUM:標準枚舉接口入口,返回 IEnumVARIANT。
  3. SafeArray 直接訪問:SafeArrayAccessData 獲取原始指針,避免逐個 SafeArrayGetElement 開銷,性能提升數量級。
  4. _variant_t 類型轉換:v.ChangeType(VT_UI1) 自動處理 Variant 到 Byte 的轉換(含溢出截斷)。
  5. 內存所有權:new BYTE[] 由調用者釋放,SafeArrayDestroy 在提取數據後釋放原 SafeArray(若所有權轉移)。

性能對比與建議

腳本100k 元素賦值耗時傳遞耗時COM 解析耗時建議
JScript極慢 (秒級)中等枚舉較慢避免大數組;必須用則預分配或用 VBArray
VBScript極快 (毫秒級)快SafeArray 直接訪問最快首選大數組傳遞語言

優化建議

  • 大數據傳輸:改用 IStream/ByteArray/共享內存/文件映射,避免腳本數組封送開銷。
  • JScript 大數組:使用 VBArray 對象轉換為 SafeArray (var safeArr = VBArray(jsArr).toArray();) 再傳遞,但賦值瓶頸仍在 JS 引擎。
  • COM 端:提供 InputArrayBytes([in] BYTE* pData, [in] ULONG len) 直接接收字節指針(需自定義封送或 C/C++ 調用)。

總結

理解腳本引擎數組封送差異,實現雙通道兼容,是 COM 組件面向腳本編程的必修課。本文提供的 VariantEnumToBytes / VariantArrayToBytes 雙模式解析器,可直接集成到 ATL/原生 COM 項目中,解決「同一接口、不同腳本」的互操作難題。

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