dll = MinGW gcc 生成動態連接庫 dll 的一些問題彙總

 

MinGW gcc 生成動態連接庫 dll 的一些問題彙總

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 2010ui

在試驗一種新的功能時,我通常會從最簡單的代碼開始。spa

  1. //dlltest.c
  2. int Double(int x)
  3. {
  4. return x * 2;
  5. }

 

下面的命令行將這個代碼編譯成 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

 

  1. //main.c
  2. #include <stdio.h>
  3. int Double(int x);
  4. int main(void)
  5. {
  6. printf("Hello :%d\n", Double(333));
  7. return 0;
  8. }

 

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。那麼代碼能夠這麼寫:

  1. //m2.c
  2. define UNICODE 1
  3.  
  4. #include <windows.h>
  5. #include <stdio.h>
  6.  
  7. typedef int (*INT_FUNC)(int);
  8. int main(void)
  9. {
  10. INT_FUNC db;
  11. HINSTANCE hInstLibrary = LoadLibrary( L"dlltest.dll");
  12. printf("LoadLibrary\n");
  13. db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
  14.  
  15. printf("Hello :%d\n", db(333));
  16. FreeLibrary(hInstLibrary);
  17.  
  18. return 0;
  19. }

編譯的時候更不須要dlltest.lib 了,甚至都不須要 dlltest,dll

gcc m2.c -o m2.exe

運行的結果也是正確的。

那麼這個dll 能夠被其餘c編譯器使用嗎?利用VC 2010來測試代表,能夠生成exe文件。若是是生成Debug模式的exe文件,執行是正常的。可是改成release模式後,每次運行都會報錯。


 

VS2010 的調試功能,看了看反彙編的結果。看似都是正常的。

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. int a;
  4. a = Double( 333);
  5. 01021000 push 14Dh
  6. 01021005 call _Double (1021024h)
  7. printf("Hello :%d\n", a);
  8. 0102100A push eax
  9. 0102100B push offset string "Hello :%d\n" (10220F4h)
  10. 01021010 call dword ptr [__imp__printf (10220A0h)]
  11. 01021016 add esp,0Ch
  12. getchar();
  13. 01021019 call dword ptr [__imp__getchar (102209Ch)]
  14. return 0;
  15. 0102101F xor eax,eax
  16. }

 

單步跟進_Double 函數後是這樣的:

  1. _ Double:
  2. 01021024 jmp dword ptr ds:[1020000h]
  3. 0102102 A nop
  4. 0102102 B nop

Jmp 語句後:

00905A4D  ???  
00905A4E  ???  
00905A4F  ???  
00905A50  ???  
00905A51  ???  
00905A52  ???  
00905A53  ???

但是在Debug 模式下:

  1. _Double:
  2. 011B144C jmp dword ptr [__imp__Double (11B8340h)]
  3. 011B1452 nop
  4. 011B1453 nop
  5. 011B1454 int 3
  6. 011B1455 int 3

Jmp 語句後:

  1. 6C101560 push ebp
  2. 6C101561 mov ebp,esp
  3. 6C101563 mov eax,dword ptr [ebp+8]
  4. 6C101566 add eax,eax
  5. 6C101568 pop ebp
  6. 6C101569 ret

而從下圖能夠看出,dlltest.dll 被加載到 6C100000 是正確的。

 

沒有想明白爲何會這樣,看來還須要努力,到目前爲止只成功了一小步。不過,若是是動態調用dll,卻沒有問題。

  1. #include <windows.h>
  2.  
  3. typedef int (*INT_FUNC)(int);
  4. int _tmain(int argc, _TCHAR* argv[])
  5. {
  6. INT_FUNC db;
  7. HINSTANCE hInstLibrary = LoadLibrary( L"dlltest.dll");
  8. printf("LoadLibrary ");
  9. db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
  10.  
  11. printf("Hello :%d\n", db(333));
  12. FreeLibrary(hInstLibrary);
  13. getchar();
  14. return 0;
  15. }

 

這個代碼用 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 函數調用約定須要修改程序代碼。


  1. //dlltest.c
  2. int _stdcall Double(int x)
  3. {
  4.     return x * 2;
  5. }
  6.  
  7. //main.c
  8. #include <stdio.h>
  9. int _stdcall Double(int x);
  10. int main(void)
  11. {
  12.         printf("Hello :%d\n", Double(333));
  13.         return 0;
  14. }

編譯命令是不變的。可是須要注意的是,這時生成的dll 文件中的函數名是有變化的。能夠參看下圖。原來是Double 如今變成了 Double@4,變成了這種相似 C++ 函數的名字了。可是這樣並不影響使用。

 

網上關於生成和使用dll 的文章都會寫到,生成dll 是函數聲明需添加 __declspec(dllexport),而使用dll時函數聲明要使用__declspec(dllimport)。你們都看到了,我前面的代碼中這兩個都沒有用到。那麼這兩個聲明有什麼用呢。下面就作個測試。

首先在生成dll 的代碼中增長:

  1. //dlltest.c
  2. int __declspec(dllexport) _stdcall Double(int x);
  3.  
  4. int _stdcall Double(int x)
  5. {
  6. return x * 2;
  7. }

 

 編譯命令以下:

gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a

 

M.c 文件不變:

  1. //m.c
  2. #include <stdio.h>
  3.  
  4. int _stdcall Double(int x);
  5.  
  6. int main(void)
  7. {
  8. printf("Hello :%d\n", Double(333));
  9. return 0;
  10. }

 

編譯命令以下:

gcc m.c dlltest.a -o m2.exe

 

編譯沒有問題,執行也沒有問題。

修改一下m.c 

  1. //m.c
  2. #include <stdio.h>
  3.  
  4. int __declspec(dllimport) _stdcall Double(int x);
  5.  
  6. int main(void)
  7. {
  8. printf("Hello :%d\n", Double(333));
  9. return 0;
  10. }

 

編譯命令以下:

Gcc m.c dlltest.a -o m2.exe

 

編譯沒有問題,執行也沒有問題。

再修改一下main.c 

  1. //main.c
  2. #include <stdio.h>
  3.  
  4. int __declspec(dllexport) _stdcall Double(int x);
  5.  
  6. int main(void)
  7. {
  8. printf("Hello :%d\n", Double(333));
  9. return 0;
  10. }

 

編譯命令以下:

Gcc main.c dlltest.a -o m2.exe

 

編譯沒有問題,執行也沒有問題。 這個實驗說明__declspec(dllexport)對於函數聲明實際上是沒什麼做用的。我也比較過生成的代碼的反彙編結果,也是沒區別的。並不像有些人所說增長了__declspec(dllexport)以後生成的代碼可以更精煉。固然,這也多是如今編譯器的優化能力愈來愈強的結果。早期編譯器跟不上,可能仍是有區別的。

 

可是__declspec(dllexport)對於輸出變量是有影響的。看下面的測試代碼:

  1. //dlltest.c
  2. int Double(int x);
  3. int xxx = 123;
  4. int Double(int x)
  5. {
  6. return x * 2;
  7. }
  8.  
  9. //m.c
  10. #include <stdio.h>
  11.  
  12. int Double(int x);
  13. extern int xxx;
  14. int main(void)
  15. {
  16. printf("Hello :%d\n", Double(333));
  17. printf("%d", xxx);
  18. return 0;
  19. }


編譯:

 

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) ,結果就不同了。

  1. //dlltest.c
  2. int Double(int x);
  3. int __declspec(dllexport) xxx = 123;
  4. int Double(int x)
  5. {
  6. return x * 2;
  7. }
  8.  
  9. //m.c
  10. #include <stdio.h>
  11.  
  12. int Double(int x);
  13. extern int xxx;
  14. int main(void)
  15. {
  16. printf("Hello :%d\n", Double(333));
  17. printf("%d", xxx);
  18. return 0;
  19. }

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)

  1. //m.c
  2. #include <stdio.h>
  3.  
  4. int Double(int x);
  5. int __declspec(dllimport) xxx;
  6. int main(void)
  7. {
  8. printf("Hello :%d\n", Double(333));
  9. printf("%d", xxx);
  10. return 0;
  11. }

 

VS2010 中編譯就能夠經過。另外,再多說一句,我試驗的結果代表,__declspec(dllimport) __declspec(dllexport) 對於編譯來講彷佛沒有任何區別,字面上的區別徹底是給程序員本身看的。

至此,MinGW gcc 生成 dll 的常見問題就都解決了。

相關文章
相關標籤/搜索