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

VB 呼叫 VC++ DLL 進階:結構陣列跨語言傳遞的正確姿勢

     編輯:關於VC++

問題背景

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 結構中。

正解:SAFEARRAY 機制深度解析

SAFEARRAY 結構定義


typedef struct tagSAFEARRAY {
    USHORT cDims;          // 維度數
    USHORT fFeatures;      // 特徵旗標
    USHORT cbElements;     // 單元素大小
    USHORT cLocks;         // 鎖定計數
    ULONG  handle;         // 記憶體控制碼
    void HUGEP* pvData;    // 實際資料指標
    SAFEARRAYBOUND rgsabound[1]; // 邊界資訊
} SAFEARRAY;

正確的 VB 宣告與呼叫


' 關鍵:參數加 () 表示陣列,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)  ' 傳遞整個陣列

VC++ 端正確解析 SAFEARRAY


// 介面定義:接收 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;
    // 處理業務邏輯...
}

為什麼字串必須用 BSTR?

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,需巢狀迴圈索引
  • 記憶體所有權:VB 負責配置/釋放 SAFEARRAY,VC++ 僅讀取,不可 呼叫 SafeArrayDestroy
  • 效能優化:大量資料建議改用 SafeArrayAccessData/SafeArrayUnaccessData 直接指標存取
  • 錯誤處理:檢查 Param1 非 NULL、cDims == 1、cbElements == sizeof(tagData)

總結

VB UDT 陣列跨 DLL 傳遞的本質是 SAFEARRAY 指標傳遞。VB 端加 ()、VC++ 端解析 pvData 與 rgsabound,配合 BSTR 處理字串,即可實現零錯誤的結構陣列互通。

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