在VC SDK的WinDef.h中,宏WINAPI被定义为__stdcall,这是C语言中一种调用约定,常用的还有__cdecl和__fastcall。这些调用约定会对我们的代码产生什么样的影响?让我们逐个分析。

首先,在x86平台上,用VC编译这样一段代码:

 int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5)
{
int n = n0 + n1 + n2 + n3 + n4 + n5;
return n;
} int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5)
{
int n = n0 + n1 + n2 + n3 + n4 + n5;
return n;
} int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5)
{
int n = n0 + n1 + n2 + n3 + n4 + n5;
return n;
} int _tmain(int argc, _TCHAR* argv[])
{
TestC(, , , , , );
TestStd(, , , , , );
TestFast(, , , , , );
return ;
}

然后在main函数的开始出设置断点、开始调试。

首先,我们会看到编译器为__cdecl产生的汇编代码:

;main函数中的调用代码
TestC(, , , , , );
013F243E push
013F2440 push
013F2442 push
013F2444 push
013F2446 push
013F2448 push
013F244A call TestC (13F11D1h)
013F244F add esp,18h ;TestC函数的实现,省略无关代码
int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5)
{
013F1400 push ebp
013F1401 mov ebp,esp
013F1403 ...
...
013F1439 mov esp,ebp
013F143B pop ebp
013F143C ret

由以上代码可以发现,main函数中调用TestC函数时,将6个参数由右至左依次压栈,也就是全部参数都通过栈传递。在TestC函数ret时,并没有清理栈上的参数,而是在main函数中通过调整esp来清理的。正因为如此,使得__cdecl可以支持参数个数不定的函数调用,如 :

void f(char* fmt, ...);

再来看一下__stdcall的汇编代码:

;main函数中的调用代码
TestStd(, , , , , );
00FB2452 push
00FB2454 push
00FB2456 push
00FB2458 push
00FB245A push
00FB245C push
00FB245E call TestStd (0FB11E0h) ;TestStd函数的实现,省略无关代码
int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5)
{
00FB1840 push ebp
00FB1841 mov ebp,esp
00FB1843 ...
... 00FB1879 mov esp,ebp
00FB187B pop ebp
00FB187C ret 18h

以上代码中,main函数中调用TestStd函数时,将6个参数由右至左依次压栈,这一点与__cdecl相同。不同的是在TestStd函数ret时,清理掉了栈上的6个参数(18h = 4 * 6)。

最后看一下__fastcall产生的代码:

;main函数中的调用代码
TestFast(, , , , , );
00FB2463 push
00FB2465 push
00FB2467 push
00FB2469 push
00FB246B mov edx,
00FB2470 xor ecx,ecx
00FB2472 call TestFast (00FB11E5)

;TestFast函数的实现,省略无关代码
int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5)
{
00FB1880 push ebp
00FB1881 mov ebp,esp
00FB1883 ...
... 00FB18C1 mov esp,ebp
00FB18C3 pop ebp
00FB18C4 ret 10h

与以上两个调用约定显著不同的是,__fastcall使用ecx和edx来传递前两个参数(如果有的话),剩余的参数依然按照从右到左的顺序压栈传递。并且在函数ret时,类似于__stdcall,会清理通过栈传递的参数(此处为4个,10h = 4 * 4)。

接下来看一下x64平台上产生的代码:

;main函数中的调用代码
000000013F3111A0 ...
... 000000013F3111AA sub rsp,30h
000000013F3111AE ...
... TestC(, , , , , );
000000013F3111C1 mov dword ptr [rsp+28h],
000000013F3111C9 mov dword ptr [rsp+20h],
000000013F3111D1 mov r9d,
000000013F3111D7 mov r8d,
000000013F3111DD mov edx,
000000013F3111E2 xor ecx,ecx
000000013F3111E4 call TestC (13F31100Ah)
TestStd(, , , , , );
000000013F3111E9 mov dword ptr [rsp+28h],
000000013F3111F1 mov dword ptr [rsp+20h],
000000013F3111F9 mov r9d,
000000013F3111FF mov r8d,
000000013F311205 mov edx,
000000013F31120A xor ecx,ecx
000000013F31120C call TestStd (13F311019h)
TestFast(, , , , , );
000000013F311211 mov dword ptr [rsp+28h],
000000013F311219 mov dword ptr [rsp+20h],
000000013F311221 mov r9d,
000000013F311227 mov r8d,
000000013F31122D mov edx,
000000013F311232 xor ecx,ecx
000000013F311234 call TestFast (13F31101Eh)
000000013F311239  ...
... 000000013F31123B  add         rsp,30h
000000013F31123F  ...
...
;TestC函数的实现,省略无关代码
int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5)
{
000000013F311080 mov dword ptr [rsp+20h],r9d
000000013F311085 mov dword ptr [rsp+18h],r8d
000000013F31108A mov dword ptr [rsp+10h],edx
000000013F31108E mov dword ptr [rsp+],ecx
000000013F311092 ...
... 000000013F3110D1 ret

;TestStd函数的实现,省略无关代码
int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5)
{
000000013F3110E0 mov dword ptr [rsp+20h],r9d
000000013F3110E5 mov dword ptr [rsp+18h],r8d
000000013F3110EA mov dword ptr [rsp+10h],edx
000000013F3110EE mov dword ptr [rsp+],ecx
000000013F3110F2 ...
... 000000013F311131 ret

;TestFast函数的实现,省略无关代码
int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5)
{
000000013F311140 mov dword ptr [rsp+20h],r9d
000000013F311145 mov dword ptr [rsp+18h],r8d
000000013F31114A mov dword ptr [rsp+10h],edx
000000013F31114E mov dword ptr [rsp+],ecx
000000013F311152 ...
... 000000013F311191 ret

可以看到,编译器忽略了3个不同的调用约定keyword,而为它们产生了同样的代码:调用者使用rcx/ecx、rdx/edx、r8/r8d、r9/r9d来传递前4个参数,剩余的参数通过栈传递,这有些类似于x86下的__fastcall,不同的是,栈上保留了前4个参数的存储空间。而且类似于x86下的__cdecl,函数ret时不会清理栈,栈的平衡由调用者负责。

在Debug版的代码中,TestXXX函数的开始处,首先将rcx/ecx、rdx/edx、r8/r8d、r9/r9d中的值拷贝到栈上预留的空间里,应该是为了方便调试。在Release版中,这些预留空间有时被用来备份某个通用寄存器的值。

x64下的这种调用约定,像是__fastcall和__cdecl的一个结合,既提高了性能又能支持不定个数的参数。

调用约定是代码函数化、模块化的基础,其实就是一种参数传递、栈平衡的策略。我们在代码中使用一个函数时,只需要提供函数声明,编译器就可以依照约定产生出调用这个函数的机器码,而在被调用的函数中,也是按照约定知道参数如何传递过来及如何使用。

宏WINAPI和几种调用约定的更多相关文章

  1. 64位只有一种调用约定stdcall

    procedure TForm2.Button1Click(Sender: TObject); function EnumWindowsProc(Ahwnd: hwnd; AlParam: lPara ...

  2. x86 x64下调用约定浅析

    x86平台下调用约定 我们都知道x86平台下常用的有三种调用约定,__cdecl.__stdcall.__fastcall.我们分别对这三种调用约定进行分析. __cdecl __cdecl是C/C+ ...

  3. C/C++/动态链接库DLL中函数的调用约定与名称修饰

    参见:http://blog.twofei.com/cc/impl/calling-convension.html 调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用 ...

  4. X86调用约定 calling convention

    http://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A 这里描述了在x86芯片架构上的调用约定(calling con ...

  5. 调用约定__stdcall / __cdecl

    __cdecl与__stdcall这两种调用约定之间的主要差别在于由谁来执行对参数的清理工作. 如果是__cdecl,那么主调函数将负责执行清理工作,如果是__stdcall那被调函数将负责执行清理. ...

  6. DLL中调用约定和名称修饰(一)

    DLL中调用约定和名称修饰(一) 调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议.这种协议规定了该语言的函数中的参数传送方式.参数是否可变和由谁来 ...

  7. 【原创+整理】简述何为调用约定,函数导出名以及extern C

    何为调用约定 调用约定指的是函数在调用时会按照不同规则,翻译成不同的汇编代码.这和参数的压栈顺序和栈的清理方式相关,也就是说不同的调用约定,这些方式会做相应改变.一般编译器是以默认的调用约定编译一份代 ...

  8. DLL导出与调用约定

    一般来说,从DLL导出函数有两种方法.一种是使用.def文件:另一种是使用__declspec(dllexport). 使用上面两种方法各有优缺点.使用.def文件就是需要额外维护,当导出函数更改名字 ...

  9. 关于调用约定(cdecl、fastcall、、thiscall) 的一点知识(用汇编来解释)good

    函数调用规范   当高级语言函数被编译成机器码时,有一个问题就必须解决:因为CPU没有办法知道一个函数调用需要多少个.什么样的参数.即计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者 ...

随机推荐

  1. OpenCV 学习笔记(模板匹配)

    OpenCV 学习笔记(模板匹配) 模板匹配是在一幅图像中寻找一个特定目标的方法之一.这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否"相似",当相似度足够 ...

  2. Kotlin——最详细的控制语句使用

    在前面 的章节中讲解了Kotlin语言中的数据类型.变量与常量的定义.不了解请参见前面的内容: Kotlin从无到有系列之数据类型介绍. Kotlin从无到有系列之变量.常量.注释的使用. 下面详细为 ...

  3. 【转】嵌入式C语言调试开关

    在调试程序时,经常会用到assert和printf之类的函数,我最近做的这个工程里就有几百个assert,在你自认为程序已经没有bug的时候,就要除去这些调试代码,应为系统在正常运行时这些用于调试的信 ...

  4. ios 返回不会自动刷新页面问题

    在实际开发过程中,移动端的兼容性问题有很大的坑,安卓可以了ios不行,ios可以了安卓又失效了这样,其中ios的回退操作就是不会自动刷新页面,很烦! 常见的history.back() history ...

  5. Ajax禁止重复提交

    var pendingRequests = []; var generatePendingRequestKey = function (obj) { return obj.data || {}; } ...

  6. viewpager的滑动

    在一个已经是月黑风高快下班的时刻了,我们产品突然通知我们开会,要添加一个功能,他闲来无聊随便戳了戳facebook,说点开联系人的那个横向滑动的卡片式的效果不错,让我们在我们的app里添加这个效果,我 ...

  7. Fastify 系列教程四 (求对象、响应对象和插件)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) Fastify ...

  8. .4-Vue源码之数据劫持(2)

    开播了开播了! vue通过数据劫持来达到监听和操作DOM更新,上一节简述了数组变化是如何监听的,这一节先讲讲对象属性是如何劫持的. // Line-855 Observer.prototype.wal ...

  9. 前端开发:H5直播起航

    前言 前不久抽空对目前比较火的视频直播,做了下研究与探索,了解其整体实现流程,以及探讨移动端HTML5直播可行性方案. 发现目前 WEB 上主流的视频直播方案有 HLS 和 RTMP,移动 WEB 端 ...

  10. windows 系统下C++实现的多线程

    摘抄http://blog.csdn.net/huyiyang2010/article/details/5809919 Thread.h #ifndef __THREAD_H__ #define __ ...