NDK开发中C与C++互相调用处理

       在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++成员函数,需要使用包装函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容