文章目录
extern “C”
vs下实现静态库:
- 包含对应目录下的头文件
- 在工程属性中配置静态库目录(链接器的常规中),添加静态库(链接器的输入中)
Cpp调C:在Cpp的#include"…/xxx/xx.h"上下加上extern “C”{}
extern "C"
{
#include"../xx/Stack.h"
}
C调Cpp:在Cpp的.h文件中加extern “C”{ 函数},Cpp静态库就会按照C的规则去处理以下函数。当然重载就要写两个函数了。
特别注意,因为.c包含了.h的头文件,所以头文件包含会将.h部分在c展开,而cpp才认识extern “C”,C部分不认识extern “C”;
- 第一种做法
#ifdef __cplusplus
extern "C" {
#endif
cpp函数声明;
#ifdef __cplusplus
}
#endif
引入条件编译,在cpp库中,是识别extern "C"并且按照C的方式进行函数推导。当C对其头文件展开的时候,由于不是cpp,条件编译直接声明成C函数。
- 第二种做法
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif
EXTERN_C cpp函数声明;
EXTERN_C cpp函数声明;
EXTERN_C cpp函数声明;
也就是说extern “C” 总是在cpp中的,因为只有cpp 认识 extern " C"
C++程序调用C的库,在C++程序中加extern “C”
C程序调用C++的库,在C++库中加extern “C”
1.C++程序中调用C库
首先为什么C++程序中不能调用C库,会产生链接错误。因为两者对函数名字的命名规则不同,因此C++的链接器会去C模块中查找对应函数,但是找不到。
那我们怎么在C++项目中使用C库模块的?
//util.h
extern "C"
{
int add(int ,int );
}
通过extern “C”,告诉g++编译器,不要对这些函数进行Name mangling,按照C编译器的方式去生成符号表符号。这样在main.c的目标文件(.o)中,参数列表为两个int类型的add函数名称为_add。链接器可以正确找到util.o中的add函数(他们都是_add)。
不过注意参数列表为两个double类型的add函数名称还是__Z3adddd。
使用 extern ”C“ 的常见情况是使用第三方提供的编译好的静态链接库(.a/.lib),动态链接库(.so/.dll)。通常我们会拿到一个头文件和对应的编译好的库文件。
在头文件中通过条件编译引入 extern “C”。
//until.h
#ifdef __cplusplus
extern "C" {
#endif
int add(int, int);
#ifdef __cplusplus
}
#endif
gcc -c xxx.c
ar -rc libxxxx.a xxx.o xxx.o
test-static:test.cc
g++ -o $@ $^ -I ./mylib/include -L ./mylib/lib -l util -static
.PHONY:clean
clean:
rm -rf *.o test-static
2.C程序中调用C++函数
假设我们有一个C++类 Robot,在文件 robot.h 和 robot.cpp 中定义。Robot 类中有个成员函数 sayHi() 我们想在C程序中调用这个函数。
robot.h
#pragma once
#include <string>
class Robot
{
public:
Robot(std::string name) : name_(name) {}
void sayHi();
private:
std::string name_;
};
robot.cpp
#include <iostream>
#include "robot.h"
void Robot::sayHi()
{
std::cout << "Hi, I am " << name_ << "!\n";
}
我们用编译C++代码的方式,使用 g++ 编译器对这个类进行编译,此时类 Robot 并不知道自己会被C程序调用。
g++ -fpic -shared robot.cpp -o librobot.so
接下来用C++创建一个C的接口,定义在 robot_c_api.h 和 robot_c_api.cpp 中,这个接口会定义一个C函数 Robot_sayHi(const char *name), 这个函数会创建一个类 Robot 的实例,并调用 Robot 的成员函数 sayHi()。
robot_c_api.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void Robot_sayHi(const char *name);
#ifdef __cplusplus
}
#endif
robot_c_api.cpp
#include "robot_c_api.h"
#include "robot.h"
#ifdef __cplusplus
extern "C" {
#endif
// 因为我们将使用C++的编译方式,用g++编译器来编译 robot_c_api.cpp 这个文件,
// 所以在这个文件中我们可以用C++代码去定义函数 void Robot_sayHi(const char *name)(在函数中使用C++的类 Robot),
// 最后我们用 extern "C" 来告诉g++编译器,不要对 Robot_sayHi(const char *name) 函数进行name mangling
// 这样最终生成的动态链接库中,函数 Robot_sayHi(const char *name) 将生成 C 编译器的符号表示。
void Robot_sayHi(const char *name)
{
Robot robot(name);
robot.sayHi();
}
#ifdef __cplusplus
}
#endif
同样用编译C++代码的方式进行编译
g++ -fpic -shared robot_c_api.cpp -L. -lrobot -o librobot_c_api.so
现在我们有了一个动态链接库 librobot_c_api.so, 这个动态链接库提供了一个C函数 Robot_sayHi(const char *name),我们可以在C程序中调用它了。
main.c
#include "robot_c_api.h"
int main()
{
Robot_sayHi("Alice");
Robot_sayHi("Bob");
return 0;
}
使用C程序的编译方式,用 gcc 对 main.c 进行编译
gcc main.c -L. -lrobot_c_api
可以看到 gcc 编译出的函数符号和 librobot_capi.so中 g++ 编译器编译出的函数符号一致。这样最终在我们的C程序中可以正确的链接到动态库中的Robot_sayHi(const char *name) 函数。
但是上述的做法并没有扩展到makefile
,下面演示一下makefile
编译动态库进行extern"C“的使用。下图中的代码内容和上一块内容的代码内容是一样的。
注意这个.c文件
一定要用g++
编译,不然识别不了c++的头文件,导致折腾了很久。
test:test.c
g++ -o $@ $^ -I ./mylib -L ./mylib -l robot_c_api
.PHONY:clean
clean:
rm -rf test
librobot_c_api.so:robot.o robot_c_api.o
g++ -shared -o $@ $^
robot_c_api.o:robot_c_api.cc
g++ -fPIC -c $<
robot.o:robot.cc
g++ -fPIC -c $<
.PHONY:clean
clean:
rm -rf *.o mylibrobot librobot_c_api.so librobot.so output
.PHONY:output
output:
mkdir -p ./output/include
mkdir -p ./output/lib
cp ./*.so ./output/lib
cp ./*.h ./output/include
export LD_LIBRARY_PATH=/home/ycb/demo1/back-Cpp-C-extern/Cpp-C-extern/Cpp-C-extern/mylib