全部博文(584)
分类: C/C++
2011-04-10 23:22:25
__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)内部表示为_sin,double __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 | |||
__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 ld 和 dllwrap 的--add-stdcall-alias 选项),我们可以在EXPORTS段里加入下面形式的函数声明(注意顺序不可颠倒):
TestFunction = _TestFunction@4
还有一件事要注意,当我们用LINK以上面的形式在DLL中导出TestFunction(或其它别名)和_TestFunction@4时,只有_TestFunction@4(内部名)会输出到导入库(.lib)中。
LIB
如果我们从其它人那里拿到一个DLL文件(没有源码)并且有对应的DEF文件,那么建立导入库最简单的方法是使用LIB工具。通常使用下面的语法就已经足够(想了解更多的话请参阅):
lib /def:DEF_file
注意:
gcc
这里我们通过gcc来调用ld,不直接使用ld的原因只是因为使用gcc更方便。-shared选项是专门用于生成DLL的。我们也可以使用-Wl选项来传递其它链接专用选项。
ld
GNU ld有不少与DLL有关的选项,不过我们只要关注下面这四个:
--add-stdcall-alias 导出的元素中是否包含@nn--kill-at 去除导出元素中的@nn--out-implib
生成导入库--output-def 为生成的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将调用gcc、ld和dlltool来履行这个任务,如果要求dllwrap生成导入库(--output-lib),它会让dlltool来帮忙。和LINK或ld不同,当dllwrap在EXPORTS段遇到下面行时
TestFunction = TestFunction@4
它只导出TestFunction而没有TestFunction@4,并且只有TestFunction被输出到导入库中(从某种角度讲有点类似于LIB)。
dlltool
GNU dlltool用于需要使用动态链接库(DLLs)时。下面的选项是值得我们关注的:
-l --output-lib
生成导入库-D --dllname 指定用于生成导入库的DLL文件名-d --input-def 读取DEF文件-U --add-underscore 为导入库里的元素加上下划线前缀-k --kill-at 去除导出的名称里的@
dlltool和LIB类似,不过有它自己的特色:-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-atdlltool -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
现在导入库已经在你手上了。