記錄一下從 VB 呼叫用 VC 製作的 DLL 的心得:

1. 使用 __stdcall 搭配 .def 檔製作 DLL VC 製作 DLL 的方法。

有兩種方法。

第一種方法是使用 __declspec(dllexport) 宣告要 export 的函式,讓 VC 幫忙製作 stub library,這時預設使用的 calling convension 是 __cdecl。這樣宣告後,VC 會產出 libFoo.dlllibFoo.lib 兩個檔案,其中後者就是所謂的 stub library,VC 會自動於 stub library 裡製作對應到 libFoo.dll_funcFoo 這個 symbol 的 __imp_funcFoo

這個方法的好處是,在 VC 裡的 project 有 dependency 時,會自動 link 所 depend 的 .lib 檔,使用起來非常方便。

第二種方法是使用 __stdcall 而不用預設的 __cdecl calling convension。Windows API 即是以 __stdcall 的形式存在,若有 #include <windows.h>,也可以用 WINAPI 這個 macro 代替。使用 __stdcall 就不需用 __declspec(dllexport) 宣告,而是必須自行製作 .def 檔案,詳列要 export 的那些 symbols。

由於 VB 只能接受使用此法製作的 DLL,所以我們必須選擇此法。缺點是,製作 .def 檔,當函式一多時,非常麻煩,且對於要 export 的 C++ class,更是複雜,畢竟我們很難人工處理 VC 的 C++ name mangling。

2. 處理 VB 到 C/C++ 之間的型別轉換

VB 與 C/C++ 兩者之間的型別對應,詳見 MSDN,記住幾點原則即可。

首先,VB 的 ByRef 相當於 C/C++ 的 indirect by pointer。所以 C/C++ 的參數若為 pointer 型別,在 VB 便用 ByRef 對應。不過我不清楚像 int*** 這種型別,究竟該怎麼對應就是了。

再來就是,C/C++ 的 char* 對應到 VB 直接是 ByVal As String,因為 VB 的 String 採用 BSTR 的機制,與 C/C++ 的 string 可以相容。只有兩點必須注意:

  • VB 的 String 不以 '\0' 結尾,而是前冠以長度的 header。因此,從 C/C++ 的 DLL 獲得 String 後,必須用 foo = Left(foo, InStr(foo, vbNullChar) - 1)'\0' 之後的部分去掉。
  • 若要傳 output string buffer 進 C/C++ 的 DLL 時,記得必須先製作好該 buffer,否則若 VB 的 String 只有宣告就拿來用,便只是相當於只是傳入一個 NULL。製作 buffer 的方法為:foo = String(BUF_SIZE, vbNullChar),其中 BUF_SIZE 為 buffer 所需的長度。

3. 記得設定 C/C++ DLL 的 locale

在 VB 裡,會自動依據所處的 OS 所設定的 locale 而設定 locale,但若呼叫的 DLL 內需要正確的 locale 環境時,便會有問題。因為 VB 設定的是他自己的 locale,而不是 C/C++ 的 locale,因此雖然在 VB 裡的 locale 是對的,但在 C/C++ 寫成的 DLL 裡,locale 仍然是錯的。因此,要記得在 DLL 裡提供對應到 setlocale() 的 export function,讓 VB 可以事先呼叫,設定 C/C++ 的 locale 讓 DLL 用。

第三點害我今天花了半個下午 debug,累,不過總是把技術問題都搞定了。