C/C++: 如何相互调用

C++ 中调用 C 比较简单. 但是 C 调用 C++ 稍微复杂一些.

C 调用 C++ 分为可以调用 C++ 类中的函数和普通 cpp 中的函数. 无论是哪种函数, 我们都可以使用封装了 C++ 的文件作为适配供给 C 来使用.

下面看具体的例子.

C++ 调用 C

首先创建 CFile.h 和 CFile.c 文件.

CFile.h

1
2
3
4
5
6
7
8
#ifndef CFile_h
#define CFile_h
#include <stdio.h>
extern void start_c(int cmd);
#endif /* CFile_h */

CFile.c

1
2
3
4
5
6
#include "CFile.h"
void start_c(int cmd) {
printf("start_c by cmd: %i\n", cmd);
}

在 C++ 文件中调用 C 代码, 示例如下:

main.cpp

1
2
3
4
5
6
7
8
9
10
extern "C" {
#include "CFile.h"
}
int main(int argc, const char * argv[]) {
start_c(1);
return 0;
}

这里可以看到导入 C 文件的方式:

1
2
3
extern "C" {
#include "CFile.h"
}

如果直接导入, 如:

1
2
3
4
5
6
7
8
#include "CFile.h"
int main(int argc, const char * argv[]) {
start_c(1);
return 0;
}

编译报错:

1
2
3
4
Undefined symbols for architecture x86_64:
"start_c(int)", referenced from:
_main in main.o
ld: symbol(s) not found for architecture x86_64

这里的 extern "C" 告诉编译器, 要按照 C 的链接约定,而不是 C++ 的链接约定.

C 编译器不支持 extern "C".

C 调用普通 C++ 的函数

C 调用 C++ 有点曲折.

CPPFile.hpp

1
2
3
4
5
6
#ifndef CPPFile_hpp
#define CPPFile_hpp
void start_cpp(int cmd);
#endif /* CPPFile_hpp */

CPPFile.cpp

1
2
3
4
5
6
7
8
9
#include "CPPFile.hpp"
#include <iostream>
using namespace std;
void start_cpp(int cmd) {
cout << "start_cpp by cmd: " << cmd << endl;
}

这里需要写一个中间的 cpp(CPPAdapter.cpp)文件, 提供方法给 C 来使用.

注意: 这里没有 CPPAdapter.hpp 头文件.

CPPAdapter.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "CPPFile.hpp"
#ifdef __cplusplus
extern "C" {
#endif
void adapter_start_cpp(int cmd) {
//调用 CPPFile 中的方法
start_cpp(cmd);
}
#ifdef __cplusplus
}
#endif

然后在 C 中调用 C++ 的代码:

CFile.c

1
2
3
4
5
6
7
8
9
10
#include "CFile.h"
//声明函数
extern void adapter_start_cpp(int cmd);
void start_c(int cmd) {
//调用 c++ 代码
adapter_start_cpp(5);
}

C 调用 C++ 类中的方法

和上面例子的原理一样的.

CPPClassFile.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef CPPClassFile_hpp
#define CPPClassFile_hpp
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
public:
Person();
~Person();
int setName(string name);
};
#endif /* CPPClassFile_hpp */

CPPClassFile.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "CPPClassFile.hpp"
Person::Person() {
cout << "Person()" << endl;
}
Person::~Person() {
cout << "~Person()" << endl;
}
int Person::setName(string name) {
this->name = name;
cout << "Set name: " << name << endl;
return 0;
}

CPPAdapter.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "CPPFile.hpp"
#include "CPPClassFile.hpp"
#ifdef __cplusplus
extern "C" {
#endif
void adapter_start_cpp(int cmd) {
//调用 CPPFile 中的方法
start_cpp(cmd);
}
int adapter_set_name(const char *cName) {
Person *person = new Person();
int ret = person->setName(cName);
delete person;
return ret;
}
#ifdef __cplusplus
}
#endif

在 C 中可以调用了, 如下代码:

CFile.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "CFile.h"
///声明 CPPFile 中的方法
extern void adapter_start_cpp(int cmd);
///声明 CPPClassFile 中的方法
extern int adapter_set_name(const char *cName);
void start_c(int cmd) {
printf("start_c by cmd: %i\n", cmd);
//调用 CPPFile 中的方法
adapter_start_cpp(5);
//调用 CPPClassFile 中的方法
adapter_set_name("www.veryitman.com");
}

extern “C”

extern "C" 中的 “C” 并不表示 C 语言,”C” 表示的是一种链接约定.

C 和 C++ 语言之间的密切关系而在它们之间更多的应用而已.

extern "C" 指令描述的是一种链接约定,它并不影响调用函数的定义,即使做了该声明,对函数类型的检查和参数转换仍要遵循 C++ 的标准,而不是 C 的标准.

不同的编程语言(编译型)链接特性是不同的,这也决定了它们编译后的链接符号的不同.

如函数 void function(int d),C 语言会把它编译成类似 _function 这样的符号,C 链接器只要找到该函数符号就可以链接成功.

C++ 会把这个函数编译成类似 _function_int_xxx_functionIntxxx 这样的符号,即在符号上增加了类型信息,这也解释了为什么 C++ 可以实现函数重载了.

那么,对于用 C 编译器编译成的库,用 C++ 直接链接势必会出现不能识别符号的问题,用 extern "C" 就可以解决, 正如上面的例子.

简单来说, extern "C" 的作用就是让编译器知道要以 C 语言的方式编译和链接函数.

__cplusplus 宏

__cplusplus 宏是 C++ 编译器默认定义的.

类似如下的代码:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C"{
#endif
void fun(int, size_t);
#ifdef __cplusplus
}
#endif

在 C++ 中, 编译器将 fun 按照 C 的链接约定来编译, 而如果是 C 编译器, 直接按照 C 的链接约定来编译即可.

__cplusplus 是在 C++ 编译器中默认定义的,C语言不支持 extern “C”.

上面的代码很实用, 也是一种编程技巧.

坚持原创技术分享,您的支持将鼓励我继续创作!