https://blog.csdn.net/liyuanbhu/article/details/42612365html
網絡上關於用 MinGW gcc 生成動態連接庫的文章不少。介紹的方法也都略有不一樣。此次我在一個項目上恰好須要用到,因此就花了點時間將網上介紹的各類方法都實驗了一遍。另外,還根據本身的理解試驗了些網上沒有提到的方法。這裏,我就將這兩天得到的成果總結一下。程序員
首先說一下個人開發環境:windows
gcc version 4.9.2 (Rev1, Built by MSYS2 project)網絡
Target: i686-w64-mingw32函數
Thread model: posix測試
--disable-sjlj-exceptions --with-dwarf2優化
另外,爲了試驗生成的 dll 是否通用。測試代碼時還用到了 Visual Stdio 2010。ui
在試驗一種新的功能時,我通常會從最簡單的代碼開始。spa
下面的命令行將這個代碼編譯成 dll。.net
gcc dlltest.c -shared -o dlltest.dll -Wl,--out-implib,dlltest.lib
其中 -shared 告訴gcc dlltest.c 文件須要編譯成動態連接庫。-Wl 表示後面的內容是ld 的參數,須要傳遞給 ld。 --out-implib,dlltest.lib 表示讓ld 生成一個名爲 dlltest.lib 的導入庫。
若是還須要 .def 文件,則上面的命令行能夠寫爲:
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a
gcc main.c dlltest.lib -o main.exe
運行結果爲:
Hello :666
說明生成的dlltest.dll是正確的。另外,也能夠用dependecy walker 查看相互調用的關係。
實際上,若是咱們的dll文件只是被MinGW gcc使用。都不須要生成 dlltest.lib。直接在編譯的時候將 dlltest.dll 加進去就好了。
gcc main.c dlltest.dll -o main.exe
若是在程序中動態加載dll。那麼代碼能夠這麼寫:
編譯的時候更不須要dlltest.lib 了,甚至都不須要 dlltest,dll。
gcc m2.c -o m2.exe
運行的結果也是正確的。
那麼這個dll 能夠被其餘c編譯器使用嗎?利用VC 2010來測試代表,能夠生成exe文件。若是是生成Debug模式的exe文件,執行是正常的。可是改成release模式後,每次運行都會報錯。
用VS2010 的調試功能,看了看反彙編的結果。看似都是正常的。
單步跟進_Double 函數後是這樣的:
Jmp 語句後:
00905A4D ??? 00905A4E ??? 00905A4F ??? 00905A50 ??? 00905A51 ??? 00905A52 ??? 00905A53 ???
但是在Debug 模式下:
Jmp 語句後:
而從下圖能夠看出,dlltest.dll 被加載到 6C100000 是正確的。
沒有想明白爲何會這樣,看來還須要努力,到目前爲止只成功了一小步。不過,若是是動態調用dll,卻沒有問題。
這個代碼用 VC2010 編譯執行一點問題都沒有。非常奇怪。在網上查找了一番,發現多是 MinGW gcc 生成的 lib 文件與 VC 生成的lib 文件有些細微的差異,致使在VC環境下,Debug模式下工做正常,而Release 模式工做卻不正常。爲了驗證這個結論,又找了些資料學會了如何從dll文件生成VC下可用的lib文件。
下面的方法參考了這篇博客:
http://blog.sina.com.cn/s/blog_4f183d960100gqfj.html
生成VC下可用的lib文件須要有 def 文件,前面已經說過 -Wl,--output-def,dlltest.def 就能夠生成對應的def 文件。
有了def文件以後,利用VS2010 提供的lib.exe能夠生成對應的lib文件。
lib /machine:ix86 /def:dlltest.def
將生成的dlltest.lib 文件拷到VC項目中。編譯,運行,一切正常。
咱們知道 WinAPI 函數是符合 Pascal 函數調用約定的,也就是所謂的 stdcall。而剛纔生成的dll 中的函數是使用的 C語言函數調用約定(__cdecl )。若是將其改成Pascal 函數調用約定須要修改程序代碼。
編譯命令是不變的。可是須要注意的是,這時生成的dll 文件中的函數名是有變化的。能夠參看下圖。原來是Double 如今變成了 Double@4,變成了這種相似 C++ 函數的名字了。可是這樣並不影響使用。
網上關於生成和使用dll 的文章都會寫到,生成dll 是函數聲明需添加 __declspec(dllexport),而使用dll時函數聲明要使用__declspec(dllimport)。你們都看到了,我前面的代碼中這兩個都沒有用到。那麼這兩個聲明有什麼用呢。下面就作個測試。
首先在生成dll 的代碼中增長:
編譯命令以下:
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a
M.c 文件不變:
編譯命令以下:
gcc m.c dlltest.a -o m2.exe
編譯沒有問題,執行也沒有問題。
修改一下m.c 。
編譯命令以下:
Gcc m.c dlltest.a -o m2.exe
編譯沒有問題,執行也沒有問題。
再修改一下main.c 。
編譯命令以下:
Gcc main.c dlltest.a -o m2.exe
編譯沒有問題,執行也沒有問題。 這個實驗說明__declspec(dllexport)對於函數聲明實際上是沒什麼做用的。我也比較過生成的代碼的反彙編結果,也是沒區別的。並不像有些人所說增長了__declspec(dllexport)以後生成的代碼可以更精煉。固然,這也多是如今編譯器的優化能力愈來愈強的結果。早期編譯器跟不上,可能仍是有區別的。
可是__declspec(dllexport)對於輸出變量是有影響的。看下面的測試代碼:
編譯:
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a
lib /machine:ix86 /def:dlltest.def
gcc m.c dlltest.a -o mm.exe
這樣是能夠編譯執行的,說明dlltest.a 中包含了足夠的信息。
可是第三句改成:
gcc m.c dlltest.lib -o mm.exe
則會有以下的錯誤,這也說明dlltest.a 和dlltest.lib 確實有些很小的差別。
undefined reference to `xxx'
collect2.exe: error: ld returned 1 exit status
VS2010 編譯也是相似的錯誤,沒法找到符號 xxx的定義。即便是main.c中增長了以下的聲明:extern int __declspec(dllimport) xxx;
結果也是相似的:error LNK2001: 沒法解析的外部符號 __imp__xxx
說明dlltest.lib 中就沒有 xxx 的相關信息,不可能訪問到dlltest.dll 中的xxx。
若是將dll的全局變量聲明中增長 __declspec(dllexport) ,結果就不同了。
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a
lib /machine:ix86 /def:dlltest.def
gcc m.c dlltest.a -o mm.exe
編譯成功,
gcc m.c dlltest.lib -o mm.exe
仍是失敗的。
在VS2010中編譯 m.c,仍然是失敗的。報的錯誤是:
error LNK2001: 沒法解析的外部符號 _xxx
m.c 作一些修改。增長 __declspec(dllimport)
VS2010 中編譯就能夠經過。另外,再多說一句,我試驗的結果代表,__declspec(dllimport) 與__declspec(dllexport) 對於編譯來講彷佛沒有任何區別,字面上的區別徹底是給程序員本身看的。
至此,MinGW gcc 生成 dll 的常見問題就都解決了。