NDK開発におけるCとC++の相互呼び出し処理

12301 ワード

NDKの開発では、CとC++の混合プログラミング、例えばCがC++を呼び出して書くsoライブラリやC++がCを呼び出す関数ライブラリに遭遇することは避けられない.特別な処理をしないと、コンパイルは通過するが、リンク時に関数が見つからないか、根こそぎコンパイルが通過しない場合がある.
どうしてこんなことになったの?
2つの理由があります.
1.C++はCより遅く、クラスやネーミングスペースなど、Cにはサポートされていません.Cは下に互換性がないため、Cプログラムでnewキーワードを直接使用してオブジェクトを生成するのはサポートされていません.
2.C++は関数のリロードをサポートし、Cは関数のリロードをサポートしない.コンパイル後、C++の関数名は変更されるが、Cの関数名は基本的に変わらない.両者はコンパイル後の関数名の命名戦略が異なるため、処理しない場合、CがC++の関数を呼び出すか、C++がC関数を呼び出すかは、リンク段階で関数のエラーを報告する.
関数名が見つからない問題を解決する方法
extern"C"{}の使用
役割:extern"C"に含まれるコードをC++コンパイラにC方式でコンパイルしリンクするように通知する
注意:extern“C”はC++の中で増加したので、Cの中で識別することができません
2つの呼び出しがあります.
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;
}

この時cの中にももし私たちが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の本質は、対応するファイルをこの行に直接コピーすることですので、funC()関数をC++プログラムで直接宣言します.
/* 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;
}

だからこそhはCに引用されたのでmyCppHead.hの中のextern“C”、外は#ifdef__を使いますclusplusで包んでください.
C++が提供していない場合、考えてみてください.h,Cはどのようにその中のグローバル関数を使用しますか?
明らかにmycppSourceでcppソースプログラムでは、C呼び出しに提供される関数をextern"C"で修飾し、Cプログラムではextern int funCPP()を用いる.ちょっと声明すればいいです.
もう一度考えてみると、C++にリロード関数があれば、どのようにCに呼び出されますか?
例えば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はCPPのいくつかの新しい特徴、例えばオブジェクト向けの特徴を下に互換性がないため、Cの中で直接newの1つのオブジェクトに行って、更にオブジェクトを使って関数を提供することはできません.前述したように,パッケージを用いて間接的にリロード関数を呼び出す解決策について述べたが,ちょうどここでもパッケージを用いることができる.
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;
}

上のように改造してもいいかどうか考えてみてください.
明らかにだめだcに一言
#include "myCppHead.h"

そしてmycppHead.hにはclassの声明があり、コンパイル時にmycsource.c間違いを報告するに違いない.どうやって解決しますか.
2つの方法、1、MyClassの声明を置く.cpp中;2、新しいcppを追加して包装する
第1の方法を採用すれば、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;
}

直接修正しないならmycppSourceとcppは、中に包装関数を追加すると、
もう1つの方法:1つ追加する.h与.cppは増加するのみである.cppで包装する:
myCppHead.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によって使用される可能性があります.cplusplusはextern“C”に対して制限を行います
3.C++がC関数を呼び出す鍵はC++でC関数を宣言し、extern「C」修飾を採用することである.
4.C++非メンバー関数を呼び出す鍵はC++で、C++関数はextern"C"修飾を使用する必要があることです.
5.C++ヘッダファイルまたはソースファイルが修正できない場合、extern"C"修飾を加えると、新しい包装cppを追加することができ、包装cppでは包装関数を用いて直接目標関数を呼び出し、包装関数はextern"C"修飾を用いてCに使用する.
6.C C++メンバー関数を呼び出し、パッケージ関数を使用する必要がある