so true

心怀未来,开创未来!
随笔 - 160, 文章 - 0, 评论 - 40, 引用 - 0
数据加载中……

windows下的调用规范

先把几篇不错的文章贴在这里:
一个程序(进程,应用程序实例)运行过程占用内存包含:代码区、全局区/静态区、常量区、堆栈、堆。其中前四种的内存是在系统为进程分配的进程地址 空间中,而堆是系统的内存,用户可以通过malloc\new等进行申请使用权。堆栈简称:栈,其实堆栈是用在函数上,所以也叫“函数堆栈”,当一个函数 被调用时,进程内核对象为其在进程的地址空间的堆栈部分进行分配一定的栈内存给该函数使用,函数堆栈用于:

(1)在进入函数之前,保存“返回地址”和“环境变量”;返回地址是指该函数结束后,刚从哪里继续执行下去。

(2)在进入函数之后,保存实参或实参拷贝、局部变量。 

下图是一个函数堆栈例子:

点击查看原始尺寸

函数原型:[连接规范] 函数类型 [调用约定] 函数名 参数列表 {......}

调用约定: 调用约定是决定函数实参或实参拷贝进入和退出函数堆栈的方式以及函数堆栈释放的方式,简单讲就是:实参或实参拷贝入栈、出栈、函数堆栈释放的方式。在Win32下有四种:

(1)__cdecl : 这个是c/c++默认的调用约定,实参是以参数列表从右依次向左入栈,出栈相反,函数堆栈是由调用方帮忙释放。主要用在那些带有可变参数的函数上。

(2)__stdcall : 这个是WIN API的调用约定,其实COM接口等只要是申明定义接口都要显示指定其调用约定为__stdcall。实参是以参数列表从右依次向左入栈,出栈相反,函数 堆栈是由被调用方自己释放。但是若函数含有可变参数那么即使显示指定了__stdcall,编译器也会自动把其改变成__cdecl。

(3)_thiscall : 这个是类的非静态成员函数默认的调用约定,其不能用在含有可变参数的函数上,否则编译会出错,实参是以参数列表从右依次向左入栈,出栈相反,函数堆栈是由 被调用方自己释放。但是注意,类非静态成员函数内部都隐含有一个this指针,该指针不是存放在函数堆栈上,而是直接存放在CPU寄存器上。

(4)__fastcall : 快速调用,这样的函数,它们的实参并不是存放在函数堆栈上,而是直接存放在CPU寄存器上,所以不存在入栈、出栈、函数堆栈释放。


_stdcall 与 _cdecl 的区别
几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,首先,需要了解两者之间的区别: WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如 fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用 _cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。

Microsoft的几种函数调用约定(calling convention)总结

  在DLL的调用中,要特别注意调用约定的不同,在windows中,有__cdecl,__stdcall,__fastcall,thiscall,naked等几种调用约定。
#include <stdio.h>
int fun();
int main(int argc, char *argv[])
{
 
 printf("this is a local variable:%d",fun(2,3,1));
 return 0;
}
int fun(int a,int b,int c)
{
 return a+b+c;
}
1:__cdecl
   __cdecl调用约定是C/C++的默认调用约定,因为是由调用者清理堆栈,所以可以实现变长参数。并且因为要求每个函数
调用都包含清理堆栈的代码,所以其生成的可执行文件要比用__stdcall生成的尺寸大,下面是这种调用约定的特征。
(1):参数传递由右向左。
(2):呼叫者清理堆栈。
(3):C编译时函数符号解析是在函数名前加一个_。
(4):/Gd选项强迫整个文件按照该调用约定进行编译。

下面是windows下的反汇编代码:
;在主函数中将参数3,2入栈,然后调用被解析后的函数名称_fun,
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push 1
 push 3
 push 2
 call _fun
;将esp加12,进行堆栈清理
 add esp, 12
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push ebp
 mov ebp, esp
 mov eax, DWORD PTR _a$[ebp]
 add eax, DWORD PTR _b$[ebp]
 pop ebp
 ret 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2:__stdcall
__stdcall是用来呼叫Win32 API函数的一种调用约定,由被调用者清理堆栈,所以不能将拥有变长参数的函数声明为此种
调用约定。
(1):参数传递由右向左
(2):被调用函数清理堆栈
(3):C编译时函数符号解析是在函数名前加_,函数名称后面加@,其后加十进制表示的参数所占的字节,例如有:
     int func(int a,int b,int c),解析后为: _func@12
(4): /Gz选项强迫整个文件按照该调用约定进行编译。
(5): 函数指针声明方式:typedef BOOL (__stdcall *funcname_ptr)(int a,int b);
下面是windows下的反汇编代码:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push 1
 push 3
 push 2
 call _fun@12
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push ebp
 mov ebp, esp
 mov eax, DWORD PTR _a$[ebp]
 add eax, DWORD PTR _b$[ebp]
 add eax, DWORD PTR _c$[ebp]
 pop ebp
 ret 12
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3:__fastdcall
  __fastcall指定函数参数通过寄存器来传递(只是约定,并不是一种保证)
(1):前两个DWORD或者字节数小于DWORD的参数通过ECX和EDX寄存器传递,其余参数从右向左进行传递
(2):被调用者清理堆栈。
(3):C编译时函数符号解析是在函数名前后都加@,在末尾加十进制表示的参数所占的字节,例如:
     int func(int a,int b,int c),解析后为: _@func@12
(4): /Gr选项强迫整个文件按照该调用约定进行编译(main函数除外)。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push 1
 mov edx, 3
 mov ecx, 2
 call @fun@12
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push ebp
 mov ebp, esp
 sub esp, 8
 mov DWORD PTR _b$[ebp], edx
 mov DWORD PTR _a$[ebp], ecx
 mov eax, DWORD PTR _a$[ebp]
 add eax, DWORD PTR _b$[ebp]
 add eax, DWORD PTR _c$[ebp]
 mov esp, ebp
 pop ebp
 ret 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4:thiscall:
  thiscall是调用C++类成员函数的默认调用约定
(1):this指针被放置在ECX中
(2):被呼叫者清理函数堆栈
(3):因为thiscall不是关键字,所以不能被显示应用于代码中
#include<iostream>
using namespace std;
class temp
{
 public:
  void print(int,int);
};
void temp::print(int a,int b)
{
 printf("the value is %d",a+b);
}
int main()
{
 temp a;
 a.print(1,2);
 return 0;
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push ebp
 mov ebp, esp
 push ecx     ;ecx中存放this指针的值
 mov DWORD PTR _this$[ebp], ecx
 mov eax, DWORD PTR _a$[ebp]
 add eax, DWORD PTR _b$[ebp]
 push eax
 push OFFSET FLAT:$SG8428
 call _printf
 add esp, 8
 mov esp, ebp
 pop ebp
 ret 8
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push 2
 push 1
 lea ecx, DWORD PTR _a$[ebp]
 call ?print@temp@@QAEXHH@Z   ; temp::print
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5:naked
   这种调用约定仅仅被用在VxD驱动程序中。
(1):参数传递方式为从右向左
(2):呼叫者清理被调用函数堆栈
#include<stdio.h>
int NakedCallFunction(int ,int);
int main()
{
 NakedCallFunction(2,3);
 return 0;
}
__declspec(naked) int NakedCallFunction(int a,int b)
{
 __asm
 {
  ret
 }
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 push ebp
 mov ebp, esp
 push 3
 push 2
 call ?NakedCallFunction@@YAHHH@Z  ; NakedCallFunction
 add esp, 8
 xor eax, eax
 pop ebp
 ret 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Function compile flags: /Odt
_a$ = 8       ; size = 4
_b$ = 12      ; size = 4
?NakedCallFunction@@YAHHH@Z PROC NEAR   ; NakedCallFunction
 ret 0
?NakedCallFunction@@YAHHH@Z ENDP   ; NakedCallFunction
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6:还有几种调用约定,例如:__pascal, __fortran, __syscall只是在相关开发工具中使用,并且不被Microsoft
所支持。
注:以上函数符号名解析都以C为标准,C++的解析规则非常复杂,这里有一个简要的描述:
http://www.microsoft.com/china/community/program/originalarticles/techdoc/dll.mspx



posted on 2010-04-10 12:17 so true 阅读(296) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: