Android と iOS で同じ C++ コードを使用するにはどうすればよいでしょうか? 質問する

Android と iOS で同じ C++ コードを使用するにはどうすればよいでしょうか? 質問する

Android搭載NDKC/C++コードとiOSをサポートしていますObjective-C++もサポートされていますが、Android と iOS で共有されるネイティブ C/C++ コードを使用してアプリケーションを作成するにはどうすればよいでしょうか?

ベストアンサー1

アップデート。

この回答は、私が書いてから 4 年経った今でも非常に人気があります。この 4 年間で多くのことが変化したため、現在の現実により適合するように回答を更新することにしました。回答のアイデアは変わりませんが、実装が少し変更されました。私の英語も変わり、大幅に向上したため、回答は今では誰にとってもより理解しやすくなりました。

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

答え

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

アーチ

各 OS には独自の UI と特性があるため、この点については各プラットフォームに固有のコードを記述するつもりです。一方、ロジック コード、ビジネス ルール、共有できるものはすべて C++ を使用して記述し、各プラットフォームに同じコードをコンパイルできるようにします。

図では、最下層に C++ レイヤーがあることがわかります。共有コードはすべてこのセグメントにあります。最上層は通常の Obj-C / Java / Kotlin コードですが、ここでは特に目新しいことはありません。難しいのは中間層です。

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

Android側では状況がさらに難しくなりました。AndroidではJavaとKotlinの両方の言語がJava仮想マシンで実行されます。そのため、C++コードにアクセスする唯一の方法は、JNI、JNI の基礎について時間をかけて読んでみてください。幸いなことに、現在の Android Studio IDE では JNI 側が大幅に改善されており、コードを編集するときに多くの問題が表示されます。

ステップごとのコード

このサンプルは、CPPにテキストを送信し、そのテキストを別のものに変換して返すというシンプルなアプリです。iOSは「Obj-C」を送信し、Androidはそれぞれの言語から「Java」を送信し、CPPコードは「cpp says hello to」というテキストを作成します。<<テキスト受信>>「」。

共有 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;
}

ユニックス

興味深いボーナスとして、同じコードを 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 コードに接続すると、ViewController などの標準の 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 を使用します。そのため、最初に行う必要があるのは、Gradle ファイルで CMake を構成することです。

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++構成の詳細については、それを読んで。

これでコア コードがアプリの一部になったので、次はブリッジを作成します。物事をよりシンプルかつ整理されたものにするために、JVM と CPP 間のラッパーとなる CoreWrapper という特定のクラスを作成します。

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

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

}

Note this class has a native method and loads a native library named native-lib. This library is the one we create, in the end, the CPP code will become a shared object .so File embed in our APK, and the loadLibrary will load it. Finally, when you call the native method, the JVM will delegate the call to the loaded library.

Now the most strange part of Android integration is the JNI; We need a cpp file as follow, in our case "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;
}

}

The first thing you will notice is the extern "C" this part is necessary to JNI work correctly with our CPP code and method linkages. You will also see some symbols JNI uses to works with JVM as JNIEXPORT and JNICALL. To you understand the meaning of those things, it is necessary to take a time and read it, for this tutorial purposes just consider these things as boilerplate.

One significant thing and usually the root of a lot of problems is the name of the method; it needs to follow the pattern "Java_package_class_method". Currently, Android studio has excellent support for it so it can generate this boilerplate automatically and show to you when it is correct or not named. On our example our method is named "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" it is because "ademar.androidioscppexample" is our package, so we replace the "." by "_", CoreWrapper is the class where we are linking the native method and "concatenateMyStringWithCppString" is the method name itself.

As we have the method correctly declared it is time to analyze the arguments, the first parameter is a pointer of JNIEnv it is the way we have access to JNI stuff, it is crucial to we make our conversions as you will see soon. The second is a jobject it is the instance of the object you had used to call this method. You can think it as the java "this", on our example we do not need to use it, but we still need to declare it. After this jobject, we are going to receive the arguments of the method. Because our method has only one argument - a String "myString", we have only a "jstring" with the same name. Also notice that our return type is also a jstring. It is because our Java method returns a String, for more information about Java/JNI types please read it.

The final step is to convert the JNI types to the types we use on CPP side. On our example, we are transforming the jstring to a const char * sending it converted to the CPP, getting the result and converting back to jstring. As all other steps on JNI, it is not hard; it is only boilerplated, all the work is done by the JNIEnv* argument we receive when we call the GetStringUTFChars and NewStringUTF. After it our code is ready to run on Android devices, lets take a look.

Androidスタジオ アンドロイド

おすすめ記事