zoukankan      html  css  js  c++  java
  • C/C++:函数的编译方式与调用约定以及extern “C”的使用

    转自:https://www.cnblogs.com/qinfengxiaoyue/archive/2013/02/04/2891908.html

    函数在C++编译方式与C编译方式下的主要不同在于:由于C++引入了函数重载(overload),因此编译器对同名函数进行了名称重整(name mangle)。因此,在C++中引

    用其他C函数库时,需要对声明使用的函数做适当的处理,以告知编译器做出适应的名称处理。

    函数的调用约定涉及了函数参数的入栈顺序、清栈主体(负责清理栈的主体:函数自身还是调用函数者?)、部分名称重整。

    如,在C编译方式下有_stdcall、_cdecl等调用约定,在C++编译方式下也有_stdcall、_cedecl等调用约定。

    两个复杂修饰的例子:

    extern "C" _declspec(dllexport) int __cdecl Add(int a, int b); //C编译方式导出_cdecl调用约定函数

    typedef int (__cdecl*FunPointer)(int a, int b);


    转自:http://blog.csdn.net/H2SO2H2SO2/article/details/4207127  

    1.编译方式

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

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

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

    __fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式@functionname@number。

    它们均不改变输出函数名中的字符大小写,这和pascal调用约定不同,pascal约定输出的函数名无任何修饰且全部大写。     

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

    __stdcall调用约定

    1、以“?”标识函数名的开始,后跟函数名;

    2、函数名后面以“@@yg”标识参数表的开始,后跟参数表;

    3、参数表以代号表示:

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

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

    5、参数表后以“@z”标识整个名字的结束,如果该函数无参数,则以“z”标识结束。

    其格式为“?functionname@@yg*****@z”或“?functionname@@yg*xz”,例如      int test1-----“?test1@@yghpadk@z”       void test2-----“?test2@@ygxxz

    __cdecl调用约定

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@yg”变为“@@ya”。

    __fastcall调用约定

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@yg”变为“@@yi”。

    2.调用约定

    调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方

    式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。

    在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便允许同一个名字(具有不同的参

    数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器。这项技术通常被称为名称改编(Name Mangling)或者名称修

    饰(Name Decoration)。许多C++编译器厂商选择了自己的名称修饰方案。

    因此,为了使其它语言编写的模块(如Visual Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使

    用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰。

    调用约定用来:(一)处理决定函数参数传送时入栈和(二)出栈的顺序由调用者还是被调用者把参数弹出栈),以及(三)编译器来识别函数

    称的名称修饰约定等问题。

    1、__cdecl

    __cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时,函数参数按

    照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈因此,实现可变参数的函数只能使用该调用约定。由于每一个使用

    __cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。

    2、__stdcall

    __stdcall调用约定用于调用Win32 API函数。采用__stdcal约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参

    数的栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆

    栈。__stdcall可以写成_stdcall。

    3、__fastcall

    __fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在

    ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。

    关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...->C/C++->Code Generation项选

    择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd,即__cdecl。当加在输出函数前的关键字与编译环境中的选择不同时,直

    接加在输出函数前的关键字有效。

    3._stdcall与_cdecl调用约定对比

    在“windef.h”头文件中可找到:

    #define CALLBACK __stdcall
    
    
    
    #define WINAPI __stdcall
    
    
    
    #define WINAPIV __cdecl
    
    
    
    #define APIENTRY WINAPI
    
    
    
    #define APIPRIVATE __stdcall
    
    
    
    #define PASCAL __stdcall
    
    
    
    #define cdecl _cdecl
    
    
    
    #ifndef CDECL#define CDECL _cdecl
    
    
    
    #endif
    

    几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,为什么?

    首先,我们谈一下两者之间的区别:WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用

    完成后,栈需要清除,这里就是问题的关键,如何清除?如果我们的函数使用了__cdecl,那么栈的清除工作是由调用者,用

    COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常

    的完成清除工作呢?答案是不能如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平

    台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函

    数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这

    种情况我们只能使用_cdecl

    注意:

    1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址

    2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:

    #define CALLBACK __stdcall

    #define WINAPI __stdcall

    3、复杂函数声明或指针的修饰符示例:

    extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);

    typedef int (__cdecl*FunPointer)(int a, int b);

    4、extern ”C” 的作用参考:http://hi.baidu.com/qinfengxiaoyue/item/8bd89e81d1cbeb5226ebd9b4

    为什么标准头文件都有类似以下的结构?

      #ifndef __INCvxWorksh
    
    
    
      #define __INCvxWorksh
    
    
    
      #ifdef __cplusplus
    
    
    
      extern "C" {
    
    
    
      #endif
    
    
    
      /*...*/
    
    
    
      #ifdef __cplusplus
    
    
    
      }
    
    
    
      #endif
    
    
    
      #endif /* __INCvxWorksh */
    

    显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

    那么

    #ifdef __cplusplus

    extern "C" {

    #endif

    #ifdef __cplusplus

    }

    #endif

    的作用又是什么呢?

    答:被extern "C" 修饰的变量和函数是按照C语言方式编译和连接的;即为实现C++与C语言的混合编程。

    明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。

    extern "C"的惯用法:

    (1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

    extern "C"

    {

    #include "cExample.h"

    }

    而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编

    译语法错误。

    以C++引用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;
    
    
    
      }
    

    如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

    (2)在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;
    
    
    
      }
    

    5、MFC提供了一些宏,可以使用AFX_EXT_CLASS来代替__declspec(DLLexport),并修饰类名,从而导出类,

    AFX_API_EXPORT来修饰函数,AFX_DATA_EXPORT来修饰变量

    AFX_CLASS_IMPORT:__declspec(DLLexport)

    AFX_API_IMPORT:__declspec(DLLexport)

    AFX_DATA_IMPORT:__declspec(DLLexport)

    AFX_CLASS_EXPORT:__declspec(DLLexport)

    AFX_API_EXPORT:__declspec(DLLexport)

    AFX_DATA_EXPORT:__declspec(DLLexport)

    AFX_EXT_CLASS:#ifdef _AFXEXT

    AFX_CLASS_EXPORT

    #else

    AFX_CLASS_IMPORT

    6、DLLMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或

    者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DLLMain。但是,使用TerminateProcess或

    TerminateThread结束进程或者线程,不会调用DLLMain。

    7、一个DLL在内存中只有一个实例

    DLL程序和调用其输出函数的程序的关系:

    1)、DLL与进程、线程之间的关系

    DLL模块被映射到调用它的进程的虚拟地址空间。

    DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。

    DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。

    DLL可以有自己的数据段,但没有自己的堆栈,使用调用进程的栈,与调用它的应用程序相同的堆栈模式。

    2)、关于共享数据段

    DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局

    变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己

    的值,则应该使用线程局部存储(TLS,Thread Local Strorage).

    既然选择了远方,就不顾风雨兼程
  • 相关阅读:
    A1023 Have Fun with Numbers (20分)(大整数四则运算)
    A1096 Consecutive Factors (20分)(质数分解)
    A1078 Hashing (25分)(哈希表、平方探测法)
    A1015 Reversible Primes (20分)(素数判断,进制转换)
    A1081 Rational Sum (20分)
    A1088 Rational Arithmetic (20分)
    A1049 Counting Ones (30分)
    A1008 Elevator (20分)
    A1059 Prime Factors (25分)
    A1155 Heap Paths (30分)
  • 原文地址:https://www.cnblogs.com/zhengfa-af/p/8108355.html
Copyright © 2011-2022 走看看