函数调用约定与名字修饰约定

在Windows下,由于很多语言支持动态链接库技术,因此动态链接库是一种很好的混合编程方法。语言对函数的约定有两种:函数调用约定和名字修饰约定。不同语言默认的调用调用约定和函数的命名方式是不同的,要想不同的语言开发的动态链接库能够相互调用,那么开发动态链接库的语言和调用链接库的语言的函数约定必须相同,同时在编译时函数的修饰名也必须一样。
1.函数调用约定
  调用约定决定了函数参数传送时入栈和出栈的顺序,以及堆栈平衡的方式,即是由调用函数还是被调用函数复制清除堆栈的参数,还原堆栈。常见的调用约定有__cdecl,__fastcall,__stdcall,__thiscall,__nakedcall。
1.1__cdecl
__cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法。_cdecl 约定所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。对参数可变的函数需要用__cdecl的方式进行声明,因为如果参数不固定的话,在被调用函数内就无法知道有多少个参数入栈,所以为了实现可变参数,必须要在被调函数执行之后才知道参数使用的字节数,所以要调用者来进行堆栈平衡操作。
1.2__fastcall
  从字面的意思看,__fastcall的特点就是快。_fastcall函数调用约定在可能的情况下使用寄存器传递参数,其余参数按照从右到左的顺序入栈,被调用函数在返回之前负责清除栈中的参数。不同编译器编译的程序规定的寄存器不同。VS编译器使用ECX和EDX寄存器传递。使用__fastcall方式无法用作跨编译器的接口。另外,在使用内嵌汇编的时候,还要注意不能和编译器使用的寄存器有冲突。
1.3__stdcall
  __stdcall 是StandardCall的缩写,绝大多数的Windows的API是__stdcall调用约定。__stdcall调用约定的参数入栈方式是从右向左入栈,由被调用函数负责清除栈中的参数。像wsprintf这样的API是采用__cdecl调用约定的。
1.4__thiscall
  __thiscall只用在C++成员函数的调用,函数参数按照从右向左的顺序入栈,类实例的this指针通过ECX寄存器传递。需要注意的是__thiscall不是C++的关键字,不能使用__thiscall声明函数,它只能由编译器使用。
1.5__nakedcall
  采用前面几种函数调用约定的函数,编译器会在必要的时候自动在函数开始添加保存ESI,EDI,EBX,EBP寄存器的代码,在退出函数时会添加代码恢复这些寄存器 的内容,使用__nakedcall方式声明的函数则不会添加这样的代码。__nakedcall不是类型修饰符,故必须和_declspec共同使用。
2.名字修饰约定
  名字修饰约定指编译器在编译阶段如何定义函数的修饰名,各种编译器在编译函数的时候,会根据函数原型生成包含诸如函数名称、参数和返回值等信息的标识字符串,这个字符串称为函数的修饰名。由于函数的修饰名仅在编译和链接阶段使用,所以该名称字符串存在于导入库.lib文件中,而在最终生成的Dll文件中,导出的函数名和源文件中定义的函数名是一致的。
  如果调用程序开发时和开发Dll使用的名字修饰约定不一致,那么在使用Dll对应的lib文件时,即使使用了正确的函数名,但是因为编译在lib文件中找的是修饰名而不是函数名,所以编译器仍然会报”找不到函数”的错误。
2.1 VC和Win32汇编编译器的函数名修饰约定
  在VC和Win32汇编编译器的函数名修饰约定如下:__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个”@”符号和其参数的字节数,格式为_functionname@number。__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。 __fastcall调用约定在输出函数名前加上一个”@”符号,后面也是一个”@”符号和其参数的字节数,格式为@functionname@number。
2.2 C++编译器函数名修饰约定
  C++编译器使用的函数名称修饰方式比较复杂,对于__stdcall方式的约定,编译器在函数名前面加”?”作为开始,然后后面跟”@@YG”表示参数表开始,参数表用以下代号表示:
代号 X D E F H I J K M N _N …
类型 void /char/unsigned char/short/int/unsigned int/long/ unsigned long /float/double/bool
PA表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以”0”代替,一个”0”代表一次重复。参数表的第一项为该函数的返回值类型,其后依次为参数化的数据类型,参数表后以”@Z”标识整个名字的结束,如果该参数无返回值,则以”Z”标识结束。
  对于__cdecl调用约定: 规则同上面的__stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。
  对于__fastcall调用约定: 规则同上面的__stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。
  VC++对函数的省缺声明是”__cedcl”,将只能被C/C++调用.
  C++使用这么复杂的名称修饰方式的好处是可以支持函数重载,但这样就造成了C++的名称修饰方式无法和其他语言兼容,造成用C++修饰方式命名的Dll函数无法被其他语言使用。不过使用C++提供的extern “C”关键字,添加在函数名和函数声明前,C++就会对该函数强制使用标准C的函数名称修饰方式,这样在DLL中用这种方式输出的函数就可以被其他语言使用了,同时其他语言编写的Dll函数在C++的头文件中声明时,前面也必须加extern “C”关键字,这样C++才会在lib文件中找到正确的函数修饰名。
  为了使头文件可以同时在C和C++中使用,可以利用C编译器的内部变量__cplusplus进行条件编译,当使用C++方式编译时,该变量被设置为TRUE,否则被设置为FALSE。这样在C++中使用时,函数声明前就会被自动加上extern “C”关键字。

#ifdef __cplusplus
extern "C" {
#endif
__stdcall function();
#ifdef __cplusplus
}
#endif

猜你喜欢

转载自blog.csdn.net/feike24/article/details/52425007