部分转自:http://mxdxm.iteye.com/blog/510486
简要介绍下函数名字修饰规则和函数调用约定的作用:
函数调用方式决定了函数参数入栈的顺序,是由调用者函数还是被调用函数负责清除栈中的参数等问题,
函数名修饰规则决定了编译器使用何种名字修饰方式来区分不同的函数。
什么是函数名字修饰(Decorated Name)方式
函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串,用来指明函数的定义或原型。LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。多数情况下程序员并不需要知道函数的名字修饰,LINK程序或其他工具会自动区分他们。当然,在某些情况下需要指定函数的名字修饰,例如在C++程序中,为了让LINK程序或其他工具能够匹配到正确的函数名字,就必须为重载函数和一些特殊的函数(如构造函数和析构函数)指定名字装饰。另一种需要指定函数的名字修饰的情况是在汇编程序中调用C或C++的函数。如果函数名字,调用约定,返回值类型或函数参数有任何改变,原来的名字修饰就不再有效,必须指定新的名字修饰。C和C++程序的函数在内部使用不同的名字修饰方式,下面将分别介绍这两种方式。
1 C编译器的函数名修饰规则
(1)__stdcall调用约定:
编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。
(2)__cdecl调用约定:
仅在输出函数名前加上一个下划线前缀,例如_functionname。
(3)__fastcall调用约定:
在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number 。
2 C++编译器的函数名修饰规则
2.1普通函数的函数名修饰规则
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”,
void Function2();
其函数修饰名则为“?Function2@@YGXXZ”
小总结:"?" + 函数名 + 参数表开始表示符 + 返回值类型 + 参数类型 + 结束标志
2.2 类的成员函数的函数名修饰规则 thiscall
对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍的不同 (1)在函数名字和参数表之间插入以“@”字符引导的类名;
(2)其次是参数表的开始标识不同:
公有(public)成员函数的标识是“@@QAE”,
保护(protected)成员函数的标识是“@@IAE”,
私有(private)成员函数的标识是“@@AAE”,
如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。
(3)如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。
下面就以类CTest为例说明C++成员函数的名字修饰规则:
class CTest
{
......
private:
void Function(int);
protected:
void CopyInfo(const CTest &src);
public:
long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);
long InsightClass(DWORD dwClass) const;
......
};
私有
成员函数Function:其函数修饰名为“?Function@CTest@@AAEXH@Z”,字符串“@@AAE”表示这是一个私有函数,X表示返回值类型是void
@Z表示表示结束
保护成员函数CopyInfo:其函数修饰名为“?CopyInfo@CTest@@IAEXABV1@Z”,字符串“@@IAE”表示这是一个保护函数,X表示返回值类型是 void类型,ABV1表示参数是类CTest的const引用参数
公有成员函数DrawText:是一个比较复杂的函数声明,不仅有字符串参数,还有结构体参数和HDC句柄参数,需要指出的是HDC实际上是一个HDC__ 结构类型的指针,这个参数的表示就是“PAUHDC__@@”,其完整的函数修饰名为
“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。
字符串“@@QAE”表示这是一个公有函数,J表示返回值类型为long,第一个参数PAUHDC__@@:HDC实际上是一个HDC__ 结构类型的指针, PA指针U结构体,第二个参数J:long类型,
公有成员函数nsightClass:是一个共有的const函数,它的成员函数标识是“@@QBE”,完整的修饰名就是“?InsightClass@CTest@@QBEJK@Z”。
无论是C函数名修饰方式还是C++函数名修饰方式均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。
3.查看函数的名字修饰
(1)使用编译输出列表或使用Dumpbin工具。使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用 dumpbin.exe /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。
(2)此外,还可以使用 undname.exe 将修饰名转换为未修饰形式。
4 函数调用约定
(1)_cdecl(又称为C调用约定,是C语言缺省调用约定)
编译器的命令行参数是/Gd
函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。函数本身不清理堆栈。
因此,实现可变参数的函数只能使用该调用约定。
(2)_stdcall(是Pascal程序的缺省调用方式,_大多数Windows的API也是__stdcall调用约定。)
编译器的命令行参数是/Gz
参数从右到左压入堆栈,函数自身修改堆栈,
函数名自动加前导的下划线,后面紧跟@符号,其后紧跟着参数的尺寸。
(3)_fastcall
编译器的命令行参数是/Gr
_fastcall函数调用约定在可能的情况下使用寄存器传递参数,通常是前两个 DWORD类型的参数或较小的参 数使用ECX和EDX寄存器传递
参数按照从右向左的顺序入栈,被调用函数在返回之前负责清除栈中的参数
函数名自动加前导的下划线,后面紧跟@符号,其后紧跟着参数的尺寸。(同_stdcall相同)
(4)thiscall
只用在C++成员函数的调用,函数参数按照从右向左的顺序入栈,类实例的this指针通过ECX寄存器传递。
需要注意的是thiscall不是C++的关键字,不能使用thiscall声明函数,它只能由编译器使用。
(5)naked call
这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不 能用return返 回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计。
5 extern "C"的作用
被extern "C" 修饰的变量和函数是按照C语言方式编译和连接的;即为实现C++与C语言的混合编程
(1)在C++中引用C中的函数和变量
在C语言的头文件中,对外部函数只能定义为extern类型,不支持extern"C"声明
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
(2)在C中调用C++的函数和变量
在C中引用C++语言中的函数和变量时,C++的头文件中的函数声明需添加前缀extern "C",但是在C语言中不能直接 引用
已由extern "C"修饰过的函数声明或变量的头文件(因为C编译方式不支持extern “C” 关键字),应该在C中将需要引用 的C++ 函数的声明为extern类型。以C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 但这样会编译出错:#include "cExample.h",因为C编译不支持extern "C" 关键字 */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}