0
点赞
收藏
分享

微信扫一扫

linux下extern“C”的使用

IT影子 2022-01-15 阅读 27

文章目录

extern “C”

vs下实现静态库:

  1. 包含对应目录下的头文件
  2. 在工程属性中配置静态库目录(链接器的常规中),添加静态库(链接器的输入中)

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 

image-20220114183816152

image-20220114193347352

2.C程序中调用C++函数

假设我们有一个C++类 Robot,在文件 robot.hrobot.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.hrobot_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

img

现在我们有了一个动态链接库 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程序的编译方式,用 gccmain.c 进行编译

gcc main.c -L. -lrobot_c_api

img

可以看到 gcc 编译出的函数符号和 librobot_capi.sog++ 编译器编译出的函数符号一致。这样最终在我们的C程序中可以正确的链接到动态库中的Robot_sayHi(const char *name) 函数。

img


但是上述的做法并没有扩展到makefile,下面演示一下makefile编译动态库进行extern"C“的使用。下图中的代码内容和上一块内容的代码内容是一样的。

image-20220115133443894

注意这个.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

image-20220115133129475

举报

相关推荐

0 条评论