MSVC和MinGW的DLL工具(Stdcall and DLL tools of MSVC and MinGW)(转帖)


------------------------------------------------------------------
本文专注于MSVC和MinGW的DLL建立和使用,包括相关工具的介绍和使用。

翻译:毛毛,原文链接: http://wyw.dcweb.cn/stdcall.htm


__stdcall 调用约定已经产生很长的时间了,一些老的调用约定如__pascal 正被逐渐废弃,而__stdcall 却成为了Win32 API的标准调用约定。和__cdecl (C/C ++默认调用约定)不同,__stdcall 被C/C++、Visual Basic、Java等语言支持,使得它成为跨语言调用的DLL的首选调用约定。

在内部,__cdecl__stdcall 函数有一些命名修饰。比如在MSVC (Microsoft Visual C++) 和 MinGW (Minimalistic GNU for Windows) GCC里,__cdecl 函数有一个下划线前缀,__stdcall 函数不仅有一个下划线前缀而且还跟有一个@开头的参数列表字节数后缀。所以double __cdecl sin(double) 内部表示为_sindouble __stdcall sin(double) 内部表示为_sin@8

但是事情还不止这些,当它们应用于DLL或用不同的编译器生成时修饰也会发生变化。下表列出了MSVC、MinGW、Digital Mars C/C++ Compiler (DMC)、Borland C++ Compiler/C++ Builder (BCC)生成的DLL的内部函数命名:

Calling Convention Internal* MSVC DLL (w/ DEF) MSVC DLL (dllexport) DMC DLL MinGW DLL BCC DLL
__stdcall _Function @n Function _Function @n _Function @n Function @n Function
__cdecl _Function Function Function Function Function _Function

* 除了BCC,它们都使用了相同的命名约定

真是混乱!(特别是MSVC里DEF文件和__declspec(dllexport) 属性对命名修饰的影响)! 尽管后缀能清楚地表示出调用函数时有多少字节需要从堆栈中弹出,不过这并不是常见用法。比如提供Win32 API的系统DLL就没有这些修饰。本文剩余部分将专注于MSVC和MinGW的DLL建立和使用,包括相关工具的介绍和使用。(在http://www.bcbdev.com/articles.htm 里有一篇关于在C++Builder中使用DLL的文章,写得不错,所以在这里就不再费话了)。

DEF文件的相关工具

首先,要介绍一下DEF文件格式以及MSVC和MinGW的相关工具。DLL使用中的不少难点就在这里。

DEF文件格式

我们只要关心DEF文件里的两个段:LIBRARY段和EXPORTS段。LIBRARY段指出DLL的内部名,EXPORTS段指出导出的函数或数据。这里有一个小例子:

LIBRARY    testdll.dll
EXPORTS
    cdeclFunction                       @1
    _stdcallFunction@8                  @2
    aliasName = cdeclFunction           @3

这个DEF文件定义了testdll.dll 的三个输出:第一个是__cdecl 函数,第二个是__stdcall 函数,第三个是第一个函数的别名。这三个函数也被赋于了各自的序号,这样函数可以通过名字或序号被调用。

CL

CL 可以接受一个DEF文件,它只是简单地把这个文件名传给LINK 。如:

cl /LD testdll.obj testdll.def

相当于调用:

link /out:testdll.dll /dll /implib:testdll.lib /def:testdll.def testdll.obj

LINK

LINK 是MSVC处理DLL和DEF文件最重要的工具。上面提及的CL 命令行显示了使用DEF文件建立DLL所使用的选项,主要问题是:如果我们建立DLL时不用DEF文件,那么导出的__stdcall 函数名会是_Function @n 的形式,而使用DEF文件,导出的函数名既可以是Function 也可以是_Function @n 。如果想同时导出两种命名形式(类似于GNU lddllwrap 的--add-stdcall-alias 选项),我们可以在EXPORTS段里加入下面形式的函数声明(注意顺序不可颠倒):

TestFunction = _TestFunction@4

还有一件事要注意,当我们用LINK 以上面的形式在DLL中导出TestFunction (或其它别名)和_TestFunction@4 时,只有_TestFunction@4 (内部名)会输出到导入库(.lib)中。

LIB

如果我们从其它人那里拿到一个DLL文件(没有源码)并且有对应的DEF文件,那么建立导入库最简单的方法是使用LIB 工具。通常使用下面的语法就已经足够(想了解更多的话请参阅MSDN ):

lib /def:DEF_file

注意 :

  1. 貌似LIB 不接受别名形式(它会简单地忽略等号后面的部分);
  2. 它假设所有在DEF文件中的函数是__cdecl ,所以DLL中的每个元素会被映射成含有下划线前缀的内部名字。例如,链接器使用导入库时会试图把DLL中未定义的_Function 分解为Function, 所以一般不用特别在意。

gcc

这里我们通过gcc 来调用ld ,不直接使用ld 的原因只是因为使用gcc 更方便。-shared 选项是专门用于生成DLL的。我们也可以使用-Wl 选项来传递其它链接专用选项。

ld

GNU ld 有不少与DLL有关的选项,不过我们只要关注下面这四个:

--add-stdcall-alias                导出的元素中是否包含@nn
--kill-at                          去除导出元素中的@nn
--out-implib <file>                生成导入库
--output-def <file>                为生成的DLL产生对应的.DEF文件

gcc 或者ld 可以在命令行直接接受一个DEF文件。当在EXPORTS段里有下面内容时,

TestFunction = TestFunction@4

两种形式都会被导出。这与稍后讲到的dllwrap 有些不同。

dllwrap

GNU dllwrap 可以按DEF文件生成一个DLL。一般用下面的语句来使用dllwrap

dllwrap --def DEF_file

 -o DLL_file

 OBJ_files

 [--output-lib LIB_file

]

dllwrap 将调用gcclddlltool 来履行这个任务,如果要求dllwrap 生成导入库(--output-lib ),它会让dlltool 来帮忙。和LINKld 不同,当dllwrap 在EXPORTS段遇到下面行时

TestFunction = TestFunction@4

它只导出TestFunction 而没有TestFunction@4 ,并且只有TestFunction 被输出到导入库中(从某种角度讲有点类似于LIB )。

dlltool

GNU dlltool 用于需要使用动态链接库(DLLs)时。下面的选项是值得我们关注的:

-l --output-lib <outname> 生成导入库
-D --dllname <name>       指定用于生成导入库的DLL文件名
-d --input-def <deffile>  读取DEF文件
-U --add-underscore       为导入库里的元素加上下划线前缀
-k --kill-at              去除导出的名称里的@<n>

dlltoolLIB 类似,不过有它自己的特色:-U 选项把DEF文件中的元素映射到DLL中有下划线前缀的名称上,-k 选项把DEF文件中的元素映射到DLL中有@n 后缀的名称上。

pexports

这是一个独立的开源工具,用于从DLL中生成DEF文件。它不和MSVC或MinGW一起发布,你可以从这里 下载(译者注:我的MinGW上怎么包含这个工具呀?)。

DLL和导入库

学习了上面的一堆工具,我们现在可以做我们想做的事了。这里我们还需要sed 工具(如果你没有,从网上下载一个,译者:MSYS里就有)和一些正则表达式的知识。

Microsoft Visual C++

生成DLL最简单的方法是使用CL 的/LD命令行参数:

cl /LD OBJ_files

生成的DLL将会导出像_MyFunction@8 这样的函数名,与前面“MSVC DLL (no DEF)”列显示的一样。要去除多余的命名修饰,我们必须用DEF文件。下面的命令能从以__declspec(dllexport) 建立的DLL中自动生成一个DEF文件:

link /out:DLL_file

 /dll OBJ_files
pexports DLL_file | sed "s/^_/([[:alnum:]_]/+/)@[[:digit:]]/+//1/" > DEF_file

上面的sed命令去除导出函数名里的下划线前缀和@n后缀,有了这个DEF文件和目标文件(.obj)以后,用下面的命令重新生成DLL和导入库:

link /out:DLL_file

 /dll /def:DEF_file

 /implib:LIB_file

 OBJ_files

现在你可以随意使用DLL和导入库了。

MinGW GCC

如果我们不用导出除__declspec(dllexport) 以外的函数,可以这样做:

gcc -shared -o DLL_file

 OBJ_files

 -Wl,--output-def,DEF_file
gcc -shared -o DLL_file OBJ_files -Wl,--kill-at
dlltool -d DEF_file --dllname DLL_file --output-lib LIB_file --kill-at

如果想使用DEF文件控制导出哪些函数,首先(假设__declspec(dllexport) 声明还在):

gcc -shared -o DLL_file

 OBJ_files

 -Wl,--kill-at,--output-def,DEF_file

产生一个DEF文件,内容类似于"Function = Function @n @Ordinal "。按我们的意愿编辑后,按下面的命令搞定剩下的工作:

dllwrap --def DEF_file

 -o DLL_file

 OBJ_files
sed "s/[[:alnum:]_]/+ *= *//" DEF_file > New_DEF_file
dlltool -d New_DEF_file --dllname DLL_file --output-lib LIB_file --kill-at

现在导入库已经在你手上了。


2002-8-20, written by Wu Yongwei
2003-3-6, last revised by Wu Yongwei
2009-9-18, 中文翻译 by 毛毛

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值