這兩天用CB(Code::Blocks)寫個小程序,要編譯出DLL供VB(6)使用。CB使用mingw-gcc作為編譯器,在庫文件的產出上跟VC、VS之類的IDE略有不同。
由於C語言的基礎知識不是太好,尤其對編譯環節更是知之甚少。結果,試了幾次,導出的DLL中的函數總是無法被調用。
用VB加載時總是提示"DLL調用約定錯誤",百度之了解到VB只能調用適配__stdcall約定(這也是其他語言也能調用C的默認方式)的函數。
於是在源文件中的函數前加上__stdcall,導出后又提示"找不到DLL入口點foo in mydll.dll",搜索得知可能是導出函數的名字有問題。
打開DLL Export Viewer,載入mydll.dll,發現函數變成了"foo@4"。
網上的說法是使用__stdcall的副作用,可以用extern "C"來避免,於是又加上extern "C",結果依舊。
還有人說可以用DEF文件來控制導出的函數名,不過我也沒查到具體該怎么加入到編譯過程中。
不斷google&baidu之后,發現gcc可以在鏈接階段通過指定--kill-at參數來消除這種情況。於是,緊接着又了解了下gcc的使用方法,嘗試幾次后終於成功了。
在這不得不吐槽部分人寫的技術博客,很多問題就是只言片語帶過,讓人看得雲里霧里。我覺得人可以說錯話,但是起碼要把自己的意思表達清楚,不然胡亂湊出一篇誤人誤己。
這里編寫個簡單例子來說明下具體是如何操作的:
建立DLL項目,結構如下:
test/
----mydll.h
----mydll.c
頭文件:mydll.h
#ifndef __MYDLL_H__ #define __MYDLL_H__ #ifdef BUILD_DLL #define DLL_EXPORT __declspec(dllexport) #else #define DLL_EXPORT __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif DLL_EXPORT int __stdcall foo(int x); #ifdef __cplusplus } #endif #endif // __MYDLL_H__
C文件:mydll.c
#include "mydll.h" DLL_EXPORT int __stdcall foo(int x) { return x; }
如果你安裝了Code::Blocks(和MinGW),那么創建環境變量:
MINGW_HOME="C:\Program Files\CodeBlocks\MinGW"
PATH=%MINGW_HOME%\bin;%PATH%
打開命令行,進入我們的項目路徑中:
d:\test> #執行編譯命令 d:\test>mingw32-gcc -c -DBUILD_DLL mydll.c #執行鏈接命令,生成mydll.dll和靜態庫文件libmydll.a d:\test>mingw32-gcc -shared -o mydll.dll mydll.o -Wl,--kill-at,--out-implib,libmydll.a Creating library file: libmydll.a
以上,就是我們生成DLL的全過程了。
接下來,打開VB我們來驗證下:
Private Declare Function foo Lib "d:\test\mydll.dll" (ByVal x As Integer) As Integer Private Sub Form_Load() Debug.Print foo(10) End Sub
運行OK,立即窗口輸出10。
用Python測試下:
>>> import ctypes >>> mydll = ctypes.windll.LoadLibrary("d:\\test\\mydll.dll") >>> print mydll.foo(10) 10 >>>
好,也沒問題,那么證明結果上是正確的。
當然,由於本人水平較低,文章中肯定有描述不正確的地方,各位大神如果如果看到還請不吝指教。
參考文檔:
http://baike.baidu.com/view/2814224.htm
http://baike.baidu.com/view/1276580.htm?fr=aladdin
http://blog.sina.com.cn/s/blog_4b02b8d001000avi.html