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

JavaScript/VBScript 陣列傳遞給 COM 元件的差異與實作全解析

     編輯:關於C++

問題背景:腳本語言陣列與 COM 的阻抗失配

在開發 ActiveX/COM 元件供網頁腳本呼叫時,VARIANT 是跨語言資料交換的通用容器。然而,JavaScript (JScript) 與 VBScript 對「陣列」的內部表達截然不同,導致 COM 端接收處理邏輯必須分流。本文深入剖析兩者差異,並提供完整的 ATL/COM 實作代碼,解決大量資料(如 100K 元素)傳遞時的效能瓶頸。

核心差異對照表

特性VBScript 陣列JavaScript (JScript) 陣列
內部結構標準 SAFEARRAY物件集合,非真正陣列
VARIANT 類型VT_ARRAY | VT_VARIANT (或具體型別)VT_DISPATCH (指向 IDispatch)
存取方式SafeArrayAccessData 直接記憶體存取需透過 IEnumVARIANT 列舉器逐項取值
大量資料效能極佳,記憶體連續、一次拷貝極差,逐項 Invoke 呼叫開銷大,CPU/記憶體飆高

IDL 介面定義

[
  uuid(...), 
  helpstring("MyComLib")
]
library MyComLib
{
  [
    uuid(...), 
    helpstring("MyControl")
  ]
  coclass MyControl
  {
    [default] interface IMyControl;
  };

  [
    uuid(...), 
    helpstring("IMyControl Interface"),
    dual, oleautomation
  ]
  interface IMyControl : IDispatch
  {
    [id(1), helpstring("方法 InputArray")]
    HRESULT InputArray([in] VARIANT vData);
  };
};

腳本端呼叫範例

VBScript(高效能路徑)

<script language="VBScript">
Dim arr(100000)
For i = 0 To 100000 : arr(i) = i Mod 256 : Next
MyControl.InputArray arr  ' 直接傳 SAFEARRAY,極快
</script>

JavaScript(低效能路徑,需改良)

<script language="JavaScript">
var arr = new Array(100000);
for (var i = 0; i < 100000; i++) arr[i] = i % 256;
// 直接傳遞會走 VT_DISPATCH 路徑,極慢
MyControl.InputArray(arr); 
</script>

建議:大量資料傳遞時,優先使用 VBScript,或改用 VBArray 轉換、Base64 編碼字串、或 ArrayBuffer/Blob 配合自定義介面。

COM 端雙軌處理實作 (ATL)

STDMETHODIMP CMyControl::InputArray(VARIANT vData)
{
    LPBYTE pBytes = nullptr;
    DWORD  dwLen = 0;
    HRESULT hr = E_FAIL;

    if (vData.vt == VT_DISPATCH) {
        // JavaScript 陣列 -> VT_DISPATCH
        hr = VariantEnumToBytes(vData.pdispVal, &pBytes, &dwLen);
    }
    else {
        // VBScript 陣列 -> VT_ARRAY / VT_VARIANT
        hr = VariantArrayToBytes(&vData, &pBytes, &dwLen);
    }

    if (SUCCEEDED(hr)) {
        // TODO: 對 pBytes[0..dwLen-1] 處理業務邏輯
        delete[] pBytes;
    }
    return hr;
}

路徑一:JavaScript 陣列 → 列舉器 → 位元組陣列

HRESULT VariantEnumToBytes(IDispatch* pDisp, LPBYTE* ppBytes, DWORD* pdwBytes)
{
    CComVariant varResult;
    DISPPARAMS noArgs = {0};

    // 1. 取得 _NewEnum 屬性 (DISPID_NEWENUM)
    HRESULT hr = pDisp->Invoke(DISPID_NEWENUM, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                               DISPATCH_PROPERTYGET, &noArgs, &varResult, NULL, NULL);
    if (FAILED(hr)) return hr;

    // 2. 轉為 IUnknown/IDispatch 介面指標
    if (varResult.vt != VT_UNKNOWN && varResult.vt != VT_DISPATCH) return E_FAIL;
    CComQIPtr pEnum(varResult.punkVal);
    if (!pEnum) return E_FAIL;

    // 3. 先數元素個數 (Skip 法)
    *pdwBytes = 0;
    while (pEnum->Skip(1) == S_OK) (*pdwBytes)++;

    // 4. 分配記憶體
    *ppBytes = new BYTE[*pdwBytes];
    if (!*ppBytes) return E_OUTOFMEMORY;

    // 5. 重置列舉器,逐項取值
    pEnum->Reset();
    CComVariant varElem;
    DWORD idx = 0;
    while (pEnum->Next(1, &varElem, NULL) == S_OK && idx < *pdwBytes) {
        if (varElem.vt == VT_I4 || varElem.vt == VT_UI1) {
            (*ppBytes)[idx++] = static_cast(varElem.lVal);
        }
        else {
            varElem.ChangeType(VT_UI1);
            (*ppBytes)[idx++] = varElem.bVal;
        }
    }
    return S_OK;
}

路徑二:VBScript SAFEARRAY → 直接記憶體存取

HRESULT VariantArrayToBytes(VARIANT* pVar, LPBYTE* ppBytes, DWORD* pdwBytes)
{
    // 1. 驗證型別:VT_VARIANT | VT_BYREF -> 指向另一個 VARIANT,該 VARIANT 為 VT_ARRAY
    if ((pVar->vt & (VT_VARIANT | VT_BYREF)) != (VT_VARIANT | VT_BYREF)) return E_INVALIDARG;
    VARIANT* pInner = pVar->pvarVal;
    if (!(pInner->vt & VT_ARRAY)) return E_INVALIDARG;

    // 2. 取得 SAFEARRAY 指標
    SAFEARRAY* psa = (pInner->vt & VT_BYREF) ? *pInner->pparray : pInner->parray;
    if (!psa || SafeArrayGetDim(psa) != 1) return E_INVALIDARG; // 僅支援一維

    // 3. 鎖定記憶體並拷貝
    VARIANT* pData = nullptr;
    HRESULT hr = SafeArrayAccessData(psa, reinterpret_cast(&pData));
    if (SUCCEEDED(hr)) {
        *pdwBytes = psa->rgsabound[0].cElements;
        *ppBytes = new BYTE[*pdwBytes];
        for (DWORD i = 0; i < *pdwBytes; ++i) {
            CComVariant v = pData[i];
            v.ChangeType(VT_UI1); // 統一轉 Byte
            (*ppBytes)[i] = v.bVal;
        }
        SafeArrayUnaccessData(psa);
    }
    // 注意:此處不銷毀 psa,所有權在呼叫端
    return hr;
}

效能優化關鍵點

  1. 避免 JavaScript 大陣列直傳:100K 元素逐項 Invoke 會導致秒級延遲,建議改用 VBScript 或 VBArray.toArray() 轉換後再傳(但仍屬 VT_DISPATCH)。
  2. 二進位資料改用 Base64 字串:btoa(String.fromCharCode.apply(null, uint8Array)) → COM 端 CryptStringToBinary 解碼,單次跨界呼叫,效能提升 100 倍以上。
  3. 記憶體管理:new BYTE[] 對應 delete[],SafeArrayAccessData 必配 SafeArrayUnaccessData,CComVariant 自動清理。
  4. 錯誤代碼語意化:回傳 E_INVALIDARG、E_OUTOFMEMORY、TYPE_E_TYPEMISMATCH 便於腳本端 try/catch 區分處理。

現代替代方案

若專案允許使用較新技術棧,建議棄用 ActiveX,改採:

  • WebAssembly + JavaScript TypedArray:零拷貝共享記憶體,極致效能。
  • Web Workers + Transferable Objects:主執行緒不阻塞,資料所有權轉移無序列化開銷。
  • Native Messaging / WebSocket:與本地原生程式雙向通訊,無瀏覽器沙箱限制。

總結

理解 VARIANT 在跨語言邊界的具體表現,是編寫高效能 COM 元件的關鍵。VBScript 的 SAFEARRAY 映射完美契合 COM 模型;JavaScript 的集合物件則需額外列舉開銷。實務上,大量二進位資料傳遞請優先選擇字串編碼或現代 Web API,將 COM 互通層限制在輕量級控制指令上,兼顧相容與效能。

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