参考博客:http://blog.csdn.net/qinmi/article/details/1744951
(1)_stdcall调用 _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。大多数WIN32Api都采用了_stdcall调用方式。如果按C编译方式(除非另外重新指定编译方式,否则编译器会根据源文件的扩展名选择编译方式,如果文件的扩展名是“.C”,编译器会采用C的语法编译,如果扩展名是“.cpp”,编译器会使用C++的语法编译程序),_stdcall调用约定会在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@bytes。例如有如下整型相加函数add,头文件add.h中声明如下:
#define DLL_EXPORTS //vs2010新建dll时,如果勾选“导出符号”选项,则会自动填充下面的宏定义代码,并且会自动define DLL_EXPORTS,以使得宏定义执行#define DLL_API __declspec(dllexp//ort),但勾选“导出符号”默认生成的是.cpp文件,此处我们需要用.c文件来生成dll,因此新建dll时不需勾选“导出符号”,但为了dll文件中的函数被声明为导出类型,///因此这里需加入#define DLL_EXPORTS,当将dll和.h文件给其他人调用时,将#define DLL_EXPORTS注释掉,以使得宏定义执行#define DLL_API __declspec(dllimport) #ifdef DLL_EXPORTS #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif DLL_API int _stdcall add(int,int); //声明_stdcall调用约定
对应的add.c文件中有:
#include "add.h" DLL_API int _stdcall add(int a ,int b) { return a+b; }
下面是指定_stdcall调用,按c方式编译时函数名的修饰(用Dependency查看):
如果按c++编译方式,则函数名以“?”标识函数名开始,后跟函数名,函数名后以“@@YG”标识参数表的开始,后跟参数表,参数表以代号表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。仍以整型相加为例,add.h中声明如下:
//不需手动定义DLL_EXPORTS,在新建dll时编译器已自动定义了DLL_EXPORTS #ifdef DLL_EXPORTS #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif DLL_API int _stdcall add(int,int);
add.cpp文件为:
#include "add.h" DLL_API int _stdcall add(int a ,int b) { return a+b; }
下面是指定_stdcall调用,按c++方式编译时函数名的修饰
(2)_cdecl调用 _cdecl是C/C++程序的缺省调用方式,也就是说所有非类成员函数和没有用__stdcall或__fastcall声明的c/c++函数都默认是__cdecl方式,其参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。按C编译方式,_cdecl调用约定保持函数原名(有些文章中说是在输出函数名前面加下划线,形如_functionname,但我在vs2010上试验的结果为保持原名)。
仍然使用上面的加法例子,指定_cdecl调用,按c方式编译函数名如下:
指定_cdecl调用,按c++编译方式函数名如下,规则同上面的_stdcall调用约定,只是参数表的开始标识由“@@YG”变为“@@YA”。 :
(3)_fastcall调用 _fastcall调用较快,它通过CPU内部寄存器传递参数。(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number,按c++方式编译,规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”
指定_fastcall调用,按c方式编译时函数名的修饰:
指定_fastcall调用,按c++方式编译时函数名的修饰:
以下参考博客:http://blog.csdn.net/huangyong19870618/article/details/5494022
在调用dll中的函数时,经常会出现这样那样的问题,下面是两种常见的错误类型。
函数调用约定不匹配引起的问题
函数调用时如果出现堆栈异常,十有八九是由于函数调用约定不匹配引起的。比如动态链接库a有以下导出函数:int add(int ,int);
动态库生成时函数add的调用约定是_stdcall,也就是函数调用时参数从右向左入栈,函数返回时自己还原堆栈。现在某个程序模块b要引用dll中的add函数,b和a一样使用C++方式编译,只是b模块的函数调用方式是__cdecl,因此,所以add在b模块中被其它调用它的函数认为是__cdecl调用方式,b模块中的这些调用函数在调用完add函数后当然要帮着恢复堆栈啦,可是add函数已经在结束时自己恢复了堆栈,b模块中的函数这样多此一举就引起了栈指针错误,从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因为参数传递顺序是一样的),add也完成了自己的功能,只是函数返回后引发错误。解决的方法也很简单,只要保证两个模块的在编译时设置相同的函数调用约定就行了。
还是用上面的add函数测试一下,生成dll时采用_stdcall调用约定,按c++方式编译,新建一个调用程序如下:
#include <iostream> #include "DLL.h" using std::cout; int main() { int a = 3,b= 2; int ans =add(a,b); cout<<ans; system("pause"); return 0; }
结果在vs2010中程序运行竟然通过了,能正确输出5,没有发生堆栈异常,是不是因为我直接调用add函数所以对函数的调用方式还是_stdcall呢,要说明调用方式是__cdecl难道要用什么语法指出来?此处不太明白。
名字修饰规则不匹配引起的问题
当函数的调用约定匹配,但按不同方式编译(用c还是c++)时就很可能出现dll中的函数找不到的情况了,即经常出现的LNK 2001错误。还以加法为例,这一次在生成a.dll和调用函数b中都采用__stdcall调用约定,但是a.dll文件中使用C语言的语法编译,所以a.dll的载入库中add函数的名字修饰就是“_add@8”。调用函数b中采用的是C++语言编译,所以add在调用模块b中被按照C++的名字修饰规则命名为“?add@@YGHHH@Z”,编译过程相安无事,链接程序时c++的链接器就到a.lib中去找“?add@@YGHHH@Z”,但是a.lib中只有“_add@8”,没有“?add@@YGHHH@Z”,于是链接器就报告:
解决的方法很简单,就是要让b模块知道这个函数是C语言编译的,extern"C"可以做到这一点。一个采用C语言编译的库应该考虑到使用这个库的程序可能是C++程序(使用C++编译器),所以在设计头文件时应该注意这一点。通常应该这样声明头文件:
#ifdef _cplusplus extern"C" { #endif int add(int ,int); #ifdef _cplusplus } #endif这样C++的编译器就知道add的修饰名是“_add@8”,就不会有链接错误了。