VB 6.0 呼叫 VC++ DLL 時,基本類型(Long、Double)按值/址傳遞相對直觀,但 使用者定義類型(UDT)陣列 傳遞卻暗藏玄機。網路上流傳的「直接對應 struct*」做法在實測中會導致資料亂碼,本文揭露根因並給出 SAFEARRAY 正解。
' VB
Private Declare Function FuncFoo Lib "MyDll.dll" _
(ByVal lParam1 As Long, ByVal lParam2 As Long) As Long
// VC++
UINT WINAPI FuncFoo(UINT lParam1, UINT lParam2);
' VB:預設 ByRef,傳遞首元素位址
Private Declare Function FuncFoo Lib "MyDll.dll" _
(lParam1 As Long, ByVal lParam2 As Long) As Long
// VC++
UINT WINAPI FuncFoo(UINT lParam1[DATASIZE], UINT lParam2);
// 或 UINT WINAPI FuncFoo(UINT* lParam1, UINT lParam2);
' VB 定義 UDT
Private Type LPDATA
Row As Long
Col As Long
Tips As String
Data As Double
End Type
' 錯誤宣告:嘗試直接對應 struct*
Private Declare Function FuncFoo Lib "MyDll.dll" _
(lParam1 As LPDATA, ByVal lParam2 As Long) As Long
// VC++ 對應嘗試
typedef struct tagData {
UINT Row;
UINT Col;
BSTR Tips; // 字串必須用 BSTR
double Data;
} *LPDATA;
UINT WINAPI FuncFoo(LPDATA lParam1[DATASIZE], UINT lParam2);
結果:資料完全錯位、亂碼。 原因在於 VB 的 UDT 陣列在記憶體中並非連續的 struct,而是被包裝在 SAFEARRAY 結構中。
typedef struct tagSAFEARRAY {
USHORT cDims; // 維度數
USHORT fFeatures; // 特徵旗標
USHORT cbElements; // 單元素大小
USHORT cLocks; // 鎖定計數
ULONG handle; // 記憶體控制碼
void HUGEP* pvData; // 實際資料指標
SAFEARRAYBOUND rgsabound[1]; // 邊界資訊
} SAFEARRAY;
' 關鍵:參數加 () 表示陣列,VB 會自動封裝成 SAFEARRAY*
Private Declare Function FuncFoo Lib "MyDll.dll" _
(lParam1() As LPDATA, ByVal lParam2 As Long) As Long
' 呼叫範例
Dim oData(1 To 3) As LPDATA
oData(1).Row = 1 : oData(1).Col = 1 : oData(1).Tips = "A" : oData(1).Data = 1.1
oData(2).Row = 2 : oData(2).Col = 2 : oData(2).Tips = "B" : oData(2).Data = 2.2
oData(3).Row = 3 : oData(3).Col = 3 : oData(3).Tips = "C" : oData(3).Data = 3.3
Call FuncFoo(oData(), 3) ' 傳遞整個陣列
// 介面定義:接收 SAFEARRAY**
UINT WINAPI FuncFoo(LPSAFEARRAY FAR* Param1, UINT lParam2);
// 實作解析
LPDATA pData = (LPDATA)((*Param1)->pvData); // 取得實際資料起始位址
UINT count = (*Param1)->rgsabound[0].cElements; // 元素個數
for (UINT i = 0; i < count; i++, pData++) {
UINT row = pData->Row;
CString tips = pData->Tips; // BSTR 自動轉換
double val = pData->Data;
// 處理業務邏輯...
}
VB 字串為 Unicode(BSTR),記憶體布局:長度前綴 + 雙位元組字元 + 雙重結束符。若在 struct 中用 char[] 或 LPCTSTR 會導致編碼錯位。
// VB 傳入 "個大12"
// 正確 BSTR 記憶體:B8 F6 B4 F3 31 00 32 00 (Unicode)
// 錯誤 char[] 讀出:2A 4E 27 59 31 00 32 00 (亂碼)
rgsabound 陣列長度等於 cDims,需巢狀迴圈索引SafeArrayDestroySafeArrayAccessData/SafeArrayUnaccessData 直接指標存取Param1 非 NULL、cDims == 1、cbElements == sizeof(tagData)VB UDT 陣列跨 DLL 傳遞的本質是 SAFEARRAY 指標傳遞。VB 端加 ()、VC++ 端解析 pvData 與 rgsabound,配合 BSTR 處理字串,即可實現零錯誤的結構陣列互通。