Active Scripting (JScript/VBScript) 向 COM 組件傳遞數組時,封送機制完全不同:
若 COM 組件僅處理一種,另一種腳本調用將失敗。本文提供統一處理邏輯,兼容兩者。
[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
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;
}
vData.vt == VT_DISPATCH 是判斷依據。IEnumVARIANT。SafeArrayAccessData 獲取原始指針,避免逐個 SafeArrayGetElement 開銷,性能提升數量級。v.ChangeType(VT_UI1) 自動處理 Variant 到 Byte 的轉換(含溢出截斷)。new BYTE[] 由調用者釋放,SafeArrayDestroy 在提取數據後釋放原 SafeArray(若所有權轉移)。| 腳本 | 100k 元素賦值耗時 | 傳遞耗時 | COM 解析耗時 | 建議 |
|---|---|---|---|---|
| JScript | 極慢 (秒級) | 中等 | 枚舉較慢 | 避免大數組;必須用則預分配或用 VBArray |
| VBScript | 極快 (毫秒級) | 快 | SafeArray 直接訪問最快 | 首選大數組傳遞語言 |
IStream/ByteArray/共享內存/文件映射,避免腳本數組封送開銷。VBArray 對象轉換為 SafeArray (var safeArr = VBArray(jsArr).toArray();) 再傳遞,但賦值瓶頸仍在 JS 引擎。InputArrayBytes([in] BYTE* pData, [in] ULONG len) 直接接收字節指針(需自定義封送或 C/C++ 調用)。理解腳本引擎數組封送差異,實現雙通道兼容,是 COM 組件面向腳本編程的必修課。本文提供的 VariantEnumToBytes / VariantArrayToBytes 雙模式解析器,可直接集成到 ATL/原生 COM 項目中,解決「同一接口、不同腳本」的互操作難題。