Windows中的库编程(三、函数调用约定 Calling Convention)

平常我们在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调用约定下,函数的多个参数由调用者从右到左的顺序压入堆栈,被调函数获得参数的序列是从左到右的,清理堆栈的工作由被调用函数负责。

常用宏WINAPICALLBACK来表示 __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);

 

 

未指定函数调用约定或函数调用约定错误的情况

如果函数调用约定错误,会出现如下这种:

Windows中的库编程(三、函数调用约定 Calling Convention)

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);

 

Windows中的库编程(三、函数调用约定 Calling Convention)

上一篇:使用WPF重写TextBox模板的时候为什么要给里面的ScrollViewer控件添加名称"PART_ContentHost"


下一篇:公众平台商户接入(微信支付)功能申请教程