转自http://blog.sina.com.cn/qingsong80
__cdecl、__fastcall与 __stdcall,三者都是调用约定(Calling Convention),它决定以下内容:
1) 函数参数的压栈顺序
2) 由调用者还是被调用者把参数弹出栈
3) 以及产生函数修饰名的方法。
1
、
__stdcall
调
用
约
定:函数的参数自右向左通
过栈传递
,被
调
用的函数在返回前清理
传
送参数的内存
栈
。
2 、_ _ cdecl 是C和C++程序的缺省 调 用方式。 每 一个 调 用它的函数都包含清空堆 栈 的代 码 ,所以 产 生的可 执 行文件大小会比 调 用_stdcall函数的大。 函数采用从右到左的 压栈 方式。
注意: 对 于可 变 参数的成 员 函数,始 终 使用 __cdecl 的 转换 方式。
3 、 __fastcall 调 用 约 定:它是通 过 寄存器来 传 送参数的( 实际 上,它用 ECX 和 EDX 传 送前两个双字( DWORD )或更小的参数,剩下的参数仍旧自右向左 压栈传 送,被 调 用的函数在返回前清理 传 送参数的内存 栈 )。
4 、 thiscall 仅仅应 用于 "C++" 成 员 函数。 this 指 针 存放于CX寄存器,参数从右到左 压 。thiscall不是 关键词 ,因此不能被程序 员 指定。
5 、 naked call 采用 1-4 的 调 用 约 定 时 ,如果必要的 话 , 进 入函数 时编译 器会 产 生代 码 来保存 ESI , EDI , EBX , EBP 寄存器,退出函数 时则产 生代 码 恢 复这 些寄存器的内容。 naked call 不 产 生 这样 的代 码 。naked call不是 类 型修 饰 符,故必 须 和_declspec共同使用。
调 用 约 定可以通 过 工程 设 置:Setting.../C/C++ /Code Generation 项进 行 选择 ,缺省状 态为 __cdecl 。
2 、_ _ cdecl 是C和C++程序的缺省 调 用方式。 每 一个 调 用它的函数都包含清空堆 栈 的代 码 ,所以 产 生的可 执 行文件大小会比 调 用_stdcall函数的大。 函数采用从右到左的 压栈 方式。
注意: 对 于可 变 参数的成 员 函数,始 终 使用 __cdecl 的 转换 方式。
3 、 __fastcall 调 用 约 定:它是通 过 寄存器来 传 送参数的( 实际 上,它用 ECX 和 EDX 传 送前两个双字( DWORD )或更小的参数,剩下的参数仍旧自右向左 压栈传 送,被 调 用的函数在返回前清理 传 送参数的内存 栈 )。
4 、 thiscall 仅仅应 用于 "C++" 成 员 函数。 this 指 针 存放于CX寄存器,参数从右到左 压 。thiscall不是 关键词 ,因此不能被程序 员 指定。
5 、 naked call 采用 1-4 的 调 用 约 定 时 ,如果必要的 话 , 进 入函数 时编译 器会 产 生代 码 来保存 ESI , EDI , EBX , EBP 寄存器,退出函数 时则产 生代 码 恢 复这 些寄存器的内容。 naked call 不 产 生 这样 的代 码 。naked call不是 类 型修 饰 符,故必 须 和_declspec共同使用。
调 用 约 定可以通 过 工程 设 置:Setting.../C/C++ /Code Generation 项进 行 选择 ,缺省状 态为 __cdecl 。
名字修
饰约
定:
1
、修
饰
名(Decoration name):"C"或者"C++"函数在内部(
编译
和
链
接)通
过
修
饰
名
识别
。
2
、
C
编译时
函数名修
饰约
定
规则
:
__stdcall
调
用
约
定在
输
出函数名前加上一个下划
线
前
缀
,后面加上一个"@"符号和其参数的字
节
数,格式
为
_functionname@number
,
例如:function(int a, int b),其修
饰
名
为
:_function@8
。
__cdecl
调
用
约
定
仅
在
输
出函数名前加上一个下划
线
前
缀
,格式
为
_functionname
。
__fastcall
调
用
约
定在
输
出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字
节
数,格式
为
:
@functionname@number
。
3
、
C++
编译时
函数名修
饰约
定
规则
:
__stdcall
调
用
约
定:
1)
、以
"?"
标识
函数名的
开
始,后跟函数名;
2)
、函数名后面以"@@YG"
标识
参数表的
开
始,后跟参数表;
3)
、参数表以代号表示:
X--void
,
D--char
,
E--unsigned char
,
F--short
,
H--int
,
I--unsigned int
,
J--long
,
K--unsigned long
,
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”
__cdecl
调
用
约
定:
规则
同上面的_stdcall
调
用
约
定,只是参数表的
开
始
标识
由上面的"@@YG"
变为
"@@YA"
。
__fastcall
调
用
约
定:
规则
同上面的_stdcall
调
用
约
定,只是参数表的
开
始
标识
由上面的"@@YG"
变为
"@@YI"
。
VC++
对
函数的省缺声明是
"__cedcl",
将只能被
C/C++
调
用。
注意:
1
、_beginthread需要__cdecl的
线
程函数地址,_beginthreadex和CreateThread需要__stdcall的
线
程函数地址。
2
、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定
义
:
#define CALLBACK __stdcall
#define WINAPI
__stdcall
3
、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl
*FunPointer)(int a, int b);
修
饰
符的
书
写
顺
序如上。
4
、
extern "C"
的作用:如果
Add(int a, int b)
是在
c
语
言
编译
器
编译
,而在
c++
文件使用,
则
需要在
c++
文件中声明:
extern "C" Add(int a, int b)
,因
为
c
编译
器和
c++
编译
器
对
函数名的解
释
不一
样
(
c++
编译
器解
释
函数名的
时
候要考
虑
函数参数,
这样
是了方便函数重
载
,而在
c
语
言中不存在函数重
载
的
问题
),使用
extern "C"
,
实质
就是告
诉
c++
编译
器,
该
函数是
c
库
里面的函数。
如果不使用extern "C"
则
会出
现链
接
错误
。
一般象如下使用:
#ifdef _cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C extern
#endif
#ifdef _cplusplus
extern "C"{
#endif
EXTERN_C int func(int a, int b);
#ifdef _cplusplus
}
#endif
5
、MFC提供了一些宏,可以使用AFX_EXT_CLASS来代替__declspec(DLLexport),并修
饰类
名,从而
导
出
类
,AFX_API_EXPORT来修
饰
函数,AFX_DATA_EXPORT来修
饰变
量
。
AFX_CLASS_IMPORT
:__declspec(DLLexport)
AFX_API_IMPORT
:__declspec(DLLexport)
AFX_DATA_IMPORT
:__declspec(DLLexport)
AFX_CLASS_EXPORT
:__declspec(DLLexport)
AFX_API_EXPORT
:__declspec(DLLexport)
AFX_DATA_EXPORT
:__declspec(DLLexport)
AFX_EXT_CLASS
:#ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
6
、DLLMain
负责
初始化(Initialization)和
结
束(Termination)工作,
每
当一个新的
进
程或者
该进
程的新的
线
程
访问
DLL
时
,或者
访问
DLL
的
每
一个
进
程或者
线
程不再使用DLL或者
结
束
时
,都会
调
用DLLMain。但是,使用TerminateProcess或TerminateThread
结
束
进
程或者
线
程,不会
调
用DLLMain。
7
、一个
DLL
在内存中只有一个
实
例
DLL
程序和
调
用其
输
出函数的程序的
关
系:
1)
、
DLL
与
进
程、
线
程之
间
的
关
系
DLL
模
块
被映射到
调
用它的
进
程的虚
拟
地址空
间
。
DLL
使用的内存从
调
用
进
程的虚
拟
地址空
间
分配,只能被
该进
程的
线
程所
访问
。
DLL
的句柄可以被
调
用
进
程使用;
调
用
进
程的句柄可以被
DLL
使用。
DLL
可以有自己的数据段,但没有自己的堆
栈
,使用
调
用
进
程的
栈
,与
调
用它的
应
用程序相同的堆
栈
模式。
2)
、
关
于共享数据段
DLL
定
义
的全局
变
量可以被
调
用
进
程
访问
;
DLL
可以
访问调
用
进
程的全局数据。
使用同一DLL的
每
一个
进
程都有自己的DLL全局
变
量
实
例。如果多个
线
程并
发访问
同一
变
量,
则
需要使用同
步
机制;
对
一个DLL的
变
量,如果希望
每
个使用DLL的
线
程都有自己的
值
,
则应该
使用
线
程局部存
储
(TLS
,Thread Local Strorage)。