1 C++中调用C的接口
我们在阅读一些库的代码的时候, 经常看到有些函数被extern “C”来修饰
1.1 extern “C”引入C的库代码
如下所示
extern "C" void func();
- 1
如果需要修饰的函数比较多, 则使用如下方式
#ifdef __cplusplus extern "C" { #endif ///////////////////// // 一段代码 ///////////////////// #ifdef __cplusplus } #endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
下面详细说明此段代码的意义:
__cplusplus是c++编译器(如g++等)定义的宏, 如果是c++调用的话, extern "C"声明会有效. 如果时c调用的话, 那么, extern “C”`声明无效
要明白为何使用extern “C”, 还得从cpp中对函数的重载处理开始说起.
在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理, 加入比如函数的返回类型等等. 而在C中, 只是简单的函数名字而已, 不会加入其他的信息.
也就是说 : C++和C在编译后对产生的函数名字的处理是不一样的. 而上面的代码extern "C"目的就是主要实现C与C++的相互调用问题.
对于C++编译器, 由于__cplusplus宏被定义, 因此通过extern "C"来通知C++编译器编译后的代码是按照C的obj文件格式编译的,要连接的话按照C的命名规则去找.
这种方法有两种妙用
在C源代码中使用extern “C”这样代码及时添加到C++的项目工程中, 也可以正常的被编译和链接
多数情况下我们C的库都是SDK(包括头文件和lib包), 没有源代码, 那么在我们的C++代码中使用extern “C”就通知编译器我们引入了C库的代码
1.2 示例程序
下面我们通过一个示例来看看C++中如果调用C的函数, 代码在language/c/cpp/cpp_link_c
我们在add.c中定义了一个add函数, 这个函数是C语言实现的函数接口
// add.c #include #include int add(const int a, const int b) { return (a + b); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我们C++中在main函数调用C语言实现的add函数
// main.cpp #include using namespace std; #ifdef __cplusplus extern "C" { #endif int add(const int a, const int b); #ifdef __cplusplus } #endif int main( ) { std::cout <
- 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
下面是我们的Makefile信息, 我们生成了两个可执行程序main_normal和main_sdk
main_normal为main.o和add.o直接编译生成, C++中通过extern “C”直接以源码的方式生成了main_normal
main_sdk类似于我们开发的方式, 首先用add.c生成了一个sdk库libadd.so, 然后main.c中通过extern “C”以C的方式链接了libadd.so中的add函数, 生成了main_sdk
# the compile options CFLAGS = -Wall -std=gnu99 -O2 -pedantic -Wextra -g CXXFLAGS = -Wall -std=c++11 -O2 -pedantic -Wextra -g SHAREDLIB_LINK_OPTIONS = -shared FPIC = -fPIC # the include directory INC = -I./ target=main_sdk main_normal libadd.so all:$(target) main_sdk : main.o libadd.so $(CXX) $^ -o $@ -L./ -ladd main_normal : main.o add.o $(CXX) $^ -O $@ libadd.so : add.o $(CC) $(SHAREDLIB_LINK_OPTIONS) $(FPIC) $(LDFLAGS) $^ -o $@ #libmyclass.a:myclass.o func.o # ar crv $@ $^ %.o : %.cpp $(CXX) $(FPIC) $(CXXFLAGS) -c $^ -o $@ $(INC) %.o : %.c $(CC) $(FPIC) $(CFLAGS) -c $^ -o $@ $(INC) clean : rm -rf *.o rm -rf $(target)
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
由于C++是高度兼容C的, 只需要通知C++编译器按照C的命名方式编译和链接二进制代码即可, 除了编译命名的处理不需要额外的层次, 因此这种方式很好的解决了, C++代码中编译链接C源代码的问题
但是C语言却不支持C++面向对象的特性, 这个问题该怎么解决啊.
C调用C++的函数接口信息
前面我们讲解了, C++是一个C基础上扩展的支持面向对象的高级语言, 因此我们将C调用C++的函数的方法分为面向过程和面向对象两种特性分开讨论.
C中调用C++中基本的数据和成员(面向过程的数据)
C中调用C++中类成员数据(面向对象的数据)
2 C中调用C++ 的接口
C++面向过程的部分是完全兼容C的, 因此其本质上俊只是编译阶段的处理不同而已, 但是C++也引入了一些新的特性, 比如函数重载等, 这些需要我们单独去兼容.
2.1 C中调用C++数据和成员(面向过程的数据)
2.1.1 基本函数的处理
这部分C与C++是完全兼容的, 没有区别, 因此使用extern “C”的方式就足以处理.
将C++函数声明为”extern “C”(在你的C++代码里做这个声明), 然后调用它(在你的C或者C++代码里调用).
例如:
我们有add.cpp做出的一套C++的库接口, 其中包含add函数接口, 但是这套接口是C++的, 我们想要在C程序中使用这个C++的库接口, 该如何实现呢
我们同样以一段示例来展示, 参见language/c/cpp/c_link_cpp_func
首先是我们的C++库的源代码
// add.cpp int add(const int a, const int b) { return (a + b); }
- 1
- 2
- 3
- 4
- 5
我们想要在C程序中使用这个函数接口, 但是C++并不兼容C的接口, 考虑我们可以通过增加一个中间层来实现, 进行一次封装, 将C++的库封装成C编译器可识别的形式
中间层libadd.cpp的形式如下, 其实就是用C++编译器编译出一套C编译器可识别的代码, 同样是通过extern "C"来实现, 将add函数封装成call_cpp_add函数
// libadd.cpp int add(const int a, const int b); #ifdef __cplusplus extern "C" { #endif int call_cpp_add(const int a, const int b) { return add(a, b); } #ifdef __cplusplus } #endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
那这样以来call_cpp_add函数虽然用C++编译器编译, 但是编译成C编译器可识别的格式, 我们就可以在C源程序main中调用C编译器可以识别的call_cpp_add函数.
// main.c #include #include int call_cpp_add(const int a, const int b); int main( ) { printf("%d\n", call_cpp_add(2, 4)); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
下面是Makefile的信息
# the compile options CFLAGS = -Wall -std=gnu99 -O2 -pedantic -Wextra -g CXXFLAGS = -Wall -std=c++11 -O2 -pedantic -Wextra -g SHAREDLIB_LINK_OPTIONS = -shared FPIC = -fPIC # the include directory INC = -I./ target=main_sdk libadd.so all:$(target) main_sdk : main.o libadd.so $(CC) $^ -o $@ -L./ -ladd -lstdc++ libadd.so : libadd.o add.o $(CXX) $(SHAREDLIB_LINK_OPTIONS) $(FPIC) $(LDFLAGS) $^ -o $@ %.o : %.cpp $(CXX) $(FPIC) $(CXXFLAGS) -c $^ -o $@ $(INC) %.o : %.c $(CC) $(FPIC) $(CFLAGS) -c $^ -o $@ $(INC) clean : rm -rf *.o rm -rf $(target)
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
2.1.2 C语言调用C++重载函数的处理
C++支持函数重载的, 函数名相同但是参数不同的重载函数在编译后链接的名字并不相同而可以被识别, 这种情况下, 我们引入一个中间层的方法同样可以实现C中调用C++的函数接口, 其实现与上一节C中调用C++非重载基本函数成员的实现没有什么区别, 只是为各个重载函数均实现一套接口而已
我们仍然以一个示例来展示, 代码参见