[解決済み] AndroidとiOSで同じC++のコードを使用するには?
質問
アンドロイドで 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と連携するために使用するシンボルもあります。
JNIEXPORT
と
JNICALL
. これらの意味を理解するためには、時間をおいて
を読むことです。
このチュートリアルの目的では、これらのことを定型文とみなしてください。
重要なことの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側で使用する型に変換することです。この例では
jstring
を
const char *
に変換してCPPに送り、結果を得て、再び
jstring
. JNI上の他の全てのステップと同様に、これは難しいことではありません。
JNIEnv*
を呼び出すときに受け取る引数によって行われます。
GetStringUTFChars
と
NewStringUTF
. これで、Android端末で動作するコードが完成しましたので、見てみましょう。
関連
-
スレッド "main "での例外をEclipseで解決 java.lang.Error: 未解決のコンパイル問題、コンパイラとパッケージの不整合
-
あるコードに出会いましたが、何に使うのか理解できません。 List<String> list = new ArrayList<String>() { { a
-
[解決済み] android.os.NetworkOnMainThreadException' を修正するにはどうすればよいですか?
-
[解決済み] JavaにおけるHashMapとHashtableの違いは何ですか?
-
[解決済み] serialVersionUIDとは何ですか、またなぜそれを使用する必要がありますか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] java.net.URLConnectionを使用してHTTPリクエストを発生させ処理する方法
-
[解決済み】Android UserManager.isUserAGoat()の正しい使用例?)
-
[解決済み】高放射能環境下で使用するアプリケーションのコンパイルについて
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
springboot project MIMEタイプ text/htmlで転送された静的ファイルを読み込む。
-
undefinedeclipse エラー。この行に複数のアノテーションが見つかりました: - 文字列を型解決に解決できない
-
Eclipseで "XXXX "の解決策を(型に)解決することができない
-
Eclipseプロンプトを実行する java仮想マシンを使用しない
-
linux run jarfile Invalid or corrupt jarfile error.
-
アイデア Springboot Web プロジェクトを jar にパッケージ化する場合、Error: 無効または破損した jarfile x.jar 解決策
-
java.lang.NoClassDefFoundError: org.apache.jasper.el.ELContextImpl クラスを初期化できませんでした。
-
java 例外。Javaツールの初期化
-
ローカルリソースのロードが許可されていない場合の解決策
-
[解決済み] SwiftとC++を混ぜることはできますか?Objective-Cの.mmファイルのように