#contents *Androidのネイティブ動作について (android-ndk-r5で確認)[#a4a42d20] Andoridは、ARMv5TE(ARMv5 Thumb Enhance、Androidで採用されている代表的なCPU)というCPUで動作する、Linux環境であるため、対象CPU用のC言語でコンパイルされたネイティブのアプリを実行することが可能である。しかし、Androidマーケットや、Androidのアプリの制限からC言語で作成したアプリケーションをAndroid上でダイレクトに起動することはできず、あくまでもJavaのオブラートにかけた上で利用する形となる。 **クロスプラットフォーム環境の構築 [#n6960bbd] EclipseなどのIDE上で、ARM5のネイティブコードを作成するには、クロスコンパイラ環境を構築する必要がある。LinuxやOSXはUnix系の環境の為、gccのコンパイラ環境が標準で準備されているが、Winodowsユーザーの場合には、Cygwin環境を構築する必要がある。ここではコンパイルの基本的な環境は準備されているものとして話を進める。またEclipseを利用する場合には、CDT( C/C++ Development Tools )を導入しておく。 ***Android NDK (Native Development Kit)のインストール [#jde32239] Android NDKは、Android SDKをインストール後にインストールしなければならない。 +[[ここのNDKのサイト:http://developer.android.com/intl/ja/sdk/ndk/index.html]]から、開発環境に合わせたファイルをダウンロードする。(手順はSDKを同じ) +ダウンロード後、android-sdkを同じフォルダに入れておく。 +PATHを通す。設置したSDKと違い、NDKフォルダのTOPだけを追加するだけでよい。(ndk-buildのパスを通すという意味となる) *** Cのコードの設置 [#ed1ad4cd] NDKといえども、上記の通りあくまでJavaから呼び出されるモジュールを作成するために、プロジェクトの作成は、Android SDKを利用した、Android Projectとして作成される。プロジェクトを作成後は、プロジェクトの直下に、Nativeソースである、C/C++のソースコードを設置するためのフォルダとして <project>/jni/ というフォルダを作成し、Cのソースを設置する。またCソースの為のMakeFileは、Android.mk という名前で作成しておく必要がある。 ***コンパイルと実行 [#x04aa215] 上記の設定で、コマンドラインから開発中のプロジェクトフォルダにカレントフォルダを移動し、ndk-build とするだけで、下位フォルダの <project>/jni/ フォルダにある、Android.mk ファイルをよみこみ、ARM5用の アプリ名.so ファイル(Linuxの動的ライブラリファイル)を <project>/obj/local/armeabi/ フォルダ以下に作成する。~ ~ その後、Eclipse上で実行をするだけで、Android SDKが自動的に .so をパッケージし、実行時に /data/data/com.example.アプリ名/lib/libアプリ名.so のような形で作成したライブラリを展開しロードする。~ ***サンプル実行 [#rc47919f] 上記でインストールした、Android NDKの中には samples フォルダがあり、Eclipse上から新規でAndroid Projectを選び、既存のソースからを選択し、複数あるサンプルから自分で確認したいサンプルを読み込んでテストすると良いだろう。もちろん、実行の為には上記のコンパイルと実行の項目で指示した ndk-build を行う必要がある。 *JNI(Java Native Interface)の説明 [#vd040e5c] -[[JNIのWikipediaはここ:http://ja.wikipedia.org/wiki/Java_Native_Interface]] ***具体的なJavaのコード [#w6e86b84] 基本的にJava側では、ライブラリのロードと、関数の定義の2つだけで対応できる。 // ライブラリのロード static { System.loadLibrary("hello-jni"); } // 関数の定義 public native String stringFromJNI(); // 実際の利用 String text = stringFromJNI(); これらは、hello-jni というCのライブラリファイル(例:hello-jni.so)をロードし、stringFromJNI() という関数を利用する事を定義している。余談だが複数のライブラリのロードをした場合は、最初のロードしたライブラリが優先される。 ***具体的なC側のコード [#o7ee1a6f] C側で特に注意するべきは、Object型のJavaとのデータ受け渡しのため、複雑な変数や名前を使う。 jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); } これは、javaのString型の戻り値(jstring型)を持つ、関数を定義している。JNIEnv型はJNI先頭のデータ受け渡し用の方であり、これを理解することがJNIを理解するといっても過言ではない。 ***Android.mkの記述について [#iaae2ca2] Android.mkは、Cのソースをビルドするにあたり、MakeFileと同じようにndk-buildによって参照されるファイルである。フォーマットの詳細は[[ここ:http://android.git.kernel.org/?p=platform/build.git;a=blob_plain;f=core/build-system.html]]を参照。 ***ミニテクニック [#leb38c07] ''java.nio.ByteBufferを使う''~ JNIはJava側との通信に非常にコストが掛かる。そのためデータ参照を効率化するために、java.nio.ByteBufferクラスを利用することで、Javaのデータを直接参照できる。 ByteBuffer buffer = ByteBuffer.allocateDirect(バイト数); buffer.order(ByteOrder.nativeOrder()); FloatBuffer floatBuffer = buffer.asFloatBuffer(); floatBuffer.put(x); nativeCall(floatBuffer); // Native呼び出し C言語(Native)側 JNIEXPORT void JNICALL Java_com_domain_nativeCall (JNIEnv*env, jobject thiz, jobject buffer){ float *buf = (float *)env->GetDirectBufferAddress(env, buffer); float x = *buf++; } [[簡単な説明ページ:http://www5.big.or.jp/~tera/Labo/jni/jni2.html]]~ [[JNIのC言語/C++側のコーディング:http://www.ne.jp/asahi/hishidama/home/tech/java/jni_code.html]]~ ***配列をやり取りする。 [#yd809021] ''JAVA側'' private native void setDataPointer( int[] data ); public void ArraySend(){ int[] data = new int[5]; for( int i=0 ; i<5 ; i++ ){ data[i] = 5000 + i; } setDataPointer(data); for( int i=0 ; i<5 ; i++ ){ Log.d(TAG, " newNum[" + i + "] = " + data[i]); } } ''C側'' void Java_com_testDraw_ArrayTest_setDataPointer( JNIEnv* env, jobject thiz, jintArray arrayInt ) { jboolean resultFlag; // 関数の結果を戻すためのフラグ領域 int length; // 取得した配列の長さ int i; // ループカウンタ // データの取り出し jint * newArray = (*env)->GetIntArrayElements(env,arrayInt,&resultFlag); length = (*env)->GetArrayLength(env,arrayInt); // データ加工 for( i=0 ; i< length ; i++ ){ newArray[i] = 100 + i; } // 取得した領域の開放(Java側でstaticで確保している場合は必要ない) (*env)->ReleaseIntArrayElements(env, arrayInt, newArray, 0); } ***JNI専用の型 [#q9f4b9a6] |JAVAの型|JNIを利用したのCの型名|説明|h |boolean|jboolean|unsigned 8 ビット| |byte|jbyte|signed 8 ビット| |char|jchar|unsigned 16 ビット| |short|jshort|signed 16 ビット| |int|jint|signed 32 ビット| |~|jsize|~| |long|jlong|signed 64 ビット| |float|jfloat|32 ビット| |double|jdouble|64 ビット| |void|void|N/A| [[詳細の情報はここを参照:http://java.sun.com/j2se/1.4/ja/docs/ja/guide/jni/spec/types.doc.html#428]] -[[詳細の情報はここを参照:http://java.sun.com/j2se/1.4/ja/docs/ja/guide/jni/spec/types.doc.html#428]] -[[JNIEnv取得の方法:http://blog.malrone.info/archives/1664]] ***トラブルシューティング [#p85cb814] ''JNI_OnUnload()が呼び出されない''~ C/C++では、メモリを確保したら解放しなければならないのですが、ライブラリが解放されると、呼び出されるはずのJNI_OnUnload()関数が呼び出されません。原因は分かっていません。~ ~ ''クラスが何度も初期化される''~ もっと不可解なのが、static節が何度も呼び出されることです。例えば今回のサンプルのように、以下のコードのようにすると、不定期に何度もこのコードが実行されます。~ static { System.loadLibrary("FireEffect"); } 結果として、JNI_OnLoad()が何度も呼び出されてしまうため、実は「JNI_OnLoad()やJNI_OnUnload()でメモリやリソースを管理する」という方法はAndroidでは破たんします。~ &color(red){※参照:[[Android NDKでJNIを使用してアプリを高速化するには:http://www.atmarkit.co.jp/fsmart/articles/android15/android15_2.html]]}; *C言語からJavaを呼び出す場合 [#e835d46b] 呼び出しの型の名前 |Java側の型の種類|int|void|boolean|short|doble|long|string|int array|long array| |CallMethodでの短縮型|I|V|Z|S|D|J|X|[I|[J| -[[JNI専用のC関数に関する解説サイト:http://java.sun.com/j2se/1.5.0/ja/docs/ja/guide/jni/spec/functions.html]] ***メソッドシグネチャーの説明 [#c701802e] -[[参考サイト:http://www.tsh-world.co.jp/sp/support/manual/V701html/interop/itr_32.htm]] -[[参考サイト・JAVA:http://java.sun.com/j2se/1.4/ja/docs/ja/guide/jni/spec/types.doc.html#16432]] *JNA(Java Native Access)の説明 [#q551fce4] -[[http://jna.java.net/]] -[[JNA のWikipedia:http://ja.wikipedia.org/wiki/Java_Native_Access]] *デバッグ [#ae5a45dc] Nativeで動作する場合、デバッグが非常に難しい。そこでSIGNAL-11などのSIGNALコマンドによって停止した場合の例を説明する。 **SIGNAL-11 で停止 [#fe713417] この場合、Logcatに下記のようなダンプが表示される。 06-17 01:17:01.261: INFO/DEBUG(2375): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 06-17 01:17:01.261: INFO/DEBUG(2375): Build fingerprint: 'samsung/SC-01C/SC-01C/SC-01C:2.2/FROYO/OMJK2:user/release-keys' 06-17 01:17:01.261: INFO/DEBUG(2375): pid: 23452, tid: 23466 >>> com.sample.android.myapp <<< 06-17 01:17:01.261: INFO/DEBUG(2375): signal 11 (SIGSEGV), fault addr 81b4c5a7 06-17 01:17:01.261: INFO/DEBUG(2375): r0 00000006 r1 00000018 r2 81b4adbe r3 81b4c5a6 06-17 01:17:01.261: INFO/DEBUG(2375): r4 81b49d60 r5 00000000 r6 430c7ec0 r7 430c7ecc 06-17 01:17:01.261: INFO/DEBUG(2375): r8 4470cb80 r9 430c7ec4 10 430c7eac fp 00000130 06-17 01:17:01.265: INFO/DEBUG(2375): ip 81b49d70 sp 4470caa0 lr 819890c0 pc 819b05c8 cpsr 20000010 06-17 01:17:01.265: INFO/DEBUG(2375): d0 643a64696f72646e d1 6472656767756265 06-17 01:17:01.265: INFO/DEBUG(2375): d2 47f8a41847f8a4ff d3 47f6e9a847f8a3ff <省略> 06-17 01:17:01.265: INFO/DEBUG(2375): scr 20000012 06-17 01:17:01.288: INFO/DEBUG(2375): #00 pc 001b05c8 /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #01 pc 001b006c /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #02 pc 0017a58c /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #03 pc 0017dddc /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #04 pc 0017d3c0 /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #05 pc 001c0a14 /data/data/com.sample.android.myapp/lib/libmyapp-native.so 06-17 01:17:01.288: INFO/DEBUG(2375): #06 pc 00016df4 /system/lib/libdvm.so 06-17 01:17:01.292: INFO/DEBUG(2375): #07 pc 00045284 /system/lib/libdvm.so 06-17 01:17:01.292: INFO/DEBUG(2375): code around pc: 06-17 01:17:01.292: INFO/DEBUG(2375): 819b05a8 e3e03000 e5cd3036 ea00000e e5dd2051 <省略> 06-17 01:17:01.292: INFO/DEBUG(2375): code around lr: 06-17 01:17:01.292: INFO/DEBUG(2375): 819890a0 e1cd30be ea00000a e59d3004 e5932000 <省略> 06-17 01:17:01.292: INFO/DEBUG(2375): stack: 06-17 01:17:01.292: INFO/DEBUG(2375): 4470ca60 00244658 [heap] 06-17 01:17:01.292: INFO/DEBUG(2375): 4470ca64 0000818c /system/bin/app_process 06-17 01:17:01.292: INFO/DEBUG(2375): 4470ca68 001f0000 [heap] 06-17 01:17:01.292: INFO/DEBUG(2375): 4470ca6c 81af1700 /data/data/com.sample.android.myapp/lib/libmyapp-native.so ここで注目されるべきは、signal 11で停止した事と、scr 20000012以下の行で説明される関数の呼び出しの遷移である。表記は#00が落ちた場所であり、#00を呼び出したのが#01、#01を呼び出したのが#02と、順番が逆で表示されている。次にこれら各行に「pc 001b05c8」というようなアドレスが記入されているが、このままではソースのどの部分に当たる行かわかりづらい。~ ~ ***objdump(実行形式のファイルから、Cのソースとアセンブラ及びアドレスの複合表示ツール) [#oc9f0b37] まず最初に、Android用としては、NDKのフォルダに、toolchains/arm-linux-androideabi-4.4.3/prebuilt/xxxx/bin/arm-linux-androideabi-objdumpが存在するので、これを使う。以下に説明するのは、一般的なobjdumpについてである。gnuのBinutilsに含まれるツールである。機能はコンパイルされた実行形式のファイルから、そのアドレスに対応するソースとアセンブラの行を表示してくれる、便利なツールであり、[[ここ:http://ftp.gnu.org/gnu/binutils/binutils-2.21.tar.gz]]からダウンロードして、自分の環境で使えるようにする。~ # tar -zxvf binutils-2.21.tar.gz # cd binutils-2.21 # ./configure # make # make install 使い方は簡単であり、下記の形でソースを混ぜて表示するだけである。ここで先程のアドレスを元にソースのどの部分が落ちたのかを大体把握することが可能である。~ # objdump -d -s -l libmyapp-native.so ~ 他の詳しいオプションなどについては、このページが詳しい -http://www26.atwiki.jp/funa_tk/pages/22.html -http://www.bookshelf.jp/texi/binutils/binutils-ja_4.html *利用上の注意点 [#j913bc6f] ***static 定義の変数やグローバル変数について [#ue99df5e] staticな変数や、グローバルな変数は、利用時に必ず初期化する必要がある。 static int gCounter = 10; のような記述は、通常のCでのライブラリでは、初期化されるがJNIでは.so ファイルがメモリに残ったまま、再度必要な関数が呼び出される可能性があるため、このプログラムロード時に行われるような static 変数が初期化されず、過去の値が設定されたままになる。 *OpenGL on NDK [#o405c328] -[[Androidプログラミング-NDK-OpenGL:http://tueda.wikkii.com/wiki/Android%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-NDK-OpenGL]] *JNIのコンパイル [#le902a8f] **ndk-build [#ce330682] ***引数一覧 [#bb312057] |コマンド|概要|h |ndk-build|ビルドの実行| |ndk-build clean|過去に生成したすべてのファイルの削除| |ndk-build NDK_DEBUG=1|デバッグモードでビルド。シンボリック情報やログを入れ、デバッグ可能なバイナリの生成| |ndk-build NDK_DEBUG=0|デバッグビルドを無効(リリース用)| |ndk-build V=1|Verboseモード。makeに渡す文字列をすべて表示| |ndk-build -C <project>|プロジェクトパスを指定しビルド。| **Android.mk [#l976bbd2] ***include構文で利用できる引数 [#v98718d0] include $(引数) |CLEAR_VARS|LOCAL_XXX変数を消す| |BUILD_SHARED_LIBRARY|LOCAL_MODULE,LOCAL_SRC_FILESをもとにshard libraryをビルドする、lib$(LOCAL_MODULE).soができる| |BUILD_STATIC_LIBRARY|static libraryをビルドする、shard libraryからはLOCAL_STATIC_LIBRARIES,LOCAL_STATIC_WHOLE_LIBRARIESに指定してリンクする、lib$(LOCAL_MODULE).aができる| |PREBUILT_SHARED_LIBRARY|事前にビルドされたshard libraryを取り込む、prebuiltするときにLOCAL_SRC_FILESで取り込むバイナリファイルを指定する(アーキテクチャに合わせて適切なバイナリモジュールを選ぶ必要がある、TARGET_ARCH_ABIを使ってパスを指定するのが良い)。他のモジュールからはLOCAL_SHARED_LIBRARYで読み込める。ヘッダを公開するときはLOCAL_EXPORT_C_INCLUDES を使う。| |PREBUILT_STATIC_LIBRARY|PREBUILT_SHARED_LIBRARYのstatic library版| |TARGET_ARCH|ターゲットのCPUアーキテクチャ名| |TARGET_PLATFORM|ターゲットのアンドロイドプラットフォーム(≒version)| |TARGET_ARCH_ABI|ターゲットのCPU+ABI| |TARGET_ABI|$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)| ***コンパイル時のポイント [#i8d01a50] +.soを連結することはできない、複数のライブラリを連結したいときは、最終ライブラリだけ、.soにしてそれ以外はすべて、.a(static)で作成 +スタティックライブラリは基本的にPREBUILD_STATIC_LIBRARYでコンパイルする。 +最終的な共有ライブラリ(.so)のコンパイル時には、LOCAL_STATIC_LIBRARIES又は LOCAL_WHOLE_STATIC_LIBRARIESで上のモジュールを指定する。(全部取り込みたいときのみ。使用するものだけ取り込みたいときはLOCAL_STATIC_LIBRARIESを利用) *その他の参考リンク [#b76f4594] -[[Run native executable in Android App:http://gimite.net/en/index.php?Run%20native%20executable%20in%20Android%20App]] -[[AndroidでC言語で書いたネイティブアプリを動かしてみる:http://d.hatena.ne.jp/Gimite/20071117/1195287778]] -[[Android NDK と NativeActivity:http://wlog.flatlib.jp/item/1493]] -[[c言語側からJava呼び出し:http://groups.google.com/group/android-group-japan/browse_thread/thread/49d865163b1da544/93343a206d60df10?hide_quotes=no]] -[[Android NDK用にSQLiteをビルドして利用する:http://www.usefullcode.net/2010/12/android_ndksqlite.html]] -[[PNGを読み込み展開する速度をNDKとJavaで比較:http://labs.techfirm.co.jp/android/iguchi/2210]]