Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1684256
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: 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

注意:

  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                 生成导入库--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

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

阅读(1785) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~