?????? 在NDK開發(fā)中難免會遇到C與C++混合編程以故,比如C調(diào)用C++寫的so庫或者C++調(diào)用C的函數(shù)庫榔昔,如果不做特別處理收奔,就會出現(xiàn)編譯通過但鏈接時找不到函數(shù)或者壓根就編譯不通過的情況。
?????? 為什么會出現(xiàn)這種情況毫捣?
有兩個原因:
1.C++比C出現(xiàn)的晚详拙,在C++里出現(xiàn)了很多新的特性,比如類蔓同、命名空間等饶辙,是C不支持的,因為C不能向下兼容斑粱,比如在C程序中直接使用new關鍵字生成一個對象是不支持的弃揽。
2.C++支持函數(shù)重載,C不支持函數(shù)重載则北,在編譯后矿微,C++的函數(shù)名會被修改,而C的函數(shù)名基本上不變尚揣,由于兩者在編譯后函數(shù)名的命名策略不同涌矢,所以在不處理的情況下,C調(diào)用C++的函數(shù)或者C++調(diào)用C函數(shù)快骗,都會在鏈接階段報找不到函數(shù)的錯誤娜庇。
如何解決函數(shù)名找不到的問題
????????? 使用 extern "C" { }
????????? 作用:通知C++編譯器將extern? "C"所包含的代碼按照C的方式編譯和鏈接
?????????? 注意:extern "C" 是在C++里增加的,C里面不能識別
有兩種調(diào)用情況:
1.C++調(diào)用C函數(shù)
??? a.如果C函數(shù)在.h中聲明方篮,我們直接用extern "C"包含函數(shù)名秀,比如這樣:
/* file myCHead.h */
#ifndef MYCHEAD_H
#define MYCHEAD_H
extern "C" {
int funC();
}
#endif //MYCHEAD_H
那么C++調(diào)用該函數(shù)時,只需要引入myCHead.h即可:
/* file myCppSource.cpp */
#include "stdio.h"
#include "myCHead.h"
int main()
{
printf("funC:%d", funC());
return 0;
}
這時候cpp里include進來的myCHead.h恭取,因為funC()函數(shù)有 extern "C"包含泰偿,那么C++編譯器不會對funC()函數(shù)名采用C++策略處理,而采用C編譯器策略蜈垮,而.c的代碼NDK會默認采用C編譯器進行編譯,正因為函數(shù)的調(diào)用處與實現(xiàn)處都采用了相同的命名策略裕照,故最后能正確鏈接攒发。
接下來實現(xiàn).c的程序:
/* file myCSource.c */
#include "myCHead.h"
int funC()
{
return 100;
}
這時候myCSource.c里也include了myCHead.h,那么問題出現(xiàn)了晋南,如果我們把myCSource.c展開(因為 #include 的本質(zhì)就是把對應的文件直接拷貝到這一行里面 ):
/* 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 來規(guī)定董虱,如果是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 的本質(zhì)就是把對應的文件直接拷貝到這一行里面,所以直接在C++程序中聲明一下 funC()函數(shù):
/* file myCppSource.cpp */
#include "stdio.h"
extern "C" int funC();
int main()
{
printf("funC:%d", funC());
return 0;
}
對編譯器來說昏滴,其實質(zhì)與引用頭文件一樣猴鲫,還省了#ifdef?__cplusplus
2.C調(diào)用C++程序
?????? C調(diào)用C++程序比C++調(diào)用C要復雜一些,因為除了同樣會有編譯后函數(shù)命令策略不同的問題影涉,還會有C不能向下兼容C++新特征的問題
a.C調(diào)用C++的全局函數(shù)(不在類里面)
?? 此時C能直接調(diào)用C++的函數(shù)变隔,但是如果不處理,同樣會在鏈接時報找不到函數(shù)名的錯誤蟹倾,所以還是要依賴extern "C"
??? 因為extern "C" 是給C++使用的匣缘,目的就是告訴C++編譯器,其函數(shù)命名規(guī)則采用C的方式鲜棠,這樣C調(diào)用了C++函數(shù)肌厨,在鏈接時就能找得到
??? 所以C++的頭文件可以這樣(比如C++中增加一個funCPP()函數(shù)提供給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++編譯器 函數(shù)funCPP()采用C的方式編譯豁陆,否則C程序調(diào)用funCPP()會出錯柑爸。
C增加調(diào)用C++函數(shù):
/* 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如何使用其中的全局函數(shù)譬圣?
???? ?很顯然,可以在myCppSource.cpp源程序中雄坪,將提供給C調(diào)用的函數(shù)用??extern "C"進行修飾厘熟,C程序里用 extern int funCPP(); 聲明一下即可。
再思考一下维哈,如果C++里面有重載函數(shù)绳姨,如何被C調(diào)用?
比如 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需要調(diào)用funCPP(int value)怎么辦阔挠?
解決方法就是用另一個函數(shù)包裝一下飘庄,然后將該函數(shù)提供給C調(diào)用:
改造后的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調(diào)用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如何調(diào)用其提供的函數(shù)切揭?
???? 也有解決辦法:也是包裝狞甚,可以增加一個新的cpp,里面使用包裝函數(shù)調(diào)用C++庫函數(shù)廓旬,然后將包裝函數(shù)用 extern "C"修飾后哼审,提供給C使用。
b.C調(diào)用C++的類函數(shù)
??????? 由于C不能向下兼容CPP的一些新特征孕豹,比如面向?qū)ο筇卣魃埽訡里不可能直接去new一個對象,再使用對象提供函數(shù)励背。我們前面有講使用包裝的方式間接調(diào)用重載函數(shù)的解決辦法春霍,正好在這里也可以用包裝的方式。
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中肋杖,再加上包裝函數(shù)次舌,則改造后的程序如下:
/* 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調(diào)用對象的成員函數(shù)
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里調(diào)用,改造后:
/* 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兽愤,向里面增加包裝函數(shù),則采用
另一種方式:增加一個.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調(diào)用成員函數(shù) 新的方式:
/* 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;
}
最后總結(jié)一下:
1.C++中增加了extern "C" 關鍵字來告訴C++編譯器挪圾,函數(shù)命名采用C的方式
2.extern "C" 只能在C++中使用浅萧,了為防止頭文件中存在extern "C" ,而該頭文件可能會被C源程序include哲思,可以采用 #ifdef?__cplusplus 對 extern "C" 進行限制
3.C++能調(diào)用C函數(shù)的關鍵就是在C++里需要聲明C函數(shù)洼畅,且采用 extern "C" 修飾
4.C能調(diào)用C++非成員函數(shù)的關鍵就是 在C++中,C++函數(shù)需要使用 extern "C" 修飾
5.如果C++頭文件或源文件無法修改棚赔,使其加上 extern "C" 修飾時帝簇,可以增加一個新的包裝cpp徘郭,包裝cpp里采用包裝函數(shù)直接調(diào)用目標函數(shù),包裝函數(shù)使用 extern "C" 修飾后供C使用
6.C調(diào)用C++成員函數(shù)丧肴,需要使用包裝函數(shù)