编译器的函数名修饰
函数名字修饰(Decorated Name)方式 函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串,用来指 明函数的定义或原型。LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正 确位置。多数情况下程序员并不需要知道函数的名字修饰,LINK程序或其他工具会自动区 分他们。当然,在某些情况下需要指定函数的名字修饰,例如在C++程序中,为了让LINK 程序或其他工具能够匹配到正确的函数名字,就必须为重载函数和一些特殊的函数(如构造 函数和析构函数)指定名字装饰。另一种需要指定函数的名字修饰的情况是在汇编程序中调 用C或C++的函数。如果函数名字,调用约定,返回值类型或函数参数有任何改变,原来 的名字修饰就不再有效,必须指定新的名字修饰。C和C++程序的函数在内部使用不同的名 字修饰方式,下面将分别介绍这两种方式。 1. C编译器的函数名修饰规则 对^—stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数 名后面加上一个“@”符号和其参数的字节数,例如一functionname@number。__cdecl调用 约定仅在输出函数名前加上一个下划线前缀,例如_functionname。_fastcall调用约定在输 出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如 @ functionname @number 2. 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 Function 1 (char *varl,unsigned long); 其函数修饰名为“?Functionl@@YGHPADK@Z”,而对于函数声明: void Function2(); 其函数修饰名则为“?Function2@@YGXXZ” 。 对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数 稍有不同,首先就是在函数名字和参数表之间插入以“@“字符引导的类名;其次是参数表 的开始标识不同,公有(public)成员函数的标识是“@@QAE“,保护(protected)成员函 数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用 了 const关键字,则相应的标识应分别为“@@QBE”,和“@@ABE”。如果参 数类型是类实例的引用,贝。使用“AAV1”,对于const类型的引用,贝。使用“ABV1”。下面 就以类CTest为例说明C++成员函数的名字修饰规则: class CTest private: void Function(int); protected: void Copylnfofconst CTest 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“表示这是一个私有函数。成员函数Copyinfo只有一个参数,是对类CTest的 const 引用参数,其函数修饰名为 “?CopyInfo@CTest@@IAEXABVl@@Z”。DrawText 是一 个比较复杂的函数声明,不仅有字符串参数,还有结构体参数和HDC句柄参数,需要指出 的是HDC实际上是一个HDC_结构类型的指针,这个参数的表示就是“PAUHDC__@@”, 其完整的函数修饰名为 “ ?DrawText@CTest@@QAEJPAUHDC_@@JPBDUtagRGBQUAD@@E_N@Z ” 。 InsightClass是一个共有的const函数,它的成员函数标识是“@@QBE”,完整的修饰名就 是“?InsightClass@CTest@@QBEJK@Z“。 无论是C函数名修饰方式还是C++函数名修饰方式均不改变输出函数名中的字符大小写, 这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。 3. 查看函数的名字修饰 有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用Dumpbin 工具。使用/FAc, /FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用 dumpbin. /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。此外, 还可以使用undname.将修饰名转换为未修饰形式。 函数调用约定和名字修饰规则不匹配引起的常见问题 函数调用时如果出现堆栈异常,十有八九是由于函数调用约定不匹配引起的。比如动态 链接库a有以下导出函数:long MakeFun(long IFun); 动态库生成的时候采用的函数调用约定是_stdcall,所以编译生成的a.dll中函数MakeFun 的调用约定是一stdcall,也就是函数调用时参数从右向左入栈,函数返回时自己还原堆栈。 现在某个程序模块b要引用a中的MakeFun, b和a 一样使用C++方式编译,只是b模块的 函数调用方式是—cdecL由于b包含了 a提供的头文件中MakeFun函数声明,所以MakeFun 在b模块中被其它调用MakeFun的函数认为是_cdecl调用方式,b模块中的这些函数在调 用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结束时自己恢复了堆栈,b模 块中的