C与C++混合编程引入
【1】为什么需要混合编程
- C有很多优秀成熟项目和库,丢了可惜,重写没必要,C++程序里要调用
- 庞大项目划分后一部分适合用C,一部分适合用C++
- 其他情况,如项目组一部分人习惯用C,一部分习惯用C++
【2】为什么不同语言可以混合编程
- 程序编译过程:源文件->目标(库)文件->可执行程序->镜像文件
- 任何编程语言执行时都必须是可执行程序,所以都必须先被编译成目标文件
- 混合编程的“混合”操作发生在链接这一步
【3】C++和C混合编程的困难
- C++和C都是编译型语言,互相混合相对容易
- 难点:C++支持函数名重载,而C不支持,因此编译器生成目标文件时,函数名在目标文件中的临时内部名称规则不同。导致链接时符号对不上
- 解决方案:使用extern “C”{}; 让C++在对接的局部向C妥协兼容
【4】使用objdump工具来研究函数编译后的符号
- 写个典型的C语言库clib.c和clib.h;提供简单的add函数
- 使用gcc -c -o编译得到库文件,再objdump -d反汇编得到.i文件
- 对比加不加extern "C"这2种情况下得到的.i文件的符号差异
实验第1步:证明了C语言中名称为add的函数,编译后符号表中就叫add
实验第2步:证明了C++语言中名称为add的函数,编译后符号表中叫_Z3addii
同样的源码,编译后生成的二进制代码其实是一样的,所以功能其实也是一样的所以本质上是可以混合编程的,但是生成的中间符号名称不同,所以链接器难受。
实验第3步:证明了在C++的头文件中,只要把C++的函数的声明放在extern “C”{}的大括号范围之内,就可以让g++在编译这个函数时生成中间符号名时按照C的规则而不是按照C++的规则,所以这样的函数就可以和C的库进行共同链接。
C与C++混合编程的可能情况分析解决
【1】实际开发中会有以下三种情况
- 同一个项目全部有源码即C源码和C++源码,一次编译链接。
- 同一个项目中C是库,C++是源码,C++调用C
- 同一个项目中C++是库,C是源码,C调用C++
1.C和C++都有源码的情况
【1】现有clib.c,clib.h,main.cpp,c和c++都有源码,并且在main.cpp中调用clib.c的函数
/*------clb.c-----------*/
#include "clib.h"
int add(int a, int b)
{
return a + b;
}
/*------clb.h-----------*/
#ifndef __CLB_H__
#define __CLB_H__
#ifdef __cplusplus
extern "C"
{
#endif
//函数声明
int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
//CPP文件中调用C的函数
/*------main.cpp--------*/
#include "clib.h"
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int x = 3, y = 5, ret = 0;
ret = add(x,y);
cout << "ret = " << ret <<endl;
return 0;
}
【2】解决办法1:在C的头文件中加extern "C"声明,全部使用g++编译,可行,不推荐。
【3】解决办法2:在C的头文件中加extern "C"声明,先用gcc编译C文件生成.o文件再用g++编译生成最终执行文件。
2.C++调用C库的情况
【1】这种是最典型的常见情况,C库一般都包含头文件
【2】】现模拟用clib.c制作成静态库libclib.a,提供clib.h,main.cpp,并且在main.cpp中调用clib.c的函数
/*------clb.c-----------*/
#include "clib.h"
int add(int a, int b)
{
return a + b;
}
- 制作静态库
/*------clb.h-----------*/
#ifndef __CLB_H__
#define __CLB_H__
//函数声明
int add(int a, int b);
#endif
//CPP文件中调用C的函数
/*------main.cpp--------*/
#include "clib.h"
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int x = 3, y = 5, ret = 0;
ret = add(x,y);
cout << "ret = " << ret <<endl;
return 0;
}
【3】通用解决方案1:在C的头文件中加extern "C"声明,在C++中直接包含头文件调用即可
/*------clb.h-----------*/
#ifndef __CLB_H__
#define __CLB_H__
#ifdef __cplusplus
extern "C"
{
#endif
//函数声明
int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
【4】解决方案2:在CPP文件中,包含C文件的头文件前加extern "C"声明
//CPP文件中调用C的函数
/*------main.cpp--------*/
#ifdef __cplusplus
extern "C"
{
#endif
#include "clib.h"
#ifdef __cplusplus
}
#endif
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int x = 3, y = 5, ret = 0;
ret = add(x,y);
cout << "ret = " << ret <<endl;
return 0;
}
【5】用g++编译连接,成功执行
3.C调用C++库的情况
【1】C调用C++的麻烦
- g++和gcc的编译时符号差异
- c++支持很多c并不支持的特性,如函数重载
- 解决方案:添加一层封装层
【2】代码实战:C调用C++库中的函数
- 用cpp写一个库,cppadd.cpp cppadd.hpp,用g++编译成静态库
/*--------cppadd.cpp---------*/
#include "cppadd.hpp"
int add(int a, int b)
{
return a + b;
}
/*--------cppadd.hpp---------*/
#ifndef __CPPADD_HPP__
#define __CPPADD_HPP__
//函数声明
int add(int a, int b);
#endif
-
objdump反编译库,查看确认符号
-
用cpp写一个封装层,用上extern “C”,cppaddwrapper.cpp和cppaddwrapper.hpp,用g++编译成静态库
/*---------cppaddwrapper.hpp-----------*/
#ifndef __CPPADDWRAPPER_HPP__
#define __CPPADDWRAPPER_HPP__
#ifdef __cplusplus
extern "C"
{
#endif
int addwrapper(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
/*---------cppaddwrapper.cpp-----------*/
#include "cppaddwrapper.hpp"
#include "cppadd.hpp" //要包含库的头文件
int addwrapper(int a, int b) //中间层的函数参数要和要调用的函数的参数一致
{
add(a,b);
}
//把封装层也要做成库
g++ cppadd_wrapper.cpp -c -o cppaddwrapper.o -lcppadd -L.
ar -r libcppaddwrapper.a libcppaddwrapper.o
-
objdump反编译库,查看确认符号
-
用c写一个test.c,调用我们制作的中间层库,用gcc编译链接,运行查看结果
/*---------test.c-----------*/
#include "cppaddwrapper.hpp"
#include <stdio.h>
int main(int argc, char const *argv[])
{
int x = 4, y = 5, ret = 0;
ret = addwrapper(x,y);
printf("ret = %d\n", ret);
return 0;
}