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


119

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


1
cocos2d-xフレームワークを試す
glo

@glo良いようですが、フレームワークなしでc ++を使用して、より一般的なものを探しています。「明らかにJNIは除外されています」。
ademar111190 2013

回答:


272

更新。

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

以下に示すコードをダウンロードして実行できるように、リポジトリを確認しください。

答え

コードを示す前に、次の図をよく読んでください。

アーチ

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

この図では、最下位レベルのC ++レイヤーを確認できます。すべての共有コードはこのセグメントにあります。最高レベルは通常のObj-C / Java / Kotlinコードです。ここではニュースはありません。難しい部分は中間層です。

iOS側の中間層は単純です。Objective-C ++として知られているObj-cのバリアントを使用してビルドするようにプロジェクトを構成する必要があるだけで、C ++コードにアクセスできます。

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

ステップごとのコード

サンプルは、テキストをCPPに送信するシンプルなアプリで、そのテキストを別のテキストに変換して返します。アイデアは、iOSのは、「OBJの-C」をお送りしますとAndroidは、それぞれの言語からの「Java」を送信し、CPPコードは次のようにテキストを作成します、である「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コードとして使用できます(例:ViewController ")。

#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

アプリの外観を見てみましょう。

Xcode iPhone

アンドロイド

今こそAndroid統合の時です。AndroidはビルドシステムとしてGradleを使用し、C / C ++コードではCMakeを使用します。したがって、最初に行う必要があるのは、CMake on gradleファイルを構成することです。

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

次に、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");
    }

}

このクラスにはnativeメソッドがあり、という名前のネイティブライブラリをロードすることに注意してくださいnative-lib。このライブラリは私たちが作成したライブラリであり、最終的には、CPPコードが.soAPKに埋め込まれた共有オブジェクトファイルになり、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"この部分がJNIがCPPコードとメソッドリンケージで正しく動作するために必要なことです。また、JNIがJVM JNIEXPORTおよびとして機能するために使用するいくつかのシンボルも表示されJNICALLます。これらの事柄の意味を理解するには、少し時間をかけて読んでおく必要があります。このチュートリアルでは、これらの事柄をボイラープレートと見なすだけにします。

重要なことの1つ、通常は多くの問題の原因は、メソッドの名前です。「Java_package_class_method」のパターンに従う必要があります。現在、Androidスタジオは優れたサポートを提供しているため、このボイラープレートを自動的に生成し、名前が正しくない場合や名前がない場合に表示できます。この例では、メソッドの名前は「Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString」です。これは、「ademar.androidioscppexample」がパッケージであるため、「」を置き換えます。「_」によって、CoreWrapperはネイティブメソッドをリンクするクラスであり、「concatenateMyStringWithCppString」はメソッド名自体です。

メソッドを正しく宣言したので、引数を分析するときがJNIEnv来ました。最初のパラメーターは、そのポインターであり、JNIへのアクセス方法です。すぐにわかるように、変換を行うことが重要です。2番目は、jobjectこのメソッドを呼び出すために使用したオブジェクトのインスタンスです。これはJavaの「this」と考えることができます。この例では、使用する必要はありませんが、宣言する必要があります。このジョブジェクトの後で、メソッドの引数を受け取ります。このメソッドには引数が1つしかないため、「myString」という文字列なので、同じ名前の「jstring」しかありません。また、戻り値の型もjstringであることに注意してください。これは、Javaメソッドが文字列を返すためです。Java/ JNIタイプの詳細については、こちらをお読みください

最後のステップは、JNIタイプをCPP側で使用するタイプに変換することです。この例では、をCPPに変換されjstringconst char *送信に変換し、結果を取得してに変換していjstringます。JNIの他のすべてのステップと同様に、それは難しくありません。それはボイレプレートされているだけであり、すべての作業はand JNIEnv*を呼び出したときに受け取る引数によって行われます。コードがAndroidデバイスで実行できるようになったら、見てみましょう。GetStringUTFCharsNewStringUTF

AndroidStudio アンドロイド


7
素晴らしい説明
RED.Skull 2013年

9
わかりません-しかし、SOで最高品質の回答の1つとして+1
Michael Rodrigues

16
@ ademar111190群を抜いて最も役立つ投稿。これは閉じられるべきではありませんでした。
Jared Burrows 2014

6
@JaredBurrows、私は同意する。再開に投票しました。
OmnipotentEntity 2014

3
@KVISH最初にObjective-Cでラッパーを実装する必要があります。次に、ラッパーヘッダーをブリッジヘッダーファイルに追加することにより、迅速にObjective-Cラッパーにアクセスします。現在のところ、SwiftでC ++に直接アクセスする方法はありません。詳細については、stackoverflow.com
Chris

3

上記の優れた答えで説明されているアプローチは、C ++ヘッダーから直接オンザフライでラッパーコードを生成するScapix言語ブリッジによって完全に自動化できます。次に例を示します。

C ++でクラスを定義します。

#include <scapix/bridge/object.h>

class contact : public scapix::bridge::object<contact>
{
public:
    std::string name();
    void send_message(const std::string& msg, std::shared_ptr<contact> from);
    void add_tags(const std::vector<std::string>& tags);
    void add_friends(std::vector<std::shared_ptr<contact>> friends);
};

そしてそれをSwiftから呼び出します:

class ViewController: UIViewController {
    func send(friend: Contact) {
        let c = Contact()

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}

そしてJavaから:

class View {
    private contact = new Contact;

    public void send(Contact friend) {
        contact.sendMessage("Hello", friend);
        contact.addTags({"a","b","c"});
        contact.addFriends({friend});
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.