??本博文由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
把 /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);
}
#include <Windows.h>
extern "C" void WINAPI showMessage();
extern "C" void WINAPI showMessage2();
int main() {
showMessage();
}
如果我们这样编译:
$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--out-implib,lib1.a
$ g++ -mwindows -static dlluse.cpp -l1 -L.
$ ./a.exe
这样是没有问题的,但是,问题出现在了 –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 [email protected]0
2 1 0000128C [email protected]0
Summary
//....
每个函数后面都出现了一个“@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
来看看是否生成了不带“@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
//....
果然如此,那么我们尝试编译 dlluse.cpp,但看见结果的那一刻,真是吓死宝宝了。
$ g++ -mwindows -static dlluse.cpp -l1 -L.
C:\Users\abc\AppData\Local\Temp\cc3M0ER9.o:dlluse.cpp:(.text+0xc): undefined reference to `[email protected]‘
collect2.exe: error: ld returned 1 exit status
我明明有-l1 -L.
,你告诉我“未定义的引用”(相当于MSVC的“无法解析的外部符号”),难不成是闹鬼了不成。。
于是吓得我赶紧 Google,但网上愣是没有一个人知道如何在 MinGW 中用导入库导入不带at的函数。
但是也有人(包括 MinGW 官方的手册)提供了一个方法,就是直接把 dll 输入进去:
$ g++ -mwindows -static dlluse.cpp 1.dll
Warning: resolving [email protected]0 by linking to _showMessage
Use --enable-stdcall-fixup to disable these warnings
Use --disable-stdcall-fixup to disable these fixups
出现了一个警告,如果想屏蔽这个警告,按照提示添加“–enable-stdcall-fixup”参数即可:
$ g++ -mwindows -static dlluse.cpp 1.dll -Wl,--enable-stdcall-fixup
这样虽然能解决问题,但是总是觉得怪怪的,毕竟把一个 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
解释:第一次编译,是按照有 at 的编译,生成 1.dll 和一个模块定义文件 1.def,第二次再编译,按照没有at的生成,第三步,使用第一步生成的 def 文件生成导入库,一定要添加 –kill-at 选项。
生成的 1.def 内容如下
EXPORTS
showMessage2@0 @1
showMessage@0 @2
后面的序号部分不是必要的,要的就是“@n”
总之我之前是很怀疑这样究竟行不行的,但事实告诉我这样可以,用 dumpbin 查 exports 和 imports 都是没有at的!
虽然解决了,但是心里并不高兴,在 MinGW 中为了达到这个目的,需要编译(连接)两次同一个代码,让我这个强迫症浑身不舒服。
MSVC 调用 MinGW 生成的 DLL
先来说说带 at 的怎么处理
$ g++ -mwindows -static -shared -o 1.dll dll.cpp -Wl,--output-def,1.def
然后就是在 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
首先利用 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 个无法解析的外部命令
这个问题又是闹得我茶饭不思啊(笑),这个问题真是麻烦,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
整个过程一气呵成,没有任何问题,正当我满心欢喜的查看成果的时候,惊呆了,弹出的是“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 [email protected]0
2 [email protected]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
//....
MSVC 按照序号导入,但是为神马 DLL 里的序号和导入库里的不一样啊,我下意识就感觉是 def 出问题了,用vim 1.def
看看def文件有没有问题,果然如此:
EXPORTS
showMessage2@0 @1
showMessage@0 @2
看来是用 MinGW 编译两次产生的后遗症
于是我就想,既然这样我就把def中的序号都删除了吧
$ sed -i ‘s/ @.*//g‘ 1.def
$ cat 1.def
EXPORTS
showMessage2@0
showMessage@0
这下连接是没有问题了,正当我满心欢喜运行之时,又是一盆冷水下来,csrss告诉我:无法定位程序输入点 [email protected] 于动态链接库 1.dll 上。
可恶,在导出函数不带 at 的情况下,如果 def 中不带 at,就会导致代码中生成的弱符号“[email protected]”找不到对应的强符号,如果def中带 at,link是没有问题的,但是却只能在 PE 文件的导入表中使用“[email protected]”而不能用“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
[email protected]0 @1
[email protected]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 [email protected]0
1 [email protected]0
//....
其中的-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
最简单的办法就是上面说的 MinGW 的“个性”:
$ g++ -mwindows -static dlluse.cpp dll.dll -Wl,--enable-stdcall-fixup
如果不想这样,可以这样:
$ 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.
直接用原来的def生成导入库再使用会出现连接错误的,原因就不说了,因为道理和上面的情况是一个道理。
总算写完了,现在思路清晰多了(笑)