VB6 時代 Variant 直接支援 Byte() 陣列 (底層為 SAFEARRAY),升級至 VB.NET 後 Object 類型封送為 Managed Array,C++ DLL 接收 VARIANT 期望 VT_ARRAY | VT_UI1,導致「類型不符」。
建立 C++/CLI 專案做為中介層,將 Managed array 轉為 Native BYTE* + 長度呼叫原 DLL。
// NativeWrapper.h
#pragma once
using namespace System;
namespace NativeInterop {
public ref class VariantBridge {
public:
// 假設原 DLL 導出: int SendData(const BYTE* data, int len);
[DllImport("OriginalDll.dll", CallingConvention = CallingConvention::Cdecl)]
static int SendDataNative(const unsigned char* data, int length);
int SendData(array^ data) {
if (data == nullptr || data->Length == 0) return -1;
pin_ptr pinned = &data[0];
return SendDataNative(pinned, data->Length);
}
};
}
VB.NET 呼叫:
Dim bridge As New NativeInterop.VariantBridge()
Dim data() As Byte = GetBinaryData()
Dim result As Integer = bridge.SendData(data)
新增不依賴 VARIANT 的導出函數:
// OriginalDll.cpp
extern "C" __declspec(dllexport) int SendDataByteArray(const BYTE* data, int length) {
// 內部邏輯不變,直接用 data/length
return InternalProcess(data, length);
}
// 保留舊介面相容
extern "C" __declspec(dllexport) int SendDataVariant(VARIANT data) {
// 解析 VARIANT -> BYTE*/length 後呼叫 InternalProcess
// ...
}
VB.NET 使用 DllImport 直接呼叫新介面:
Private Shared Function SendDataByteArray(data As Byte(), length As Integer) As Integer
End Function
' 呼叫
Dim result As Integer = SendDataByteArray(data, data.Length)
使用 System.Runtime.InteropServices 手動建立 SAFEARRAY 並填入 VARIANT:
Imports System.Runtime.InteropServices
Module VariantHelper
Private Const VT_UI1 As Short = &H11
Private Const VT_ARRAY As Short = &H2000
Private Structure SAFEARRAYBOUND
Public cElements As Integer
Public lLbound As Integer
End Structure
Private Structure VARIANT
Public vt As Short
Public wReserved1 As Short
Public wReserved2 As Short
Public wReserved3 As Short
Public data As IntPtr
End Structure
Private Function SafeArrayCreate(vt As Short, cDims As Integer, bounds As SAFEARRAYBOUND()) As IntPtr
End Function
Private Function SafeArrayAccessData(psa As IntPtr, ByRef ppData As IntPtr) As Integer
End Function
Private Function SafeArrayUnaccessData(psa As IntPtr) As Integer
End Function
Private Sub SafeArrayDestroy(psa As IntPtr)
End Sub
Public Function CreateByteArrayVariant(data As Byte()) As Object
If data Is Nothing OrElse data.Length = 0 Then Return Nothing
Dim bounds As SAFEARRAYBOUND() = {New SAFEARRAYBOUND With {.cElements = data.Length, .lLbound = 0}}
Dim psa As IntPtr = SafeArrayCreate(VT_UI1, 1, bounds)
Try
Dim pData As IntPtr
SafeArrayAccessData(psa, pData)
Marshal.Copy(data, 0, pData, data.Length)
SafeArrayUnaccessData(psa)
Dim var As New VARIANT With {.vt = VT_ARRAY Or VT_UI1, .data = psa}
Return var ' 傳遞給接受 Object (VARIANT) 的 COM 介面
Catch
SafeArrayDestroy(psa)
Throw
End Try
End Function
End Module
' 使用
Dim var As Object = VariantHelper.CreateByteArrayVariant(binaryData)
comObject.SendData(var) ' 假設 COM 介面方法簽名為 SendData(ByVal data As Object)
有 C++ DLL 原始碼?
├─ 是 → 能重新編譯部署 DLL?
│ ├─ 是 → 方案二 (修改 DLL 介面) 最乾淨
│ └─ 否 → 方案一 (C++/CLI 封裝) 無需動 DLL
└─ 否 → 方案三 (手動 VARIANT) 或 方案一 (C++/CLI 包裝現有 DLL)
| 現象 | 原因 | 解決 |
|---|---|---|
| 方案一編譯失敗 | 專案目標平台不匹配 (x86/x64) | C++/CLI 專案 Platform Target 需與呼叫端一致 |
| 方案三傳遞後 DLL 讀到空資料 | SAFEARRAY 未正確釘住/複製 | 檢查 SafeArrayAccessData 後 Marshal.Copy 位址 |
| 呼叫後記憶體洩漏 | 方案三未呼叫 SafeArrayDestroy | 用 Try/Finally 確保銷毀 |
| 64 位元環境 AccessViolation | IntPtr 大小不匹配 | 確認所有 IntPtr 對應 64 位元指標 |