AndroidでNDK
AndroidでNDKを使ってみたメモです。
環境構築
- Eclipseで、Android SDKの設定をしておきます。
- DeveloperサイトからMac OS X 64bit用をダウンロードして、任意の場所に展開しておきます。
- Eclipseのメニューの環境設定>Android>NDKで、Step2で展開した場所のパスを設定します。
- 同じくメニューのHelp>Install New Software>Addで、以下のリポジトリを追加します
(最後の"luna"はバージョンに合わせて変更します)。
http://download.eclipse.org/releases/luna
- Step4で表示された項目のうち、[Programming Language]から以下をインストールします
(Development ToolsまたはDevelopment Tools SDKが、項目としては表示されなかったような気がしますが、表示されたものをチェックしてインストールすればOKです)。
C/C++ Development Tools C/C++ Development Tools SDK C/C++ Library API Documentation Hover Help
プロジェクト作成
通常のAndroidプロジェクトと同じように、New>Other>Android>Android Application Projectから作成します
(Blank Activityにして、Target APIは21にしてみました)。
ネイティヴコードの使用準備
- Package Explorerで、プロジェクト直下に[jni]というフォルダを作成します。
- 同じくPackage Explorerの、プロジェクト名の上で右クリック>Android Tools>Add Native Supportをクリックします
(自動でStep1のjniフォルダに[プロジェクト名.cpp]と[Android.mk]というファイルが作成されます)。 - Step1のjniフォルダに[Application.mk]を作成して、以下を記述します
([APP_PLATFORM]のバージョンはプロジェクトに合わせて変更してください)。
APP_PLATFORM := android-21 APP_ABI := armeabi armeabi-v7a x86
- プロジェクトをビルドします。ネイティヴコードをJavaから呼び出すためのライブラリ名の入力を求められるので、任意で設定します。
C++
AndroidNdkTest.cpp
#include < jni.h > extern "C" { jstring Java_jp_co_masanori_androidndktest_MainActivity_getHello(JNIEnv *env, jobject obj) { jstring jstHello = env->NewStringUTF("世界さん チーッス"); return jstHello; } }
- C++では[extern "C"]が必要となります。今回のようにメソッドが一つだけなら、{}で括らずメソッドの頭につけても良いかと。
- NDK(というかjni)では、そのままではstd::stringは使用できず、jstringで文字列を扱えるようです。
- メソッド名は[Java]、[呼び出すクラスのパッケージ名]、[呼び出すクラス名]、[メソッド名]という形式となっています。
またそれぞれの項目、パッケージ名に含まれる[.]は[]に置き換える必要があるため、メソッド名やパッケージ名などに[]が含まれていると上手く呼び出せないという罠が...。 - 引数の[JNIEnv][jobject]は固定で、任意で引数を渡したい場合は3つ目以降の引数として渡します。
Java
MainActivity.java
package jp.co.masanori.androidndktest; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity { TextView _txtHello; public native String getHello(); static { System.loadLibrary("AndroidNdkTest"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _txtHello = (TextView)findViewById(R.id.txtHello); _txtHello.setText(getHello()); } }
- プロジェクト作成時にデフォルトで作成されるMenuについては特に何もしていないので省いています。
- C++のメソッドから受け取った文字列を表示するためのTextViewを追加しています。
- jni側のメソッドの宣言は、[native 型 メソッド名()]の形で行います。
- jniのファイルにアクセスするには、以下のようにライブラリのロードが必要です
(ライブラリ名はネイティヴコードの使用準備のStep4で設定したもので、jni>Android.mk>LOCAL_MODULEに記述されています)。
static { System.loadLibrary("ライブラリ名"); }
- なお、ネイティヴコードの使用準備のStep3でAPP_ABIの項目を記述していないと、メソッド名やライブラリ名などが正しくてもUnsatisfiedLinkErrorが発生します (Application.mkの作成は任意と書かれたりしていたので結構ハマりました)。
C++のファイルを追加したい
自動で作成される[プロジェクト名.cpp]の主な役割はJavaとの連携にあると思います。
ではさらにクラスやファイルを追加するにはどうするか?というと、割と普段のC++のままで大丈夫だったりします。
Add_Text.h
単純に呼び出したら文字列を返すメソッドを作ります。
#include < jni.h > class Add_Text { public: jstring GetStrText(JNIEnv *env); };
Add_Text.cpp
#include "Add_Text.h" jstring Add_Text::GetStrText(JNIEnv *env) { return env->NewStringUTF("!!");; }
AndroidNdkTest.cpp
先ほどのコードに、Add_Textクラスを使用する内容を追加します。
#include < jni.h > #include < string.h > #include "Ctrl_Object.h" extern "C" { jstring Java_jp_co_masanori_androidndktest_MainActivity_getHello(JNIEnv *env, jobject obj) { Add_Text *addText = new Add_Text; char chrCombined[256]; jstring jstHello = env->NewStringUTF("世界さん チーッス"); // 文字列結合のため、Char型に変換. const char *chrHello = env->GetStringUTFChars(jstHello, 0); jstring jstText = addText->GetStrText(env); // 文字列結合のため、Char型に変換. const char *chrText = env->GetStringUTFChars(jstText, 0); // 文字列を追加して一つにまとめる. strcpy(chrCombined, chrHello); strcat(chrCombined, chrText); // 解放 env->ReleaseStringUTFChars(jstHello, chrHello); env->ReleaseStringUTFChars(jstText, chrText); delete Add_Text; return env->NewStringUTF(chrCombined); } }
思ったこと
とりあえずバージョンによってやり方が違っていたり、jniがAndroid固有のものではなくJavaからきているために環境も違ったりと、調べるのがちょっと面倒かな、というのが正直なところ。
また、stdがそのままでは使えないなどC++でAndroidプログラミング、というには少し壁がある気はします。
ただ、openFrameworksのようにネイティヴコードを使うフレームワークを取り入れたり、コードがある程度他のプラットフォームと共通化させられるなどメリットもあると思いますので、状況に応じて使い分けていきたいところです。