论调用约定

在第一篇技术博客"动态链接库简介"中说到了调用约定对函数名改编的影响,当时没有详细说明,这里跟大家一起讨论一下调用约定


常见的调用约定

  • stdcall 

  • cdecl 

  • fastcall 

  • thiscall 

  • naked call

 

VC中的定义

#defineCALLBACK __stdcall

#defineWINAPI __stdcall

#defineWINAPIV __cdecl

#defineAPIENTRY WINAPI

#defineAPIPRIVATE __stdcall

#definePASCAL __stdcall

#definecdecl _cdecl

#ifndefCDECL

#defineCDECL _cdecl

#endif

调用约定包含了3个方面的内容

  1. 函数参数的压栈顺序

  2. 由调用者还是被调用者把参数弹出栈

  3. 产生函数修饰名的方法(调用约定和编译方式共同决定)

 

_stdcall

  1. standard call,标准调用约定,就是WINAPI的调用约定,Pascal程序的缺省调用方式,通常用于windowsAPIDelphi调用约定,每一个WINDOWSAPI函数都是__stdcall类型的

windows API都是函数参数个数固定。所以由函数自身清理堆栈由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。

  1. 2.      函数采用从右到左的压栈方式

  2. 3.      被调函数(函数自己)在退出时清空堆栈

  3. 函数修饰名随调用约定和编译种类(CC++)的不同而变化
    C
    编译方式
    在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数
    格式为:_functionname@number



wKioL1QCsdfBudNIAAIQFLL181g764.jpg

wKiom1QCsMDB6T1uAACu5VB7jqE705.jpg

C++编译方式

__stdcall调用约定:

1、以“?”标识函数名的开始,后跟函数名;

2、函数名后面以@@YG标识参数表的开始,后跟参数表;

3、参数表以代号表示:

X--void

D--char

E--unsignedchar

F--short

H--int

I--unsignedint

J--long

K--unsignedlong

M--float

N--double

_N--bool

....

PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;

4参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;

5参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ

例如

int Test1(char*var1,unsigned long)-----?Test1@@YGHPADK@Z

void Test2()-----?Test2@@YGXXZ

示例:

wKioL1QCsdeBpsICAAHzIXAJJoQ074.jpg

导出到DLL,查看

wKioL1QCsdfCoNZ-AAD-bnwXcZk696.jpg

  1. __stdcall可以写成_stdcall

在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。

那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用函数自己事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。


_cdecl

  1. 1.      也叫做C调用约定(是C Declaration的缩写(declaration,声明))

      _cdecl:CC++程序的缺省调用约定(也是VC默认的方式)

      __cdecl:C/C++MFC程序默认使用的调用约定
         由于C/C++中函数可以是可变参数的,所以由调用函数清理堆栈

  1. 2.      函数参数按照从右到左的顺序入栈

  2. 3.      由调用函数把参数弹出栈以清理堆栈(实现可变参数的函数只能使用该调用约定)

  3. 4.      函数修饰名随调用约定和编译种类(CC++)的不同而变化


C
编译方式

输出函数名不变。

VS编译的时候,还是原函数名

wKiom1QCsMDwPgX7AAIGNZcnrIw684.jpg

wKioL1QCsdjjQ5FbAAC3VTD9u_A761.jpg

C++编译方式
__cdecl
调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”

wKiom1QCsMHShW3AAAHye2JF4hw093.jpg

导出函数到DLL文件,查看

wKioL1QCsdjA2JWpAADN4DZCdGA325.jpg

  1.     由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大

  2.    当遇到可变参数的时候,使用_cdecl

      程序中没有涉及可变参数,最好使用_stdcall关键字

_fastcall

用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECXEDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall

 

C编译方式

__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number

wKiom1QCsMGwVlrQAAHWOfSOvXs128.jpg

导出函数到DLL文件

wKiom1QCsMGSxV7OAADNgUWdhTY957.jpg

C++编译时

__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”

wKioL1QCsdnw3JBqAAHZunEZKBk722.jpg

导出函数到DLL文件

wKiom1QCsMGA55s6AADCu8A7jaE382.jpg

thiscall

thiscall仅仅应用于"C++"成员函数this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

naked call

采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESIEDIEBXEBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。


到这里,关于第一篇博客"动态链接库简介"里的全部内容都写完了,由于当时笔记整理的比较乱,各位博友如果发现有不足和错误的地方,希望能提出宝贵的意见和建议



你可能感兴趣的:(C++,dll,调用约定)