MinGW 與 MSVC 生成 DLL 各種情況的折騰筆記


本博文由CSDN博主zuishikonghuan所作,版權歸zuishikonghuan所有,轉載請注明出處:http://blog.csdn.net/zuishikonghuan/article/details/51918076

寫這篇博客,主要是剛折騰 MinGW,相關內容網上的資料不全,而且錯誤很多

其實之前我根本沒把這個當回事,我就想 MinGW 跟 Linux 上的 GNU 編譯器不會有差別,但是事實卻不是這樣。。。

提示:所有代碼均使用 __stdcall

安裝 MSVC 和 MinGW
MSVC:安裝 Visual Studio,之后即可在開始菜單中找到“Visual Studio開發人員命令提示”,啟動后會自動配制環境變量,不多說了(之前我寫過提取 MSVC 編譯器的博客)

MinGW:這真是一個悲傷的故事,官方的下載工具總是失敗,看起來需要 ,其實,有一種更簡單的方法。。

http://www.mingw.org/wiki/InstallationHOWTOforMinGW 里面下載各個組件,然后自己解壓到一起就行。注意上面的頁面中有的組件的連接已經失效了(但放心並不多),所以只能在 MinGW 的 Sourceforge 上一點點找了。

MSYS 環境就不用了,這個下來不好用,版本很老,不知道官方為什么不更新,其實,只需要安裝一個 msysgit,MSYS 環境就有了,版本也是最新的,不過 msysgit 在 AWS 上,還是需要 才能下載。

嘿嘿,寫一個超簡單的腳本

!bash

export PATH="/c/Users/abc/Downloads/MinGW/MinGW/bin:$PATH"
bash
1
2
3
把 /c/Users/abc/Downloads/MinGW/MinGW/bin 換成你的 MinGW/bin 目錄即可,雙擊打開一個可以用 MinGW GCC、G++ 的 Bash 終端。

MinGW 調用 MinGW 生成的 DLL
自家調用自家的,也會出現問題,別不信,比如這兒有 dll.cpp 和 dlluse.cpp

include <Windows.h>

BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
) {
return TRUE;
}

extern "C" __declspec(dllexport) void WINAPI showMessage() {
MessageBoxA(0, "I am showMessage", 0, 0);
}

extern "C" __declspec(dllexport) void WINAPI showMessage2() {
MessageBoxA(0, "showMessage2", 0, 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

include <Windows.h>

extern "C" void WINAPI showMessage();
extern "C" void WINAPI showMessage2();

int main() {
showMessage();
}
1
2
3
4
5
6
7
8
如果我們這樣編譯:

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--out-implib,lib1.a

$ g++ -mwindows -static dlluse.cpp -l1 -L.

$ ./a.exe
1
2
3
4
5
這樣是沒有問題的,但是,問題出現在了 –kill-at 選項上

我們先用微軟的 dumpbin 工具來看一下導出表:

dumpbin /exports 1.dll
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file 1.dll

File Type: DLL

Section contains the following exports for 1.dll

//....

ordinal hint RVA      name

      1    0 000012BB showMessage2@0
      2    1 0000128C showMessage@0

Summary

//....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
每個函數后面都出現了一個“@n”,如果我們不希望 DLL 的導出函數中還要帶着一個 “@n” 標記(就像Windows的DLL一樣都是沒有的),在 MSVC 中我們可以通過 DEF 導出,MInGW 則提供了 –kill-at 選項。

你可能會問為什么非的不要這個呢,如果帶上這個參數占用的堆棧大小,那么如果我們想動態調用 DLL 中的函數,就得自己計算這個大小,這是不能忍受的,OK,我們使用 –kill-at 來編譯試試:

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--kill-at,--out-implib,lib1.a
1
來看看是否生成了不帶“@n”的導出函數

dumpbin /exports 1.dll
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

//....

ordinal hint RVA      name

      1    0 0000128C showMessage
      2    1 000012BB showMessage2

//....
1
2
3
4
5
6
7
8
9
10
11
12
果然如此,那么我們嘗試編譯 dlluse.cpp,但看見結果的那一刻,真是嚇死寶寶了。

$ g++ -mwindows -static dlluse.cpp -l1 -L.
C:\Users\abc\AppData\Local\Temp\cc3M0ER9.o:dlluse.cpp:(.text+0xc): undefined reference to `showMessage@0'
collect2.exe: error: ld returned 1 exit status
1
2
3
我明明有-l1 -L.,你告訴我“未定義的引用”(相當於MSVC的“無法解析的外部符號”),難不成是鬧鬼了不成。。

於是嚇得我趕緊 Google,但網上愣是沒有一個人知道如何在 MinGW 中用導入庫導入不帶at的函數。

但是也有人(包括 MinGW 官方的手冊)提供了一個方法,就是直接把 dll 輸入進去:

$ g++ -mwindows -static dlluse.cpp 1.dll
Warning: resolving _showMessage@0 by linking to _showMessage
Use --enable-stdcall-fixup to disable these warnings
Use --disable-stdcall-fixup to disable these fixups
1
2
3
4
出現了一個警告,如果想屏蔽這個警告,按照提示添加“–enable-stdcall-fixup”參數即可:

$ g++ -mwindows -static dlluse.cpp 1.dll -Wl,--enable-stdcall-fixup
1
這樣雖然能解決問題,但是總是覺得怪怪的,畢竟把一個 PE 文件當作目標文件或源文件輸入進去總是覺得不好,Linux 上的 GNU 編譯器絕對不能直接把 so 庫傳進去的,MinGW 你是要鬧哪樣?

我就是想用導入庫來調用不帶at的DLL函數,MinGW你做不到嗎。。

在接下來的時候,我一直在考慮這個問題,最終茶飯不思(咳咳,扯遠了),不過最終還是讓我找到了方法

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--output-def,1.def

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--kill-at

$ dlltool --kill-at -d 1.def --dllname 1.dll -l lib1.a

$ g++ -mwindows -static dlluse.cpp -l1 -L.

$ ./a.exe
1
2
3
4
5
6
7
8
9
解釋:第一次編譯,是按照有 at 的編譯,生成 1.dll 和一個模塊定義文件 1.def,第二次再編譯,按照沒有at的生成,第三步,使用第一步生成的 def 文件生成導入庫,一定要添加 –kill-at 選項。

生成的 1.def 內容如下

EXPORTS
showMessage2@0 @1
showMessage@0 @2
1
2
3
后面的序號部分不是必要的,要的就是“@n”

總之我之前是很懷疑這樣究竟行不行的,但事實告訴我這樣可以,用 dumpbin 查 exports 和 imports 都是沒有at的!

雖然解決了,但是心里並不高興,在 MinGW 中為了達到這個目的,需要編譯(連接)兩次同一個代碼,讓我這個強迫症渾身不舒服。

MSVC 調用 MinGW 生成的 DLL
先來說說帶 at 的怎么處理

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--output-def,1.def
1
然后就是在 MSVC 中編譯 dlluse 了

lib /machine:i386 /def:1.def
Microsoft (R) Library Manager Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

正在創建庫 1.lib 和對象 1.exp

cl /MT /c dlluse.cpp
用於 x86 的 Microsoft (R) C/C++ 優化編譯器 19.00.23918 版
版權所有(C) Microsoft Corporation。保留所有權利。

dlluse.cpp

link dlluse.obj 1.lib
Microsoft (R) Incremental Linker Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

dlluse.exe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
首先利用 MinGW 生成的 def 文件創建了導入庫,然后編譯 dlluse,連接,運行

這樣是沒有問題的。但是莫名其妙的問題在不帶 at 的DLL中出現了:

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--output-def,1.def,--kill-at

$ cat 1.def
EXPORTS
showMessage = showMessage@0 @1
showMessage2 = showMessage2@0 @2

lib /machine:i386 /def:1.def
Microsoft (R) Library Manager Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

正在創建庫 1.lib 和對象 1.exp

剛才已經cl編譯過了,現在直接連接就行

link dlluse.obj 1.lib
Microsoft (R) Incremental Linker Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

dlluse.obj : error LNK2019: 無法解析的外部符號 _showMessage@0,該符號在函數 _main 中被引用
dlluse.exe : fatal error LNK1120: 1 個無法解析的外部命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
這個問題又是鬧得我茶飯不思啊(笑),這個問題真是麻煩,MSVC 的 link 是支持 def 文件中的“=”的,但 lib 不支持,於是我就想還是用帶 at 的版本的 def,用不帶 at 的DLL文件不就行了,就像上面編譯兩次的那個是一個道理

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--output-def,1.def

$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--kill-at

標准輸出略

lib /machine:i386 /def:1.def

cl /MT /c dlluse.cpp

link dlluse.obj 1.lib

dlluse.exe
1
2
3
4
5
6
7
8
9
10
11
12
整個過程一氣呵成,沒有任何問題,正當我滿心歡喜的查看成果的時候,驚呆了,彈出的是“showMessage2”而不是 “I am showMessage”。

這又是鬧哪樣啊,不過內心也在慶幸自己導出了兩個函數,不然這個問題可能真發現不了。

不多說,dumpbin 走起

dumpbin /exports 1.dll
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file 1.dll

File Type: DLL

//....

ordinal hint RVA      name

      1    0 0000128C showMessage
      2    1 000012BB showMessage2

//....

dumpbin /exports 1.lib
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file 1.lib

File Type: LIBRARY

 Exports

   ordinal    name

         1    _showMessage2@0
         2    _showMessage@0

//....

dumpbin /imports dlluse.exe
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file dlluse.exe

File Type: EXECUTABLE IMAGE

Section contains the following imports:

1.dll
            40D000 Import Address Table
            412264 Import Name Table
                 0 time date stamp
                 0 Index of first forwarder reference

                  Ordinal     2

//....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
MSVC 按照序號導入,但是為神馬 DLL 里的序號和導入庫里的不一樣啊,我下意識就感覺是 def 出問題了,用vim 1.def看看def文件有沒有問題,果然如此:

EXPORTS
showMessage2@0 @1
showMessage@0 @2
1
2
3
看來是用 MinGW 編譯兩次產生的后遺症

於是我就想,既然這樣我就把def中的序號都刪除了吧

$ sed -i 's/ @.*//g' 1.def

$ cat 1.def
EXPORTS
showMessage2@0
showMessage@0
1
2
3
4
5
6
這下連接是沒有問題了,正當我滿心歡喜運行之時,又是一盆冷水下來,csrss告訴我:無法定位程序輸入點 showMessage@0 於動態鏈接庫 1.dll 上。

可惡,在導出函數不帶 at 的情況下,如果 def 中不帶 at,就會導致代碼中生成的弱符號“showMessage@0”找不到對應的強符號,如果def中帶 at,link是沒有問題的,但是卻只能在 PE 文件的導入表中使用“showMessage@0”而不能用“showMessage”

只能說微軟的 lib.exe 功能太弱,生成的導入庫很多功能比不上 link 生成DLL時生成的。

如果def中不帶at,那么生成的導入庫就不能與代碼正常連接,如果帶at,顯然可以與代碼連接,但卻顯然無法從DLL的不帶at中找到相應的導出函數,歸根到底,是 MSVC 的 lib.exe 不支持別名(即“=”后的內容被忽略),現在我們已經走入了死胡同。

但我突然靈光一閃(笑),剛剛的那個情況,不就說明了可以在 def 中使用帶at的名字,而連接到正確的不帶at的函數嗎,那個不是通過別名來實現的(這一點和 MinGW 不同),而是靠的函數序號,之所以不行,是因為序號和DLL中導出的序號不一致而已

想明白了這一點,問題就簡單了,我們可以這樣:

$ pexports -h dlluse.cpp -o 1.dll > 1.def

$ cat 1.def
LIBRARY 1.dll
EXPORTS
showMessage@0 @1
showMessage2@0 @2

lib /machine:i386 /def:1.def

dumpbin /exports 1.lib
Microsoft (R) COFF/PE Dumper Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file 1.lib

File Type: LIBRARY

 Exports

   ordinal    name

         2    _showMessage2@0
         1    _showMessage@0

//....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
其中的-h dlluse.cpp不能少,我們需要有一個包含函數聲明的頭文件,這樣 pexports 才能正確計算at后面的堆棧字節數。

這樣序號就對了,之后在 link 就行了!

MinGW 調用 MSVC 生成的 DLL
這個就非常簡單了,我們先用 MSVC 編譯這個 DLL(用 MSVC 編譯器的估計大家都用 DEF 導出,也就是不帶 at 的):

cl /MT /c dll.cpp
用於 x86 的 Microsoft (R) C/C++ 優化編譯器 19.00.23918 版
版權所有(C) Microsoft Corporation。保留所有權利。

dll.cpp

link /dll dll.obj user32.lib /def:dll.def
Microsoft (R) Incremental Linker Version 14.00.23918.0
Copyright (C) Microsoft Corporation. All rights reserved.

正在創建庫 dll.lib 和對象 dll.exp
1
2
3
4
5
6
7
8
9
10
11
最簡單的辦法就是上面說的 MinGW 的“個性”:

$ g++ -mwindows -static dlluse.cpp dll.dll -Wl,--enable-stdcall-fixup
1
如果不想這樣,可以這樣:

$ pexports -h dlluse.cpp dll.dll > dll.def

$ dlltool --kill-at -d dll.def --dllname dll.dll -l libdll.a

$ g++ -mwindows -static dlluse.cpp -ldll -L.
1
2
3
4
5
直接用原來的def生成導入庫再使用會出現連接錯誤的,原因就不說了,因為道理和上面的情況是一個道理。

總算寫完了,現在思路清晰多了(笑)

https://blog.csdn.net/zuishikonghuan/article/list/ 此人文章值得收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM