在NDK开发中难免会遇到C与C++混合编程,比如C调用C++写的so库或者C++调用C的函数库,如果不做特别处理,就会出现编译通过但链接时找不到函数或者压根就编译不通过的情况。
为什么会出现这种情况?
有两个原因:
1.C++比C出现的晚,在C++里出现了很多新的特性,比如类、命名空间等,是C不支持的,因为C不能向下兼容,比如在C程序中直接使用new关键字生成一个对象是不支持的。
2.C++支持函数重载,C不支持函数重载,在编译后,C++的函数名会被修改,而C的函数名基本上不变,由于两者在编译后函数名的命名策略不同,所以在不处理的情况下,C调用C++的函数或者C++调用C函数,都会在链接阶段报找不到函数的错误。
如何解决函数名找不到的问题
使用 extern "C" { }
作用:通知C++编译器将extern "C"所包含的代码按照C的方式编译和链接
注意:extern "C" 是在C++里增加的,C里面不能识别
有两种调用情况:
1.C++调用C函数
a.如果C函数在.h中声明,我们直接用extern "C"包含函数,比如这样:
/* file myCHead.h */ #ifndef MYCHEAD_H #define MYCHEAD_H extern "C" { int funC(); } #endif //MYCHEAD_H
那么C++调用该函数时,只需要引入myCHead.h即可:
/* file myCppSource.cpp */ #include "stdio.h" #include "myCHead.h" int main() { printf("funC:%d", funC()); return 0; }
这时候cpp里include进来的myCHead.h,因为funC()函数有 extern "C"包含,那么C++编译器不会对funC()函数名采用C++策略处理,而采用C编译器策略,而.c的代码NDK会默认采用C编译器进行编译,正因为函数的调用处与实现处都采用了相同的命名策略,故最后能正确链接。
接下来实现.c的程序:
/* file myCSource.c */ #include "myCHead.h" int funC() { return 100; }
这时候myCSource.c里也include了myCHead.h,那么问题出现了,如果我们把myCSource.c展开(因为 #include 的本质就是把对应的文件直接拷贝到这一行里面 ):
/* file myCSource.c */ extern "C" { int funC(); } int funC() { return 100; }
前面说过,extern "C"是C++里加入的,在C里面并不识别,所以这里在编译时就会报错
解决方案:使用 #ifdef __cplusplus,因为“__cplusplus”是C++中的自定义宏,C里面没有,所以当.h可能被C与C++同时使用时,又使用了extern "C",这时使用 #ifdef __cplusplus 来规定,如果是C的代码,就不需要使用extern "C",所以正确的 myCHead.h为:
/* file myCHead.h */ #ifndef MYCHEAD_H #define MYCHEAD_H #ifdef __cplusplus extern "C" { #endif int funC(); #ifdef __cplusplus } #endif #endif //MYCHEAD_H
在这里要明白,#ifdef __cplusplus主要为了防止extern "C"被C程序使用,假设头文件只被C++使用,那就不需要用 #ifdef __cplusplus。
b.如果没有 myCHead.h头文件,如何做
很简单,前面说了 #include 的本质就是把对应的文件直接拷贝到这一行里面,所以直接在C++程序中声明一下 funC()函数:
/* file myCppSource.cpp */ #include "stdio.h" extern "C" int funC(); int main() { printf("funC:%d", funC()); return 0; }
对编译器来说,其实质与引用头文件一样,还省了#ifdef __cplusplus
2.C调用C++程序
C调用C++程序比C++调用C要复杂一些,因为除了同样会有编译后函数命令策略不同的问题,还会有C不能向下兼容C++新特征的问题
a.C调用C++的全局函数(不在类里面)
此时C能直接调用C++的函数,但是如果不处理,同样会在链接时报找不到函数名的错误,所以还是要依赖extern "C"
因为extern "C" 是给C++使用的,目的就是告诉C++编译器,其函数命名规则采用C的方式,这样C调用了C++函数,在链接时就能找得到
所以C++的头文件可以这样(比如C++中增加一个funCPP()函数提供给C用):
C++头文件:
/* file myCPPHead.h */ #ifndef MYCPPHEAD_H #define MYCPPHEAD_H #ifdef __cplusplus extern "C" { #endif int funCPP(); #ifdef __cplusplus } #endif #endif //MYCPPHEAD_H
C++源程序:
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppHead.h" extern "C" int funC(); int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; }
注意:#include "myCppHead.h"一定不能少,因为这句话就是告诉C++编译器 函数funCPP()采用C的方式编译,否则C程序调用funCPP()会出错。
C增加调用C++函数:
/* file myCSource.c */ #include "myCHead.h" #include "myCppHead.h" int funC() { return 100; } int main() { printf("funCPP:%d", funCPP()); return 0; }
正因为myCppHead.h被C引用了,所以myCppHead.h里的 extern "C" , 外面要用#ifdef __clusplus包一下。
可以思考一下,如果C++没有提供的.h,C如何使用其中的全局函数?
很显然,可以在myCppSource.cpp源程序中,将提供给C调用的函数用 extern "C"进行修饰,C程序里用 extern int funCPP(); 声明一下即可。
再思考一下,如果C++里面有重载函数,如何被C调用?
比如 myCppSource.cpp变成:
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppSource.h" extern "C" int funC(); int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; } int funCPP(int value){ return value; }
然后C需要调用funCPP(int value)怎么办?
解决方法就是用另一个函数包装一下,然后将该函数提供给C调用:
改造后的myCppHead.h:
/* file myCPPHead.h */ #ifndef MYCPPHEAD_H #define MYCPPHEAD_H int funCPP(); int funCPP(int value); #ifdef __cplusplus extern "C" { #endif int funCPP1(); int funCPP2(int value); #ifdef __cplusplus } #endif #endif //MYCPPHEAD_H
改造后的myCppSource.cpp:
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppHead.h" extern "C" int funC(); int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; } int funCPP(int value){ return value; } int funCPP1() { return funCPP(); } int funCPP2(int value) { return funCPP(value); }
C调用funCPP(int value),改造后的myCSource.c:
/* file myCSource.c */ #include "myCHead.h" #include "myCppHead.h" int funC() { return 100; } int main() { //printf("funCPP:%d", funCPP()); printf("funCPP(int):%d", funCPP2(300)); return 0; }
最后再思考一下,如果C++提供的库,你没办法去修改头文件与源文件,向其中加上 extern "C",那么C如何调用其提供的函数?
也有解决办法:也是包装,可以增加一个新的cpp,里面使用包装函数调用C++库函数,然后将包装函数用 extern "C"修饰后,提供给C使用。
b.C调用C++的类函数
由于C不能向下兼容CPP的一些新特征,比如面向对象特征,所以C里不可能直接去new一个对象,再使用对象提供函数。我们前面有讲使用包装的方式间接调用重载函数的解决办法,正好在这里也可以用包装的方式。
C++里增加一个类:
/* file myCPPHead.h */ #ifndef MYCPPHEAD_H #define MYCPPHEAD_H int funCPP(); int funCPP(int value); class MyClass { int funCPP(); }; #ifdef __cplusplus extern "C" { #endif int funCPP1(); int funCPP2(int value); #ifdef __cplusplus } #endif #endif //MYCPPHEAD_H
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppHead.h" extern "C" int funC(); int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; } int funCPP(int value){ return value; } int funCPP1() { return funCPP(); } int funCPP2(int value) { return funCPP(value); } int MyClass::funCPP(){ return 400; }
请思考一下,上面这样改造后,可行否?
显然是不行的, 因为 myCSource.c里有一句
#include "myCppHead.h"
而myCppHead.h里有class的声明,编译时myCSource.c肯定会报错,如何解决?
两个办法,1、将MyClass的声明放到.cpp中;2、增加一个新的cpp来包装
如果采用第一种方法,将MyClass的声明放到.cpp中,再加上包装函数,则改造后的程序如下:
/* file myCPPHead.h */ #ifndef MYCPPHEAD_H #define MYCPPHEAD_H int funCPP(); int funCPP(int value); #ifdef __cplusplus extern "C" { #endif int funCPP1(); int funCPP2(int value); //以下供C调用对象的成员函数 void* createObject(); int funCPP3(void* object); #ifdef __cplusplus } #endif #endif //MYCPPHEAD_H
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppHead.h" extern "C" int funC(); class MyClass { int funCPP(); }; int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; } int funCPP(int value){ return value; } int funCPP1() { return funCPP(); } int funCPP2(int value) { return funCPP(value); } void* createObject(){ return new MyClass(); } int funCPP3(void* object){ MyClass* myClassObj = (MyClass*)object; return myClassObj->funCPP(); } int MyClass::funCPP(){ return 400; }
myCSource.c里调用,改造后:
/* file myCSource.c */ #include "myCHead.h" #include "myCppHead.h" int funC() { return 100; } int main() { //printf("funCPP:%d", funCPP()); //printf("funCPP(int):%d", funCPP2(300)); void *cppObj = createObject(); printf("class funCPP:%d", funCPP3(cppObj)); return 0; }
如果不许直接修改myCppHead.h与myCppSource.cpp,向里面增加包装函数,则采用
另一种方式:增加一个.h与.cpp或者只增加.cpp来进行包装:
myCppHead.h与myCppSource.cpp恢复成之前:
/* file myCPPHead.h */ #ifndef MYCPPHEAD_H #define MYCPPHEAD_H int funCPP(); int funCPP(int value); #ifdef __cplusplus extern "C" { #endif int funCPP1(); int funCPP2(int value); #ifdef __cplusplus } #endif #endif //MYCPPHEAD_H
/* file myCppSource.cpp */ #include "stdio.h" #include "myCppHead.h" extern "C" int funC(); class MyClass { int funCPP(); }; int main() { printf("funC:%d", funC()); return 0; } int funCPP(){ return 200; } int funCPP(int value){ return value; } int funCPP1() { return funCPP(); } int funCPP2(int value) { return funCPP(value); } int MyClass::funCPP(){ return 400; }
新增的.h与.cpp:
/* file myWrapperCppHead.h */ #ifndef MYWRAPPERCPPHEAD_H #define MYWRAPPERCPPHEAD_H #ifdef __cplusplus extern "C" { #endif void* createObjectFromWrapper(); int funCPP3FromWrapper(void* object); #ifdef __cplusplus } #endif #endif //MYWRAPPERCPPHEAD_H
/* file myWrapperCppSource.cpp */ #include "myWrapperCppHead.h" #include "myCppHead.h" void* createObjectFromWrapper(){ return new MyClass(); } int funCPP3FromWrapper(void* object){ MyClass* myClassObj = (MyClass*)object; return myClassObj->funCPP(); }
C调用成员函数 新的方式:
/* file myCSource.c */ #include "myCHead.h" #include "myWrapperCppHead.h" int funC() { return 100; } int main() { //printf("funCPP:%d", funCPP()); //printf("funCPP(int):%d", funCPP2(300)); void *cppObj = createObjectFromWrapper(); printf("user wrapper class funCPP:%d", funCPP3FromWrapper(cppObj)); return 0; }
最后总结一下:
1.C++中增加了extern "C" 关键字来告诉C++编译器,函数命名采用C的方式
2.extern "C" 只能在C++中使用,了为防止头文件中存在extern "C" ,而该头文件可能会被C源程序include,可以采用 #ifdef __cplusplus 对 extern "C" 进行限制
3.C++能调用C函数的关键就是在C++里需要声明C函数,且采用 extern "C" 修饰
4.C能调用C++非成员函数的关键就是 在C++中,C++函数需要使用 extern "C" 修饰
5.如果C++头文件或源文件无法修改,使其加上 extern "C" 修饰时,可以增加一个新的包装cpp,包装cpp里采用包装函数直接调用目标函数,包装函数使用 extern "C" 修饰后供C使用
6.C调用C++成员函数,需要使用包装函数
作者:会上网的井底之蛙
链接:https://www.jianshu.com/p/49859d267567
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。