教你看懂C++类库函数定义之三---_stdcall
一切从一个C++ 类库头文件开始,现在在做一个C++的项目,期间用到一个开源的界面库DUILib(类似MFC),这个东西还不错能很容易的写出漂亮的界面,比如QQ的界面,可以去下载下来研究研究,地址:http://code.google.com/p/duilib/
废话不多说,我比较困扰的是UIWebBrowser.h这个头文件,虽然是C++写的,但里面包含太多大学C++课本以外的东西,第一遍看下来跟看天书一样,里面有很多的不惑,接下来我们一个一个解开。
首先看一下这个函数定义:
virtual HERSULT STDMETHODCALLTYPE GetTypeInfoCount( __RPC__out UINT *pctinfo);
这一篇详细介绍 __stdcall
上篇文章我们知道#define STDMETHODCALLTYPE __stdcall ,那__stdcall又是个什么东东呢,有什么作用呢?下面来完全的了解一下.
1. _cdecl
(1). 是C Declaration的缩写,表示C语言默认的函数调用方法,实际上也是C++的默认的函
数调用方法。
(2). 所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。具体所示:调用
方的函数调用->被调用函数的执行->被调用函数的结果返回->调用方清除调整堆栈。 (3). 被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全
不同的参数都不会产生编译阶段的错误。总的来说函数的参数个数可变的(就像printf函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。
(4). 因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。
2. _stdcall(CALLBACK/WINAPI)
(1). 是Standard Call的缩写,要想函数按照此调用方式必须在函数名加入_stdcall,通常_
win32 api 应该是_stdcall调用规则。通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为_stdcall 方式,WINAPI都采用这种方式。
(2). 所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。具体所示:调用方的函数调用->被调用函数的执行-> 被调用方清除调整堆栈->被调用函数的结果返回。
(3). 这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。 (4). 函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能
多,不能少,否则返回后会出错。总的来说,就是函数的参数个数不能是可变的。是从 _cdecl 修改而来, _stdcall 不支持可变参数,并且清栈由被调用者负责,其他的都一样 (5). 因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小。
3. PASCAL 是Pascal语言的函数调用方式,也可以在C/C++中使用,参数压栈顺序与前两者相反。返回时的清栈方式忘记了。。。
4. _fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传
递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和_stdcall相当。
5. _thiscall 是为了解决类成员调用中this指针传递而规定的。_thiscall要求把this指针放
在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C++编译器使用eax。返回方式和_stdcall相当。
6. _fastcall 和 _thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以
Windows上的COM对象接口都定义为_stdcall调用方式。
7. C中不加说明默认函数为_cdecl方式(C中也只能用这种方式),C++也一样,但是默认
的调用方式可以在IDE环境中设置。
8. 带有可变参数的函数必须且只能使用_cdecl方式,例如下面的函数:
int printf(char * fmtStr, ...);
int scanf(char * fmtStr, ...); 9. 函数名修饰
(1). _cdecl :对于_cdecl而言,如果对于定义在C程序文件(编译器会通过后缀名为.C判断)
的输出函数,函数名会保持原样;对于定义在C++程序文件中的输出函数,函数名会被修饰(见10)。为使函数名不被修饰,有两种方法:A.可通过在前面加上extern “c”以去除函数名修饰;B. 可通过.def文件去除函数名修饰。 (2). _stdcall:无论是C程序文件中的输出函数还是C++程序文件中的输出函数,函数名都会
被修饰。对于定义在C++程序文件中的输出函数,好像更复杂,和_cdecl的情况类似。去除函数名修饰方法:只能通过.def文件去除函数名修饰。 10. 函数名修饰规则: (1). 为什么要函数名修饰:
函数名修饰就是编译器在编译期间创建的一个字符串,用来指明函数的定义和原型。
LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。多少情况下程序员并不需要知道函数的名字修饰,LINK程序或其他工具会自动区分他们。当然,在某些情况下需要指定函数名修饰,例如在c++程序中,为了让LINK程序或其他工具能够匹配到正确的函数名字,就必须为重载函数后一些特殊函数(如构造函数和析构函数)指定名字修饰。另一种需要指定函数名修饰的情况是在汇编程序中调用C或C++函数。 (2). C语言:
对于_stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数
名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。_cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。_fastcall调用约定在输出函数名前加上一个 “@“符号,后面也是一个”@“符号和其参数的字节数,例如@functionname@number。 (3). C++语言:
C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。参数表的拼写代号如下所示: X--void D--char
E--unsigned char F--short H--int
I--unsigned int J--long
K--unsigned long(DWORD) M--float N--double _N—bool U—struct ....
指针的方式有些特别,用PA表示指针,用PB表示const类型的指针。后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。U表示结构类型,通常后跟结构体的类型名,用“@@”表示结构类型名的结束。函数的返回值不作特殊处理,它的描述方式和函数参数一样,紧跟着参数表的开始标志,也就是说,函数参数表的第一项实际上是表示函数的返回值类型。参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。下面举两个例子,假如有以下函数声明:
int Function1(char *var1,unsigned long);
其函数修饰名为“?Function1@@YGHPADK@Z”,而对于函数声明: oid Function2();
其函数修饰名则为“?Function2@@YGXXZ” 。
对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。 11. 查看函数的名字修饰
有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用Dumpbin工具。使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用dumpbin.exe /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。此外,还可以使用 undname.exe 将修饰名转换为未修饰形式。
12. _beginthread需要_cdecl的线程函数地址,_beginthreadex和_CreateThread需要_stdcall
的线程函数地址。
13. #define CALLBACK __stdcall //这就是传说中的回调函数
#define WINAPI __stdcall //这就是传说中的WINAPI #define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在这里 #define APIPRIVATE __stdcall #define PASCAL __stdcall