1. ホーム
  2. java

[解決済み] AndroidとiOSで同じC++のコードを使用するには?

2022-07-01 20:41:29

質問

アンドロイドで NDK は C/C++ コードに対応し、iOS は Objective-C++ もサポートしているので、Android と iOS で共有するネイティブ C/C++ コードでアプリケーションを書くにはどうしたらよいでしょうか。

どのように解決するのですか?

更新してください。

この回答は、私が書いた4年後でもかなり人気があります。この4年の間に多くのことが変わりましたので、私は現在の現実にもっと合うように私の回答を更新することにしました。答えの考え方は変わりませんが、実装が少し変わりました。私の英語も変わり、かなり上達しましたので、今の回答は誰にとってもより分かりやすくなっています。

をご覧ください。 レポ を参照して、以下に示すコードをダウンロードして実行することができます。

回答

コードを表示する前に、次の図をよく見てください。

OSごとにUIやクセがあるので、その点については各プラットフォームに特化したコードを書いていく予定です。一方、ロジックコードやビジネスルールなど共有できるものはすべてC++で書くつもりなので、各プラットフォームに同じコードをコンパイルすることができます。

図では、最下層の C++ レイヤーを見ることができます。すべての共有コードはこのセグメントにあります。最上位は通常の Obj-C / Java / Kotlin コードで、ここでのニュースはありません。

iOS 側の中間層はシンプルです。 Objective-C++ という Obj-c の変種を使用してビルドするようにプロジェクトを設定するだけで、C++ コードにアクセスできるようになります。

Android側で難しくなったのは、Android上のJavaとKotlinの両方の言語が、Java仮想マシンの下で実行されることです。そのため、C++ コードにアクセスする唯一の方法は JNI を使うしかないのですが、JNIの基本を読むのに時間がかかります。幸い、今日のAndroid Studio IDEはJNI側で大きく改善されており、コードを編集しながら多くの問題が表示されます。

ステップごとのコード

今回のサンプルは、CPPにテキストを送ると、そのテキストを別のものに変換して返してくれるというシンプルなアプリです。iOSは"Obj-C"、Androidは"Java"とそれぞれの言語から送信し、CPPのコードは次のようにテキストを作成します。 << テキストを受信しました >> "と表示されます。

共有CPPコード

まず最初に、私たちは共有CPPコードを作成するつもりです。それを行うには、目的のテキストを受け取るメソッド宣言を持つ単純なヘッダーファイルがあります。

#include <iostream>

const char *concatenateMyStringWithCppString(const char *myString);

そして、CPPの実装です。

#include <string.h>
#include "Core.h"

const char *CPP_BASE_STRING = "cpp says hello to %s";

const char *concatenateMyStringWithCppString(const char *myString) {
    char *concatenatedString = new char[strlen(CPP_BASE_STRING) + strlen(myString)];
    sprintf(concatenatedString, CPP_BASE_STRING, myString);
    return concatenatedString;
}

Unix

興味深いことに、同じコードを Linux や Mac、その他の Unix システムでも使用することができます。この可能性は、共有コードをより速くテストできるため特に有用です。そこで、以下のように Main.cpp を作成し、マシンから実行して共有コードが動作しているかどうかを確認します。

#include <iostream>
#include <string>
#include "../CPP/Core.h"

int main() {
  std::string textFromCppCore = concatenateMyStringWithCppString("Unix");
  std::cout << textFromCppCore << '\n';
  return 0;
}

コードをビルドするには、実行する必要があります。

$ g++ Main.cpp Core.cpp -o main
$ ./main 
cpp says hello to Unix

iOS

いよいよモバイル側への実装です。iOS には簡単な統合機能があるため、まずそれを使用します。iOSアプリは典型的なObj-cアプリですが、1つだけ違うのは、ファイルが .mm であり .m つまり、Obj-Cアプリではなく、Obj-C++アプリです。

より良く構成するために、以下のように、CoreWrapper.mmを作成します。

#import "CoreWrapper.h"

@implementation CoreWrapper

+ (NSString*) concatenateMyStringWithCppString:(NSString*)myString {
    const char *utfString = [myString UTF8String];
    const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
    NSString *objcString = [NSString stringWithUTF8String:textFromCppCore];
    return objcString;
}

@end

このクラスは、CPPの型と呼び出しをObj-Cの型と呼び出しに変換する責任があります。Obj-C上の好きなファイルでCPPコードを呼び出せるようになれば、これは必須ではありませんが、組織を維持するのに役立ち、ラッパーファイルの外側では完全なObj-Cスタイルのコードを維持し、ラッパーファイルだけがCPPスタイルとなります。

ラッパーがCPPコードに接続されると、それを標準的なObj-Cコードとして使用することができます。

#import "ViewController.h"
#import "CoreWrapper.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* textFromCppCore = [CoreWrapper concatenateMyStringWithCppString:@"Obj-C++"];
    [_label setText:textFromCppCore];
}

@end

アプリの見た目を見てみましょう。

アンドロイド

さて、いよいよAndroidとの連携です。AndroidはビルドシステムとしてGradleを使用し、C/C++のコードにはCMakeを使用します。そこで、まず最初に行うべきことは、CMakeをgradleファイル上で設定することです。

android {
...
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}
...
defaultConfig {
    externalNativeBuild {
        cmake {
            cppFlags "-std=c++14"
        }
    }
...
}

そして、2つ目のステップは、CMakeLists.txtファイルを追加することです。

cmake_minimum_required(VERSION 3.4.1)

include_directories (
    ../../CPP/
)

add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    ../../CPP/Core.h
    ../../CPP/Core.cpp
)

find_library(
    log-lib
    log
)

target_link_libraries(
    native-lib
    ${log-lib}
)

CMakeファイルでは、プロジェクトで使用するCPPファイルとヘッダーフォルダーを追加する必要があります。 CPP フォルダーと Core.h/.cpp ファイルを追加しています。C/C++の設定についてもっと知りたい方は を読んでください。

さて、コア・コードがアプリの一部となったので、ブリッジを作成する時が来ました。よりシンプルで組織的なものにするために、 CoreWrapperという特定のクラスを作成して、JVMとCPPの間のラッパーとします。

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

    static {
        System.loadLibrary("native-lib");
    }

}

このクラスは native という名前のネイティブ・ライブラリーをロードします。 native-lib . このライブラリは、最終的に CPP コードが共有オブジェクトとなる、作成した .so ファイルを私たちのAPKに埋め込み、その中に loadLibrary がそれをロードします。最後に、ネイティブメソッドを呼び出すと、JVMはロードされたライブラリに呼び出しを委譲します。

さて、Androidの統合で最も奇妙な部分はJNIです。以下のようにcppファイル、ここでは"native-lib.cpp"が必要です。

extern "C" {

JNIEXPORT jstring JNICALL Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString(JNIEnv *env, jobject /* this */, jstring myString) {
    const char *utfString = env->GetStringUTFChars(myString, 0);
    const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
    jstring javaString = env->NewStringUTF(textFromCppCore);
    return javaString;
}

}

まず最初に目につくのは extern "C" この部分は、CPPコードとメソッドのリンクでJNIが正しく動作するために必要です。また、JNIがJVMと連携するために使用するシンボルもあります。 JNIEXPORTJNICALL . これらの意味を理解するためには、時間をおいて を読むことです。 このチュートリアルの目的では、これらのことを定型文とみなしてください。

重要なことの1つは、通常多くの問題の根源となるメソッドの名前です。それはパターン "Java_package_class_method" に従う必要があります。現在、Android Studioはこの定型文を自動的に生成し、正しい名前かそうでないかを表示してくれる優れたサポートを持っています。この例では、メソッドの名前は、 "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" これは、 "ademar.androidioscppexample" が我々のパッケージなので、 " を置き換えるからです。 CoreWrapperはネイティブ・メソッドをリンクしているクラスで、"concatenateMyStringWithCppString"はメソッド名そのものです。

メソッドを正しく宣言できたので、次は引数を分析します。 JNIEnv これはJNIにアクセスする方法であり、すぐにわかるように変換を行うために重要です。2つ目は jobject で、これはこのメソッドを呼び出すために使用したオブジェクトのインスタンスです。これはJavaのquotと同じだと考えてください。 この この例では、これを使用する必要はありませんが、宣言する必要があります。このオブジェクトの後、メソッドの引数を受け取ることになります。このメソッドには引数が1つしかないので、同じ名前の "myString" があるだけです。また、戻り値の型もjstringであることに注目してください。これは、JavaメソッドがStringを返すためです。Java/JNI型の詳細については を読んでください。

最後のステップは、JNIの型をCPP側で使用する型に変換することです。この例では jstringconst char * に変換してCPPに送り、結果を得て、再び jstring . JNI上の他の全てのステップと同様に、これは難しいことではありません。 JNIEnv* を呼び出すときに受け取る引数によって行われます。 GetStringUTFCharsNewStringUTF . これで、Android端末で動作するコードが完成しましたので、見てみましょう。