posts - 297,  comments - 15,  trackbacks - 0

(VC编译器下)

 

1. CALLBACKWINAPIAFXAPI到底是什么?它们分别在什么地方 被定义的?

在头文件windef.h中,CALLBACK, WINAPI, APIENTRY

……

#define CALLBACK  __stdcall

#define WINAPI         __stdcall

#define WINAPIV       __cdecl

#define APIENTRY    WINAPI

……

 

在头文件AFXVER_.H中,AFXAPI的定义如下:

    ……

   // AFXAPI is used on global public functions

#ifndef AFXAPI

        #define AFXAPI __stdcall

#endif

    ……

 

2. __stdcall__cdecl有什么作用?他们的区别是什么?

a. __stdcall是新标准C/C++函数的调用方法。从底层上说,使用这种调用方法参数的进栈顺序和 标准C调用(__cdecl方法)是一样的,都

  是从右到左,但是__stdcall采用自动清栈的方式,而__cdecl是手工清栈。

b. windows规定,凡事由它来负责调用的函数必须定义为_stdcall类型,比如回调函数、WinMain函数等。

c. 如果没有显试声明的话,函数的调用方法默认是__cdecl。

 

3. 调用约定种类

   一共有5中函数调用约定(calling convention),它决定一下内 容:

   1) 函数参数的压栈顺序

   2) 由调用者还是被调用者把参数弹出栈

   3) 产生函数修饰名的方法

 

__stdcall调用约定:

函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,

 

__cdecl调用约定:

是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码, 所以产生的可执行文件大小会比调用__stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。

 

__fastcall调用约定:

它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在 返回前清理传送参数的内存栈)。

 

thiscall调用约定:

仅仅应用于"C++"成 员函数。this指 针存放于CX寄存 器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

 

naked call调用约定:

采用上述4种调用约 定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

 

关键字 __stdcall、__cdecl 和 __fastcall 可以直接加在要输出的函数前,也可以在编译环境的 Setting...\C/C++ \Code Generation 项选择。当加在 输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd 和 /Gr。缺省状态为/Gd,即__cdecl。缺省状态为__cdecl。

 

4. 名 字修饰约定

"C" 或者 "C++" 函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译 函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出"C++"重载函数、构造函数、析构函数,又如在汇编代码里调用"C""或"C++"函数等。修饰名由函数名、类名、调用约定、返回类型、参数等共同 决定。函数名修饰约定随编译种类(C或C++)和调用约定的不同而不同,下面分别说明。

 

C编译时函数名修饰约定规则:

__stdcall调用约定:

    在输出函数名前加上一 个下划线前缀,后面加上一个"@"符号 和其参数的字节数,格式为 _functionname@number。

 

__cdecl调用约定:

    仅在输出函数名前加上一个下划线前缀,格式为 _functionname。

 

__fastcall调用约定:

    在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@functionname@number。

 

它们均不改变输出函数名中的字符大小写。

 

C++编译时函数名修饰约定规则:

__stdcall调用约定:

以"?"标识 函数名的开始,后跟函数名;函数名后面以"@@YG"标识参数表的开始,后跟参数表;

参数表以代号表示:

    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"代表一次重复;

 

参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前。

参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。其格式为

    "?functionname@@YG*****@Z" 或 "?functionname@@YG*XZ",

    例如

    int Test1(char *var1,unsigned long)    -----“?Test1@@YGHPADK@Z”

    void Test2()              -----“?Test2@@YGXXZ”(第一个X表示返回类型,第二个X表示参数 类型)

 

__cdecl调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用。

 

__fastcall调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。

 

对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“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,“H”表示参数类型为int类型。

 

成员函数CopyInfo只有一个参数,是对类CTest的const引用参数,其函数修饰名为“?CopyInfo@CTest@@IAEXABV1@@Z”。

 

DrawText是一个比较复杂的函数声明,不仅有字符串参数,还有结构体参数和HDC句柄参数,需要指出的是HDC实际上是一个HDC__结构类型的指针,这个参数的表示就是“PAUHDC__@@”,其完整的函数修饰名为“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。

 

InsightClass是一个共有的const函数,它的成员函数标识是“@@QBE”,完整的修饰名就是“?InsightClass@CTest@@QBEJK@Z”。

 

举例:

比如动态链接库a有以下导出函数:

long MakeFun(long lFun);

动态库生成的时候采用的函数调用约定是__stdcall,所以编译生成的a.dll中函数MakeFun的调用约定是_stdcall,也就是函数调用时参数从右向左入栈,函数返回时自己还原堆栈。现在某个程序模块b要引用a中的MakeFun,b和a一样使用C++方式编译,只是b模块的函数调用方式是__cdecl,由于b包含了a提供的头文件中MakeFun函数声明,所以MakeFun在b模块中被 其它调用MakeFun的函数认为是__cdecl调用方式,b模块中的 这些函数在调用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结束时自己恢复了堆栈,b模块中的函数这样多此一举就引起了栈指针错误,从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因为参数传递 顺序是一样的),MakeFun也完成了自己的功能,只是函数返回后引发错误。解决的方法也很简单,只要保证两个模块的在编译时设置相同的函数调用约定就行了。

 

现在再假定两个模块在编译的时候都采用__stdcall调用约定,但是a.dll使用C语言的语法编译的(C语言方式),所以a.dll的 载入库a.lib中MakeFun函数的名字修饰就是“_MakeFun@4”。b包含了a提供的头文件中MakeFun函数声明,但是由于b采用的是C++语言编译,所以MakeFun在b模块中被按照C++的名字修饰规则命名为“?MakeFun@@YGJJ@Z”,编译过程相安无事,链接程序时c++的链接器就到a.lib中去找“?MakeFun@@YGJJ@Z”,但是a.lib中只有“_MakeFun@4”,没有“?MakeFun@@YGJJ@Z”,于是链接器就报告:

error LNK2001: unresolved external symbol ?MakeFun@@YGJJ@Z

解决的方法和简单,就是要让b模块知道这个函数是C语言编译的,extern "C"可以做到这一点。一个采用C语言编译的库应该考虑到使用这个库的程序可能是C++程序(使用C++编译器),所以在设计头文件时应该注意这一点。通常应该这样声明 头文件:

#ifdef _cplusplus

extern "C" {

#endif

long MakeFun(long lFun);

#ifdef _cplusplus

}

#endif

这样C++的编译器就知道MakeFun的修饰名是“_MakeFun@4”,就不会有链接错误了。

 

许多人不明白,为什么我使用的编译器都是VC的编译器还会产生“error LNK2001”错误?其实,VC的编译器会根据源文件的扩展名选择编译方式,如果文件的扩展名是“.C”,编译器会采用C的语法编译,如果扩展名是“.cpp”,编译器会使用C++的语法编译程序,所以,最好的方法就是使用extern "C"。

 

5. 单看函数的名字修饰

有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用Dumpbin工具。使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用dumpbin.exe /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。此外,还可以使用 undname.exe 将修饰名转换为未修饰形式。

from:
http://patmusing.blog.163.com/blog/static/13583496020103233446784/

posted on 2010-04-28 00:10 chatler 阅读(541) 评论(0)  编辑 收藏 引用 所属分类: windows

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2010年11月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(10)

随笔分类(307)

随笔档案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感觉这个博客还是不错,虽然做的东西和我不大相关,觉得看看还是有好处的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新评论

阅读排行榜

评论排行榜