VB6 專案升級至 Visual Studio 2012 (.NET Framework 4.5) 時,呼叫 C++ DLL 的函數參數為 VARIANT 型別。VB6 使用 Variant (預設型別) 可直接傳遞 Byte() 陣列,升級後改用 Object 卻出現「類型不符」錯誤。
| 特性 | VB6 Variant | .NET Object |
|---|---|---|
| 底層實作 | COM VARIANT 結構 (16 bytes) | System.Object 參考型別 (指標) |
| 陣列支援 | 內建 SAFEARRAY,自動封送 | Managed 陣列 (System.Array),非 SAFEARRAY |
| 二進制相容性 | 與 C++ VARIANT 完全相容 | 需透過 COM Interop 封送 |
| 預設值 | Empty (VT_EMPTY) | null (null reference) |
| 型別資訊 | 執行期 VT_* 標籤 | 執行期 Type 物件 |
C++ DLL 簽名:
HRESULT SendData(VARIANT data); // 期望 VT_ARRAY | VT_UI1 (Byte 陣列)
VB6 代碼:
Dim sndData As Variant ' 預設 Empty
sndData = data() ' data() 為 Byte(),sndData 變為 VT_ARRAY | VT_UI1
Result = Msocket.SendTo(sndData) ' 成功:VARIANT 直接對應
.NET 代碼:
Dim sndData As Object = VB6.CopyArray(data) ' 產生 Managed Byte()
Result = Msocket.SendTo(sndData) ' 失敗:Object 封送為 VT_UNKNOWN 或 VT_DISPATCH,非 VT_ARRAY
關鍵點: .NET 的 Byte() 是 Managed 陣列,經 COM Interop 封送為 SAFEARRAY 時,若未明確指定封送行為,預設不會產生 C++ 期望的 VT_ARRAY | VT_UI1。
建立 C++/CLI 專案 (ManagedWrapper.dll),介接 Managed 程式碼與 Native C++ DLL。
// ManagedWrapper.h
#pragma once
using namespace System;
namespace ManagedWrapper {
public ref class SocketWrapper {
private:
NativeSocket* pNative; // 原始 C++ 類別指標
public:
SocketWrapper() { pNative = new NativeSocket(); }
~SocketWrapper() { this->!SocketWrapper(); }
!SocketWrapper() { delete pNative; pNative = nullptr; }
// Managed 介面:接受 byte 陣列
int Send(array^ data) {
if (data == nullptr || data->Length == 0) return -1;
// 釘住 Managed 陣列取得原始指標
pin_ptr pinned = &data[0];
return pNative->SendTo(pinned, data->Length); // Native 簽名改為 (BYTE*, int)
}
};
}
C#/VB.NET 呼叫:
Dim wrapper As New ManagedWrapper.SocketWrapper()
Dim data() As Byte = GetData()
Dim result As Integer = wrapper.Send(data)
將 VARIANT 參數改為標準 C 介面:
// 原版
HRESULT SendTo(VARIANT data);
// 新版:兩個導出函數
extern "C" __declspec(dllexport) HRESULT SendToByteArray(const BYTE* data, int length);
extern "C" __declspec(dllexport) HRESULT SendToVariant(VARIANT data); // 保留相容
.NET 端使用 DllImport + Marshal.Copy:
Private Shared Function SendToByteArray(data As Byte(), length As Integer) As Integer
End Function
' 呼叫
Dim result As Integer = SendToByteArray(data, data.Length)
使用 System.Runtime.InteropServices 手動構造 VARIANT 結構:
Imports System.Runtime.InteropServices
Public Structure VARIANT
Public vt As Short
Public wReserved1 As Short
Public wReserved2 As Short
Public wReserved3 As Short
Public data As IntPtr // 聯合體簡化:指向 SAFEARRAY
End Structure
Private Shared Function SafeArrayCreate(vt As Short, cDims As Integer, bounds As SAFEARRAYBOUND()) As IntPtr
End Function
Private Shared Function SafeArrayAccessData(psa As IntPtr, ByRef ppData As IntPtr) As Integer
End Function
Private Shared Function SafeArrayUnaccessData(psa As IntPtr) As Integer
End Function
Public Function CreateByteArrayVariant(data As Byte()) As VARIANT
Dim bounds As SAFEARRAYBOUND() = {New SAFEARRAYBOUND With {.cElements = data.Length, .lLbound = 0}}
Dim psa As IntPtr = SafeArrayCreate(VT_UI1, 1, bounds)
Dim pData As IntPtr
SafeArrayAccessData(psa, pData)
Marshal.Copy(data, 0, pData, data.Length)
SafeArrayUnaccessData(psa)
Return New VARIANT With {.vt = VT_ARRAY Or VT_UI1, .data = psa}
End Function
' 呼叫
Dim var As VARIANT = CreateByteArrayVariant(data)
Dim result As Integer = SendTo(var)
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| C++/CLI 封裝 | 型別安全、效能好、易維護、可重構 Native 介面 | 需額外專案、需 C++/CLI 知識 | 長期專案、團隊有 C++ 能力 |
| 修改 C++ DLL | 最乾淨、無封送開銷 | 需 DLL 原始碼與重新部署 | 擁有 DLL 原始碼、可控制部署 |
| 手動 VARIANT | 零依賴、快速修補 | 複雜、易出錯、效能差、維護困難 | 無法修改 DLL、臨時方案 |
Variant 參數類型 (陣列、字串、數值、物件)Object 通常可正常封送 (VT_I4, VT_BSTR)Option Strict On 下的編譯警告