zoukankan      html  css  js  c++  java
  • (转)函数调用方式与extern "C"

    原文:http://patmusing.blog.163.com/blog/static/13583496020103233446784/


    (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调用约定:

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

     

    __fastcall调用约定:

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

     

    thiscall调用约定:

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

     

    naked call调用约定:

    采用上述4种调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESIEDIEBXEBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。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++"函数等。修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。函数名修饰约定随编译种类(CC++)和调用约定的不同而不同,下面分别说明。

     

    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只有一个参数,是对类CTestconst引用参数,其函数修饰名为“?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中的MakeFunba一样使用C++方式编译,只是b模块的函数调用方式是__cdecl,由于b包含了a提供的头文件中MakeFun函数声明,所以MakeFunb模块中被其它调用MakeFun的函数认为是__cdecl调用方式,b模块中的这些函数在调用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结束时自己恢复了堆栈,b模块中的函数这样多此一举就引起了栈指针错误,从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因为参数传递顺序是一样的),MakeFun也完成了自己的功能,只是函数返回后引发错误。解决的方法也很简单,只要保证两个模块的在编译时设置相同的函数调用约定就行了。

     

    现在再假定两个模块在编译的时候都采用__stdcall调用约定,但是a.dll使用C语言的语法编译的(C语言方式),所以a.dll的载入库a.libMakeFun函数的名字修饰就是“_MakeFun@4”b包含了a提供的头文件中MakeFun函数声明,但是由于b采用的是C++语言编译,所以MakeFunb模块中被按照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 将修饰名转换为未修饰形式。

  • 相关阅读:
    MongoDB查询修改操作语句命令大全
    SpringBoot读取war包jar包Resource资源文件解决办法
    linux lsof命令详解
    taskset
    POI导出excel,本地测试没问题,linux测试无法导出
    js中文乱码
    处理Account locked due to 217 failed logins的问题
    普通用户无法su到root用户
    gp数据库停止
    eclipse导入maven工程missing artifact(实际是存在的)错误解决
  • 原文地址:https://www.cnblogs.com/sfqtsh/p/3667653.html
Copyright © 2011-2022 走看看