平常我们在C#中使用DllImportAttribute引入函数时,不指明函数调用约定(CallingConvention)这个参数,也可以正常调用。如FindWindow函数
[DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
在DllImportAttribute类中, CallingConvention属性可以用于指明函数调用约定,默认值是Winapi(__stdcall),而Windows API函数默认的调用约定是Winapi(__stdcall),这也是为什么平常我们不指定CallingConvention值也能调用成功的原因。
函数调用约定
函数调用约定(Calling Convention)是指在函数调用时关于函数的多个参数入栈和出栈顺序的约定。
通俗地讲就是关于堆栈的一些说明,首先是函数参数压栈顺序,其次是压入堆栈的内容由谁来清除,调用者还是函数自己。如果函数调用约定不一致,在使用库中的导出函数时会调用异常。
下面介绍一下在Windows的库编程中的5种调用约定
__cdecl
__cdecl(_cdecl)调用约定又称为C调用约定,是C默认的调用约定,也是C++全局函数的默认调用约定,通常省略。在未指定调用约定时就默认为__cdecl,如下面两个写法是一样的
1 int sum(int num1,int num2); 2 int __cdecl sum(int num1,int num2);
在__cdecl调用约定下,函数的多个参数由调用者按从右到左的顺序压入堆栈,被调函数获得参数的序列是从左到右的,清理堆栈的工作由调用者负责,因为函数参数的个数是可变的。如果是被调函数清理堆栈,则参数个数必须确定,否则由于被调用函数事先无法知道参数的个数,事后的清除工作也将无法正常进行。
__stdcall
__stdcall(_stdcall)调用约定又称为Pascal调用约定,也是Pascal语言的调用约定,如下:
1 int __stdcall sum(int num1,int num2);
在__stdcall调用约定下,函数的多个参数由调用者从右到左的顺序压入堆栈,被调函数获得参数的序列是从左到右的,清理堆栈的工作由被调用函数负责。
常用宏WINAPI或CALLBACK来表示 __stdcall调用约定。
1 #define CALLBACK __stdcall 2 #define WINAPI __stdcall
Windows API函数大部分是__stdcall约定,如文章最开头提到的FindWindow函数。
1 HWND 2 WINAPI 3 FindWindowA( 4 _In_opt_ LPCSTR lpClassName, 5 _In_opt_ LPCSTR lpWindowName); 6 WINUSERAPI
__fastcall调用约定
__fastcall调用约定称为快速调用约定,前两个双字(DWORD)参数或更小尺寸的参数通过寄存器ECX和EDX来传递,剩下的参数按照自右向左的顺序压栈传递,清理堆栈的工作由被调用者函数来完成。
1 int __fastcall sum(int num1,int num2);
thiscall调用约定
thiscall调用约定是C++中的非静态类成员函数的默认调用约定。thiscall只能被编译器使用,没有相应的关键字。因此不能手动指定。采用thiscall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈。ECX寄存器传送一个额外的参数,this指针
naked call调用约定
naked call调用约定也称为裸调,是一个不大用的调用约定,不建议使用。编译器不会给这样的函数增加初台化和清理的代码。naked call不是类型修饰符,它必须和__declspec共同使用。
1 __declspec(naked) int sum(int num1,int num2);
未指定函数调用约定或函数调用约定错误的情况
如果函数调用约定错误,会出现如下这种:
Run-Time Check Failure #0- The value of ESP was not properly saved across a function call. Thie is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
我最近就遇到了这个问题,在C++动态去调用一个库中的函数,运行时就报这个错,后面给函数指针加上了__stdcall就调用成功了,类似下面这样
1 typedef void (__stdcall *FUNC)(int num1, int num2);