`
java-admin
  • 浏览: 1362405 次
  • 性别: Icon_minigender_1
  • 来自: 陕西.西安
社区版块
存档分类
最新评论

C++项目中的extern "C" {}

阅读更多

引言

在用C++的项目源码中,经常会不可避免的会看到下面的代码:

1 #ifdef __cplusplus
2 extern "C" {
3 #endif
4   
5 /*...*/
6   
7 #ifdef __cplusplus
8 }
9 #endif

它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中。下面我就从以下几个方面来介绍它:

  • 1、#ifdef _cplusplus/#endif _cplusplus及发散
  • 2、extern "C"
    • 2.1、extern关键字
    • 2.2、"C"
    • 2.3、小结extern "C"
  • 3、C和C++互相调用
    • 3.1、C++的编译和连接
    • 3.2、C的编译和连接
    • 3.3、C++中调用C的代码
    • 3.4、C中调用C++的代码
  • 4、C和C++混合调用特别之处函数指针

1、#ifdef _cplusplus/#endif _cplusplus及发散

在介绍extern "C"之前,我们来看下#ifdef _cplusplus/#endif _cplusplus的作用。很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus——表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。

在这里为什么需要#ifdef _cplusplus/#endif _cplusplus呢?因为C语言中不支持extern "C"声明,如果你明白extern "C"的作用就知道在C中也没有必要这样做,这就是条件编译的作用!在.c文件中包含了extern "C"时会出现编译时错误。

既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。还记得腾讯笔试就考过这个题目,给出类似下面的代码(下面是我最近在研究的一个开源web服务器——Mongoose的头文件mongoose.h中的一段代码):

01 #ifndef MONGOOSE_HEADER_INCLUDED
02 #define    MONGOOSE_HEADER_INCLUDED
03   
04 #ifdef __cplusplus
05 extern "C" {
06 #endif /* __cplusplus */
07   
08 /*.................................
09  * do something here
10  *.................................
11  */
12   
13 #ifdef __cplusplus
14 }
15 #endif /* __cplusplus */
16   
17 #endif /* MONGOOSE_HEADER_INCLUDED */

然后叫你说明上面宏#ifndef/#endif的作用?为了解释一个问题,我们先来看两个事实:

  • 这个头文件mongoose.h可能在项目中被多个源文件包含(#include "mongoose.h"),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被#include两次(如,a.h头文件包含了mongoose.h,而在b.c文件中#include a.h和mongoose.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
  • 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立

为了解决这个问题,上面代码中的

#ifndef MONGOOSE_HEADER_INCLUDED
#define    MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#endif /* MONGOOSE_HEADER_INCLUDED */

就起作用了。如果定义了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到mongoose.h头文件,它的内容会被读取且给定MONGOOSE_HEADER_INCLUDED一个值。之后再次看到mongoose.h头文件时,MONGOOSE_HEADER_INCLUDED就已经定义了,mongoose.h的内容就不会再次被读取了。

2、extern "C"

首先从字面上分析extern "C",它由两部分组成——extern关键字、"C"。下面我就从这两个方面来解读extern "C"的含义。

2.1、extern关键字

在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。首先来一个例子:

1 //file1.c:
2     int x=1;
3     int f(){do something here}
4 //file2.c:
5     extern int x;
6     int f();
7     void g(){x=f();}

在file2.c中g()使用的x和f()是定义在file1.c中的。extern关键字表明file2.c中x,仅仅是一个变量的声明,其并不是在定义变量x,并未为x分配内存空间。变量x在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。但是可以声明多次,且声明必须保证类型一致,如:

1 //file1.c:
2     int x=1;
3     int b=1;
4     extern c;
5 //file2.c:
6     int x;// x equals to default of int type 0
7     int f();
8     extern double b;
9     extern int c;

在这段代码中存在着这样的三个错误:

  1. x被定义了两次
  2. b两次被声明为不同的类型
  3. c被声明了两次,但却没有定义

回到extern关键字,extern是C/C++语言中表明函数全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

与extern对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

2.2、"C"

典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。

为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:

1 extern "C" char* strcpy(char*,const char*);

注意它与下面的声明的不同之处:

1 extern char* strcpy(char*,const char*);

下面的这个声明仅表示在连接的时候调用strcpy()。

extern "C"指令非常有用,因为C和C++的近亲关系。注意:extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。

还有要说明的是,extern "C"指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern "C",仍然要遵守C++的类型检测、参数转换规则。

再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定extern关键字,但是当你又加上了"C",它不会改变语义,但是会改变它的编译和连接方式。

如果你有很多语言要加上extern "C",你可以将它们放到extern "C"{ }中。

2.3、小结extern "C"

通过上面两节的分析,我们知道extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern "C",表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)

3、C和C++互相调用

我们既然知道extern "C"是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern "C"来实现相互调用。

3.1、C++的编译和连接

C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:

1 void print(int i);
2 void print(char c);
3 void print(float f);
4 void print(char* s);

编译为:

1 _print_int
2 _print_char
3 _print_float
4 _pirnt_string

这样的函数名,来唯一标识每个函数。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。

C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。

3.2、C的编译和连接

C语言中并没有重载和类这些特性,故并不像C++那样print(int i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern "C"的作用就体现出来了。

3.3、C++中调用C的代码

假设一个C的头文件cHeader.h中包含一个函数print(int i),为了在C++中能够调用它,必须要加上extern关键字(原因在extern关键字那节已经介绍)。它的代码如下:

1 #ifndef C_HEADER
2 #define C_HEADER
3   
4 extern void print(int i);
5   
6 #endif C_HEADER

相对应的实现文件为cHeader.c的代码为:

1 #include <stdio.h>
2 #include "cHeader.h"
3 void print(int i)
4 {
5     printf("cHeader %d\n",i);
6 }

现在C++的代码文件C++.cpp中引用C中的print(int i)函数:

1 extern "C"{
2 #include "cHeader.h"
3 }
4   
5 int main(int argc,char** argv)
6 {
7     print(3);
8     return 0;
9 }

执行程序输出:

image 

3.4、C中调用C++的代码

现在换成在C中调用C++的代码,这与在C++中调用C的代码有所不同。如下在cppHeader.h头文件中定义了下面的代码:

1 #ifndef CPP_HEADER
2 #define CPP_HEADER
3   
4 extern "C" void print(int i);
5   
6 #endif CPP_HEADER

相应的实现文件cppHeader.cpp文件中代码如下:

1 #include "cppHeader.h"
2   
3 #include <iostream>
4 using namespace std;
5 void print(int i)
6 {
7     cout<<"cppHeader "<<i<<endl;
8 }

在C的代码文件c.c中调用print函数:

1 extern int print(int i);
2 int main(int argc,char** argv)
3 {
4     print(3);
5     return 0;
6 }

 

注意在C的代码文件中直接#include "cppHeader.h"头文件,编译出错。而且如果不加extern int print(int i)编译也会出错。

4、C和C++混合调用特别之处函数指针

当我们C和C++混合编程时,有时候会用一种语言定义函数指针,而在应用中将函数指针指向另一中语言定义的函数。如果C和C++共享同一中编译和连接、函数调用机制,这样做是可以的。然而,这样的通用机制,通常不然假定它存在,因此我们必须小心地确保函数以期望的方式调用。

而且当指定一个函数指针的编译和连接方式时,函数的所有类型,包括函数名、函数引入的变量也按照指定的方式编译和连接。如下例:

01 typedef int (*FT) (const void* ,const void*);//style of C++
02   
03 extern "C"{
04     typedef int (*CFT) (const void*,const void*);//style of C
05     void qsort(void* p,<code clas
分享到:
评论

相关推荐

    C++项目中的extern “C” {}

     在用C++的项目源码中,经常会不可避免的会看到下面的代码:  #ifdef __cplusplus  extern "C" {  #endif  /*…*/  #ifdef __cplusplus  }  #endif  它到底有什么用呢,你知道吗?而且这样的问题...

    MATLAB模糊控制器(*.fis)C/C++接口文件

    * 1、将fis.c文件拷贝到项目中; * 2、修改VS2010的配置属性,项目-&gt;属性-&gt;C/C++-&gt;预处理器-&gt;预处理器定义,添加 * _CRT_SECURE_NO_WARNINGS * 3、在【解决方案资源管理器】中选中fis.c文件,单击右键的属性 * ...

    C++Builder调用vs2010演示代码

    #define DLL_API extern "C" _declspec(dllexport) #else #define DLL_API extern "C" _declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif DECLDIR int __stdcall InitKpHttp(void); ...

    flutter调用c/c++方法并在项目中使用(记录)

    flutter调用c/c++方法并在项目中使用(记录) 参考 官方文档 做人要简单的帖子 1、在vscode中使用指令新建一个plugin项目:flutter create -t plugin native_add 2、在新建的native_add项目中,新建native_add.cpp...

    C#与C++与互操作实例讲解

    一、C#调用C++库 1、创建C++库 打开VisualStudio,创建一个C++工程,输入项目名称...加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。 #include stdafx.h #include&lt;iostre

    C++中静态成员函数与静态成员变量(static )

    C++中静态成员函数与静态成员变量(static ) ...这在C语言的多人项目中非常有用,避免了变量的重名。然而在C++中这一功能已经被命名空间取代,但是为了保持和C语言的兼容,static还是有这样的功能。 (3)

    C语言代码中调用C++代码的方法示例

    由于历史原因,以及不同开发人员的技术偏好,C语言和C++语言都有一些独有的非常有价值的项目,因而两种语言的互操作,充分利用前人造的轮子是一件非常有价值的事情。 C++代码调用C代码很简单,只要分别在包含的C...

    C# 调用C++写的dll的实现方法

    C++dll代码: 代码如下:extern”C”__declspec(dllexport) char* ShowImages(BYTE img[],int w,int h){;} C#调用dll基本也是固定格式,如下样式,这边有添加unsafe这是因为有用到指针的缘故[没用指针不需要unsafe],...

    LoadRunner测试过程中调用dll文件的制作与使用

    在该项目中新建一个dllfortest.h和dllfortest.cpp文件,文件的内容如下: //dllfortest.h extern "C" _declspec(dllexport) int Max(int a, int b, int c); extern "C" _declspec(dllexport) int Min(int a, int b,...

    vs2019 实现C#调用c++的dll两种方法

    1.托管与非托管的区别 链接地址(仅供参考) ...第一步:创建C++空项目(命名Caculate)添加一个类AddOperate .h代码部分: #pragma once extern "C" _declspec(dllexport) int Sum(int a, int b); class AddOperate

    C#调用C++dll方法步骤

    C#调用C++dll的方法和步骤 其他分享涉及到的概念和方法对于像我这样比较菜的选手看起来比较费劲并且很难抓住...extern C _declspec(dllexport)int add(int a ,int b) { int sum=a+b; return sum; } 注意:函数前一

    如何用VC++创建及调用DLL

    在将C++生成的DLL供标准C语言使用,输出文件需要用“extern "C"”修饰,否则不能被标准C语言调用。如果使用“__stdcall”调用方式,可能产生C不识别的修饰名,所以设置导出函数时要采用.def文件形式,而不是__...

    创建和使用DLL的示例代码

    一个使用创建和使用dll的示例代码,开发环境为...MyDll是一个dll项目,使用extern "C"导出内容,会生成一个dll;MyDll2也是一个dll项目,不使用extern "C"导出内容,也会生成一个dll。LearnDLL演示了怎么使用生成的dll

    求一个矩阵的特征值码matlab代码-EllipseFitting:基于代数距离的椭圆拟合(c++实现)

    求一个矩阵的特征值码matlab代码 EllipseFitting ...3.2.1)已经为我们在头文件中加上了这些限制符,因此最新的版本可以兼容c和c++,所以直接在项目包含头文件即可。 比如像下面一样: //Eigen #incl

    Linux编译程序时的一些注意事项

     一、在C++程序里要包含C语言写的程序的头文件时,在C++文件里应该这样来包含: extern C { #include 你的头文件 }  注意:这里是大写的C。  举个例子:在一个目录里有main.cpp hello.c hello.h三...

    hf_ctp_c_proxy:最新版本已合并到 hf_ctp_py_proxy

    最新版本合并到 hf_ctp_py_proxy 项目中,hf_ctp_c_proxy上期技术CTP封装,可用C#,PY等语言调用.采用最新CTP版本,支持期权交易.支持win和linux 64位系统.##C++封装的目的是什么?通过封装,将原DLL中无法访问到的函数...

    C#调用C++的dll文件

     extern C{  #endif  __declspec(dllexport) int __cdecl add(int a, int b);  #ifdef __cplusplus  }  #endif  int add(int a, int b)  {  return a + b;  }  AdderImpl项目修改配置类型为...

    python使用ctypes库调用DLL动态链接库

    最近要使用python调用C++编译生成的DLL动态链接库,因此学习了一下ctypes库的基本使用。 ctypes是一个用于Python的外部函数库,它提供C兼容的...#define DLLEXPORT extern "C" __declspec(dllexport) //放在 #include

    万能makefile写法详解,一步一步写一个实用的makefile

    2-这时打开main.d查看,发现main.d中未加入pub.h,因为根据模式规则%.d: %.c中的定义,只有依赖的.c文件变了,才会重新生成.d,而刚才改的是command.h,不会重新生成main.d、及在main.d中加入对pub.h的依赖关系,这...

    Vulkan_samples:使用Vulkan API的计算机图形学项​​目

    纹理:在我们的Vulkan项目中用作纹理的图像 依存关系: GLFW (窗口系统和输入) GLM (数学库) Vulkan SDK (对Vulkan开发有用的存储库集)(安装在特定于平台的目录中) Vulkan装载机(Khronos) Vulkan验证...

Global site tag (gtag.js) - Google Analytics