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:UsersabcAppDataLocalTempcc3M0ER9.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://www.cnblogs.com/marklove/p/14267918.html