C++中的extern “C”用法详解

C++中的extern "C"用法详解

简介

在C++中,存在着C和C++的二进制兼容性问题,即C++编译后的函数名与C编译后的函数名不一样。这会导致当我们在头文件中声明一个C++函数的时候,在C语言中无法使用这个函数。所以我们需要在C++ 中使用 extern "C" 关键字声明特定函数,以便在 C++ 环境下使用 C 标准程序声明及定义的函数。

用法

使用 extern "C" 关键字前需要知道两个概念:

  • 名称修饰:C++ 编译器对函数名的处理是以一种叫做“名称修饰(Name Mangling)”的方式来实现的,这种方式的本质是在函数名前面加上符号“_”或者类似于“_Z”的字符来实现的。而在C语言中,由于不存在像C++中的函数重载等机制,所以编译器对函数名不做任何修饰。
  • 函数签名:函数签名是指函数的类型信息,包括函数类型、参数列表、参数类型和返回类型等信息。

由于名称修饰的存在,我们定义的函数名与C语言的原型并不一致。通过 extern "C" 关键字,我们可以将名称修饰去掉,使得C语言可以调用这个函数并链接成功。

下面我们来看看 extern "C" 的使用例子。

示例1:在C++代码中定义函数并在C中使用

假设我们在C++中有以下代码:

// Sum.cpp
#include <iostream>
#include "sum.h"

int sum(int a, int b) {
  std::cout << "Sum function in C++ is being called." << std::endl;
  return a + b;
}

其中,头文件 sum.h 的内容为:

// sum.h
#ifndef SUM_H
#define SUM_H

extern "C" int sum(int a, int b);

#endif

在我们编写完以上代码后,在C语言中可以通过 below 的方式调用:

// main.c
#include <stdio.h>
#include "sum.h"

int main(void) {
  int result = sum(1, 2);
  printf("%d\n", result);
  return 0;
}

则在链接的时候会提示找不到sum函数的符号,这个时候使用 extern "C" 来解决。

// Sum.cpp
#include <iostream>
#include "sum.h"

// 添加 extern "C" 修饰符
extern "C" {
  int sum(int a, int b) {
    std::cout << "Sum function in C++ is being called." << std::endl;
    return a + b;
  }
}

Sum.cpp 重新编译,并且在相应C编译器的编译选项(-I选项)中添加 sum.h 的头文件路径,即可在C中使用。

示例2:C++和C通过动态库进行交互

前面一节只是简单地通过头文件来解决问题。我们还可以通过动态库(或称为共享库)的方式,将需要提供C语言调用的接口打包成一个动态库,让C语言程序调用该动态库,以进行功能的扩展。

在这里我们同样以 sum 函数为例,来展示C++和C之间通过动态库进行交互的方法。

// Sum.cpp
#include <iostream>

extern "C" {
  int getSum(int a, int b) {
    std::cout << "Sum function in dynamic library is being called." << std::endl;
    return a + b;
  }
}

在这里,我们将 getSum 作为动态库中的函数提供给C语言调用。

接下来,我们先将 getSum 字符串名称修饰去掉:

// Sum.cpp
#include <iostream>

extern "C" {
  // 去掉名称修饰
  __declspec(dllexport) int __stdcall getSum(int a, int b) {
    std::cout << "Sum function in dynamic library is being called." << std::endl;
    return a + b;
  }
}

其中,__declspec(dllexport) 是 Windows 下的一个关键字(类似于 __attribute__((visibility("default"))),用于指定该函数导出的符号),因为动态库在编译的时候需要明确地指定导出和导入,这也是和静态库最大区别。

接下来,我们可以通过以下步骤来实现动态库的编译,以及C语言程序的调用:

1.编译 Sum.cpp,产生 Sum.obj 文件:

> g++ Sum.cpp -fPIC -c -o Sum.o

其中,-fPIC 表示生成位置独立的代码,防止反汇编时找不到详细地址等信息。

2.使用下列命令将 Sum.obj 文件生成动态库文件 libsum.so:

> g++ -shared -o libsum.so Sum.o

3.编写C程序,调用动态库:

// main.c
#include <stdio.h>
#include <dlfcn.h>

int main() {

    void *dlib = dlopen("./libsum.so", RTLD_NOW);

    if (dlib == NULL) {
        fprintf(stderr, dlerror()); 
        exit(1);
    }

    typedef int (*funcptr)(int, int);

    funcptr func = (funcptr)dlsym(dlib, "getSum");

    printf("%d\n", (*func)(1, 2));

    dlclose(dlib);

    return 0;
}

其中, dlfcn.h头文件包含了动态链接库的头文件,dlopen 函数可以用来打开动态库,dlsym函数的参数 getSum 是动态库中提供给C语言的函数名,dlclose 是关闭链接。

通过上述步骤,我们实现了C++和C之间通过动态库进行交互的方法。

结论

通过在C++中使用 extern "C" 关键字,我们可以在C++环境下使用C标准程序声明及定义的函数,并且可以通过动态库的方式提供给C语言进行调用。通过多种实例的讲解,相信读者已经对C++中的 extern "C" 用法有了更为详细的理解。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++中的extern “C”用法详解 - Python技术站

(0)
上一篇 2023年5月23日
下一篇 2023年5月23日

相关文章

  • C++ 如何将Lambda转换成函数指针

    要将 C++ 中的 Lambda 表达式转换成函数指针,需要使用到一种特殊的转换方式,也就是将 Lambda 表达式转换成函数指针类型。 Lambda 表达式是一种可调用对象,它往往是为了满足某些特定的需求而创建的,而将 Lambda 表达式转换成函数指针则可以让它更加灵活地应用于程序的不同场景。下面是具体的转换攻略: 步骤1:定义 Lambda 表达式 首…

    C 2023年5月23日
    00
  • C语言 位域详解及示例代码

    C语言 位域详解及示例代码 什么是位域 在 C 语言中,结构体中的成员可以是各种类型的变量,如整型、浮点型等。我们还可以用一种叫作位域的特殊类型来定义结构体中的成员。 位域是按位存储的,它允许我们将一个字节(也就是八个二进制位)分为几个不同长度的字段,然后用这些字段来存储不同的信息。这样,我们就可以用一个变量来存储多个信息,这样节省了内存空间。 位域的声明和…

    C 2023年5月24日
    00
  • C语言 strchr()函数

    当要在一个字符串中查找某个字符的位置时,可以使用C语言中的strchr()函数。下面是strchr()函数的完整使用攻略。 函数原型 char *strchr(const char *str, int c); 在参数str所指向的字符串中搜索第一次出现字符c的位置。如果成功找到指定的字符,该函数返回指向该字符的指针;否则返回NULL。 参数说明 str:要查…

    C 2023年5月9日
    00
  • C语言代码中调用C++代码的方法示例

    当我们在C语言中需要使用一些C++代码的时候,可以通过以下几个步骤实现: 编写C++代码 在C++中编写我们需要使用的函数或者类,注意要在代码中添加extern “C”修饰,使C++代码能够被C语言调用。例如,我们编写一个简单的C++函数: #include<iostream> using namespace std; extern "…

    C 2023年5月23日
    00
  • Dev-C++同时编译多个C或C++文件方法

    使用Dev-C++同时编译多个C或C++文件,需要进行如下步骤: 新建工程 打开Dev-C++,选择File -> New -> Project -> Console Application,点击“OK”按钮。在弹出的对话框中,输入项目名称和存储路径,点击“Next”按钮。 添加文件 在工程中,先新建一个主函数所在的.c或.cpp文件,然后…

    C 2023年5月23日
    00
  • VC6.0常用快捷键大全

    VC6.0常用快捷键大全 为什么需要快捷键? 在编程的过程中,我们需要频繁地进行复制、粘贴、撤销等操作。如果每次都使用鼠标进行操作,效率会非常低下。而快捷键的存在,可以极大地提高我们的工作效率。以下是VC6.0中的一些常用快捷键。 快捷键列表 常用快捷键 Ctrl + S 保存当前文件 Ctrl + C 复制选中内容 Ctrl + V 粘贴剪贴板内容 Ctr…

    C 2023年5月23日
    00
  • C++中的拷贝构造详解

    C++中的拷贝构造详解 什么是拷贝构造函数 拷贝构造函数是C++类中的一种构造函数,用于创建对象的副本。当原对象被传递给一个函数或以值的方式返回时,拷贝构造函数被调用来创建一个新的对象,该新对象是原对象的一个完全拷贝。拷贝构造函数的原型通常是:ClassName(const ClassName& obj);。 在某些情况下,编译器会自动生成拷贝构造函…

    C 2023年5月22日
    00
  • Java程序的逻辑控制和方法详解

    Java程序的逻辑控制和方法详解 什么是逻辑控制 在Java中,逻辑控制是指程序判断和执行语句的顺序、次数、循环和选择等。常用的逻辑控制语句有if、for、while等等。 if语句 if语句是最简单的逻辑控制语句,有条件地执行语句。if语句的基本格式为: if (condition) { statement(s) to be executed if con…

    C 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部