前言:為什麼需要轉換?
VB6/VBA 開發者常需調用 Windows API 實現高級功能,而 MSDN、開源項目多提供 C/C++ 版本定義。掌握 VC++ → VB 核心類型映射、常量轉換、結構對齊、函數聲明規則,即可無障礙移植代碼。
一、常量定義轉換
1. 簡單宏 #define
// VC++
#define STD_COPY 1
#define LB_SELECTSTRING 0x018C
' VB
Public Const STD_COPY = 1
Public Const LB_SELECTSTRING = &H18C ' 十六進制 0x -> &H
2. 表達式常量
// VC++
#define TCN_FIRST (0U - 550U) ' U 表示 unsigned long
' VB
' VB 無 unsigned 類型,Long 為有符號 32 位,存儲位模式相同
Public Const TCN_FIRST = (0 - 550) ' 直接去掉 U/L 後綴
' 同理:#define VAL 123L -> Public Const VAL = 123
3. 位運算組合
// VC++
#define CDDS_ITEMPOSTERASE (CDDS_ITEM | CDDS_POSTERASE)
' VB
' | (按位或) -> Or
Public Const CDDS_ITEMPOSTERASE = (CDDS_ITEM Or CDDS_POSTERASE)
二、結構體 (struct) 轉換 (Type)
類型映射核心表
| VC++ 類型 | VB 類型 | 說明 |
| char / signed char | Byte | 1 字節 |
| unsigned char / BYTE | Byte | |
| short / SHORT | Integer | 2 字節 |
| int / long / LONG / INT | Long | 4 字節 (Win32 下 int=long=4 字節) |
| unsigned int / UINT / DWORD / ULONG | Long | VB 無無符號,用 Long 存儲位模式 |
| WPARAM / LPARAM / LRESULT | Long | 32 位系統下均為 4 字節 |
| HWND / HDC / HANDLE / HBRUSH / HKEY / HINSTANCE | Long | 句柄本質是指針/索引,32 位下用 Long |
| float | Single | 4 字節 |
| double | Double | 8 字節 |
| BOOL | Long | VC++ 4 字節,VB Boolean 僅 2 字節,必須用 Long |
| LPSTR / LPCSTR / char* | String | 傳遞給 API 時 VB 自動轉 ANSI 指針 (ByVal) |
| LPWSTR / LPCWSTR / wchar_t* | String | VB6 內部 Unicode,傳遞時轉 ANSI;建議用 Byte 數組或 StrPtr 配合 Wide API |
| 指針類型 (* / **) | Long | 地址值存儲 |
| VARIANT | Variant | 直接對應 |
實戰轉換示例
// VC++ (commctrl.h)
typedef struct tagTBSAVEPARAMSA {
HKEY hkr;
LPCSTR pszSubKey;
LPCSTR pszValueName;
} TBSAVEPARAMSA, *LPTBSAVEPARAMSA;
' VB
Public Type TBSAVEPARAMSA
hkr As Long ' HKEY -> Long
pszSubKey As String ' LPCSTR -> String
pszValueName As String
End Type
' 注意:VB Type 不支持指針成員,結構大小固定。
' 若結構含可變長數組/指針,需拆分為定長部分 + 額外分配內存 (VarPtr/CopyMemory) 處理。
關鍵注意點
- 對齊: VC++ 默認 8 字節對齊 (`#pragma pack(8)`),VB `Type` 成員按聲明順序緊湊排列 (1 字節對齊)。涉及 API 交互的結構,**必須在 VC++ 端用 `#pragma pack(1)` 或 `#pragma pack(4)` 確保一致**,或在 VB 端手動添加填充字節 (`Dummy As Byte` 等)。
- 可變長數組: `DWORD dwReserved[10]` 易轉換;`BYTE data[1]` (柔性數組) 在 VB 中無法直接表示,需定義定長部分,運行時分配內存 `GlobalAlloc`,用 `CopyMemory` 讀寫數據區。
- 嵌套結構: VB `Type` 支持嵌套,成員類型為另一個 `Public Type`。
三、函數/ API 聲明轉換
標準模板
// VC++
WINCOMMCTRLAPI HWND WINAPI CreateStatusWindowA(
LONG style, LPCSTR lpszText, HWND hwndParent, UINT wID
); // 來自 ComCtl32.dll
' VB
' 1. 確定 DLL 名 (MSDN 文檔底部或 Dependency Walker 查看)
' 2. 返回類型映射 (HWND -> Long)
' 3. 參數映射 (按類型表)
' 4. 字符串參數建議 ByVal ... As String (VB 自動 ANSI 轉換)
' 5. 其餘參數 ByVal 避免修改原值
Public Declare Function CreateStatusWindowA Lib "ComCtl32.dll" (
ByVal style As Long,
ByVal lpszText As String,
ByVal hwndParent As Long,
ByVal wID As Long
) As Long
調用約定與修飾符
| VC++ 關鍵字 | 含義 | VB 處理 |
| WINAPI / __stdcall | 標準調用 (被調用者清棧) | VB 默認即 StdCall,無需聲明 |
| __cdecl | C 調用 (調用者清棧) | VB **不支持** 直接調用,需編寫 StdCall 包裝 DLL 中轉 |
| __fastcall | 寄存器傳參 | VB 不支持,同需包裝 |
| __declspec(dllimport) | 導入聲明 | VB `Declare` 隱含此意 |
字符串參數進階
LPSTR (輸出緩衝區):VB 傳 ByVal String 會導致崩潰。**正確做法**:定義固定長度字符串 `Dim buf As String * 256`,傳 `ByVal buf`,調用後用 `Left$(buf, InStr(buf, Chr$(0))-1)` 截取。
LPCSTR (只讀輸入):ByVal str As String 安全。
- Unicode 版 API (以 `W` 結尾,如 `CreateWindowExW`):VB6 內部 Unicode,可用 `StrPtr(str)` 獲取指針,聲明參數為 `ByVal Long`,或使用類型庫 (TLB) 導入。
四、回調函數轉換
// VC++
typedef BOOL (CALLBACK* WNDENUMPROC)(HWND hwnd, LPARAM lParam);
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
' VB
' 1. 模塊中聲明 (必須在標準模塊,非類模塊)
Public Declare Function EnumWindows Lib "user32" (
ByVal lpEnumFunc As Long, ' 地址傳遞
ByVal lParam As Long
) As Long
' 2. 回調函數 (標準模塊,Public)
Public Function MyEnumProc(ByVal hwnd As Long, ByVal lParam As Long) As Long
' 處理邏輯
MyEnumProc = 1 ' 繼續枚舉
End Function
' 3. 獲取函數地址調用 (VB6 無 AddressOf 運算符直接用於 API 參數)
' 技巧:使用 AddressOf 獲取地址傳給 Long 參數
Dim lpfn As Long: lpfn = AddressOf MyEnumProc
EnumWindows lpfn, 0
' 注意:VB6 AddressOf 僅在傳給 Long 參數時有效,回調函數必須在標準模塊,且項目屬性勾選 "Compile to Native Code" 且無 p-code 優化干擾。
五、常見陷阱總結
| 陷阱 | 現象 | 修正 |
| BOOL vs Boolean | VC++ 4 字節,VB Boolean 2 字節,棧錯位 | VB 用 Long 接收 BOOL |
| 字符串輸出緩衝區 | 傳 ByVal String 導致寫入只讀內存崩潰 | 預分配固定長度 String * N,傳 ByVal |
| 結構對齊不一致 | API 讀寫錯誤字段 | VC++ 加 `#pragma pack(4)` 或 VB 手動填充 |
| __cdecl 函數 | 調用後棧不平衡崩潰 | 寫 C++ 包裝 DLL 轉為 __stdcall |
| 64 位兼容 | Long 截斷指針/句柄 | VB7+ (VBA7) 用 `LongPtr` / `LongLong` + `PtrSafe` |
工具推薦
- API Viewer (VB6 自帶/增強版):內置海量 API 聲明,可直接複製
- Pinvoke.net:雖針對 .NET,但類型映射表極具參考價值
- Dependency Walker:查看 DLL 導出函數、參數個數、調用約定
- VC++ 編譯器預處理輸出 (`/E`):展開宏、條件編譯,獲取最終結構定義
總結
VC++ 轉 VB 核心在於:**類型映射表內化、結構對齊對齊、調用約定 StdCall、字符串輸出預分配、回調地址 AddressOf**。掌握這五大鐵律,配合工具輔助,將 C/C++ SDK 文檔轉化為 VB 可用聲明不再是障礙,而是標準化的機械翻譯過程。