一般的なアプリケーションと異なり、通常はAndroid NDKが単独でアプリケーションとなることはなく、Android SDKで作成されたアプリケーションに含まれることになります。しかし、Android SDKと組み合わせてNDK使用するととても複雑となってしまうため、一般的ではありませんが単純なCアプリケーションをARMクロスビルド環境で作成して、エミュレータで実行することを目標とします。(つまり、通常のネイティブプログラムをNDKを使用してクロスビルドするだけである)また、単純なCアプリケーションではAndroidのエミュレータでみえないため、ここでgdbのリモートデバッグ機能を使用してeclipse上でのリッチデバッグ環境も構築します。
ただ、eclipseのAndroid向けプラグインがNDKに対応すれば以下の様な面倒なことはしなくても良くなるはずですので、以下が面倒に思える人は待つのも手です。(事実、私は面倒だと思います。)
次から先はプロジェクト単位となりますので、プロジェクトを作成します。プロジェクトはファイルメニューの新規からC Projectを選択します。(C++ Projectでも可能ですが、ここでは簡単のためにC Projectのケースだけを説明します。)パースペクティブをC/C++に指定していない場合はメニューC Projectがメニューから選択できないため、新規からProjectを選択してください。
次にMakefile projectを選択します。これはeclipse側で自動的にMakefileを作ってビルドしてくれない設定です。Android NDK側のツールを使用してビルドする必要があるため、こうしています。またtoolchainにはCygwin GCCを選択しています。Android NDK側で用意されているmakefileはAndroid NDKで用意しているクロスビルド用のGCCを使用するため、あまり意味がありませんが、こうしています。プロジェクト名はHelloNDKとしていますが、任意の名前を付けてかまいません。また、makefileの説明で後述しますが、"Android NDKのインストールディテクトリ/sources/プロジェクト名"にC側ソースを配置した方が楽ですので、そこに配置します。また、Android NDKで用意しているmakefileの関係上、その次のダイアログは特に何もせず終了を指定して問題ありません。
今度は、Android NDKのツールチェーンでビルドが行われる様にAndroid NDKのアプリケーションディレクトリに移動してからビルドを行うような設定をします。(eclipseのデフォルトではネイティブビルド環境となっているため、Android NDKのツールチェーンにあるクロスビルド環境でビルドを実施する必要があります)具体的にはカレントディレクトリをAndroid NDKのインストールディレクトリにして、そこでmakeするための小細工としてbatファイル(この例ではC:\android-ndk-1.5_r1\build.bat)を作成します。以下のAndroid NDKインストールディレクトリは環境に合わせて置き換えてください。
cd "Android NDKインストールディレクトリ"
make APP=%1
次にそのbatファイルがmake時に実行されるように設定を変更します。具体的にはプロジェクトメニューのプロパティを選択し、プロジェクトのビルド設定を開きます。デフォルトではビルドにmakeコマンドが使用される様になっていますが、それを変更して先ほど作成したbatファイルを指定します。つまり、eclipseでビルドを指定するとbatファイルが起動される様に指定します。
次にそのbatファイルに引数を渡す設定をします。つまり、"make APP=%1"の%1に渡すものを指定します。具体的にはbehaviorタブを指定し、ビルドターゲットをデフォルトのallからプロジェクト名に変更します。他に並列ビルドやエラー発生時の動作の指定がありますが、これを指定しても多分上手く動作しません。具体的にはmakeコマンドに渡す-jオプション(依存関係のないビルド内容をいくつ並列に動作させるかのオプション)と-kオプション(エラーが発生しても依存関係のない部分はできる限り継続して実施するオプション)の指定になっていて、作成したbatファイルではそれようの引数を準備していないからです。
Android NDKでアプリケーションをビルドする際には自分で全てのビルド環境を整えるmakefileを作成しても良いですが、通常はデフォルトのmakefileからアプリケーションのmakefileを起動する形式が一般的です。図にすると以下の様な形になっています。(以下は実装に基づいて記載していないため、Android NDKが提供しているmakefileの改造の参考にはしないでください。)
今回はC側のmakefileのみであるためApplication.mkはほとんど意味をなしませんが、両方を作成します。サンプルhello-jniのAndroid.mk、Application.mkを元に作成すると楽ですので、それをまず説明します。
これはベースとするディレクトリの宣言となり、$(call my-dir)はAndroid.mkが置いてあるディレクトリで置換されます。(別ディレクトリにソースを置く場合等は変更する必要があります)
このAndroid.mkでビルドする対象のライブラリを宣言します。(最終的にはlibここで宣言した名前.soというファイルが作成されます。)
ビルド対象となるソースを指定します。(複数ある場合はスペース区切りです)。
これは共有ライブラリを結果として出力する設定になっています。今回は実行ファイルを出力したいため、"include $(BUILD_EXECUTABLE)"に置き換える指定にします。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
これはプロジェクトのディレクトリの指定になります。$(call my-dir)はAndroid.mkが置いてあるディレクトリで置換されます。(別ディレクトリにプロジェクト(JAVAソース)を置く場合等は変更する必要があります)
このApplication.mkで必要とするライブラリを宣言します。(必要なAndroid.mkが実行されることになります。)
APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES := hello-jni
上記の通りに今度は自分のプロジェクト向けにAndroid.mkとApplication.mkを作成します。まずはAndroid.mkを作成します。プロジェクトを"Android NDKインストールディレクトリ/soureces/プロジェクト名"の配下に配置しなかった場合はeclipseからは実施できないので、手動でファイルを作成してください。具体的には以下の様に単純にプロジェクトにファイルを追加するだけです。
ソースとしてはmain.cファイルを追加するだけとして、以下の通りに記述します。(LOCAL_CFLAGS += -o0 -gはデバッグ情報付与と最適化禁止です。説明されていないLOCAL_LDLIBSが出てきますが、それは後述します。)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += -o0 -g
LOCAL_MODULE := HelloNDK
LOCAL_SRC_FILES := main.c
LOCAL_LDLIBS := -L/cygdrive/Android NDKをインストールしたディレクトリ/build/platforms/android-1.5/arch-arm/usr/lib -llog
include $(BUILD_EXECUTABLE)
通常、Android NDKで開発をする場合はJNIを使用してネイティブ側とJAVA側で連携をするため、JAVAのNATIVE関数宣言を元にC側のヘッダを出力するのですが、今回はネイティブ側のみの開発となるため、その部分は全く無視して作成します。また、通常は共有ライブラリとしてJAVA側から起動するので、エントリポイント(通常はmain関数)は不要ですが、今回は単体でネイティブアプリケーションとして動作させますので、作成する必要があります。
簡単のため、単純なC言語向けhello worldを作りましょう。ただし、標準出力に出力をしたとしても結果を確認できないため、Androidのログ機構へログ出力を行うことにします。(デバッガで確認はできますが、デバッガ頼りの開発はあまりおすすめできません。)ネイティブ側からのAndroidのログ機構アクセスは以下の様にしてできます。
ログ機構はライブラリファイルが必要なため、makefileでライブラリファイルを指定する必要があります。具体的には"LOCAL_LDLIBS := -llog"を追加するだけです。本来はこの手順だけで実施できるはずですが、windows上のcygwin環境ではどうも上手くライブラリのパスを指定してくれないようです。そのため、リンカの-Lオプションを指定してライブラリへのフルパスをcygwinのパス名で記載する必要があります。"-L/cygdrive/Android NDKをインストールしたディレクトリ/build/platforms/android-1.5/arch-arm/usr/lib"
Android NDK提供のmakefile側でインクルードパスは指定してくれますので、ただ"#include <android/log.h>"と書くだけです。
以下の関数でログを出力できます。第2,3引数の意味はJAVA側と同じです。第1引数の意味はLog.eなどの重要度の意味です。インクルードしたヘッダに詳細は書いてありますので、そちらを参照してください。(現段階ではこだわる必要はありません。)
__android_log_write(ANDROID_LOG_DEBUG,"Tag","Message");
それではCのソースファイルを追加しましょう。"Android NDKインストールディレクトリ/sources/プロジェクト名"にプロジェクトを構築している場合は以下のようにそのままeclipseから追加ができます。
以下が最終的なソースです。これができたら、eclipseのプロジェクトメニューからビルドを指定しましょう。環境が正しく構築されていれば、実行ファイルが作成されるはずです。
#include <android/log.h>
int main(void) {
__android_log_write(ANDROID_LOG_DEBUG,"HelloNDK", "congratulation!");
return 0;
}
また、ここでは不要ですがeclipseへのincludeパスの指定を行います。(Android NDKが提供しているmakefileがincludeパスを指定するため、ビルドには不要ですがコードアシスト等のためには指定した方が便利です)具体的にはビルドの設定の際と同様にプロジェクトの設定ダイアログを開き、Addを指定して"Android NDKのインストールディレクトリ\build\platforms\android-1.5\arch-arm\usr\include"を追加します。
実行についてはAndroid SDKで作成したアプリケーションを実行するのとあまり変わりませんが、eclipseのプラグインが対応していないため、batファイルを作ってそれを実行します。方針としては実機・エミュレータにadbコマンドのpushで実行ファイルを転送し、abdコマンドのshellを使用して実行ファイルに実行権を付けて、abdコマンドのshellを使用して実行ファイルを実行します。デバッグ時についてもだいたいは同じですが、デバッグ可能にするためにgdbを親プロセスとして実行します。(この方法だとあらかじめ実機・エミュレータが接続されていないと起動できません。エミュレータで実施する場合はちょっと不便ですが、Android SDKで作成したアプリケーション実行をすればエミュレータを立ち上げることはできますので、ここでは対応しません。)
上記の通りの方法であるため、Android SDKをあらかじめインストールしておき、toolのパスを環境変数に設定しておく必要があります。それについてはAndroid SDKのチュートリアルを参考にしてください。
具体的にはまず、以下の実行batファイルを作成します。ここではちょっと悪質ですが、アプリケーションのプロジェクトディレクトリにコピーする前に配置されるディレクトリ("Android NDKインストールディレクトリ\out\apps\HelloNDK\android-1.5-arm\"に配置されます)のファイルを利用します。(単純にmakefileを読みとってアプリケーションのプロジェクトディレクトリにアクセスするbatファイルの作成に手間がかかるためです)
adb root
adb push C:\android-ndk-1.5_r1\out\apps\%1\android-1.5-arm\%1 /data/data
adb shell chmod 755 /data/data/%1
adb shell /data/data/%1 %2 %3 %4 %5 %6
次に実行時にbatファイルが実行されるようにします。通常のCアプリケーションに対してeclipseはアプリケーションをそのまま実行するようになっているため、標準のeclipseの実行では追加できません。そのため、以下の様に外部ツールの構成を選択し、外部ツールにbatファイルを登録します。引数にはプロジェクト名=実行ファイル名を指定してください。また、引数を受け取るアプリケーションにしたい場合は追加で引数を入れればそれが渡されます。登録した後は登録と同じように実行できます。また、融通が利くように実行メニューにも後で追加します。
次にデバッグ用の準備をします。手順としては実機・エミュレータのgdb配下で実行させる。gdbで使用するポートをフォワーディングする。eclipseで実機・エミュレータのgdbとローカルのgdbとが連携をするように設定する。と言う手順になります。まずは実機・エミュレータのgdb配下で実行させるためのbatファイルを作成し、単純な実行と同じ様に実行設定に登録します。以下のポート番号は自分の環境に応じて適宜読み替えてください。
adb root
adb push C:\android-ndk-1.5_r1\out\apps\%1\android-1.5-arm\%1 /data/data
adb shell chmod 755 /data/data/%1
adb forward tcp:5039 tcp:5039
adb shell gdbserver tcp:5039 /data/data/%1 %2 %3 %4 %5 %6
次に実機・エミュレータのgdbとローカルのgdbとが連携するように設定します。つまり、デバッグ実行の設定でgdbserverと指定したポートで通信するということと、デバッグ用に使用するソースコードの位置、シンボル情報を指定してやることになります。具体的に見ていきます。まず、デバッグの構成を選択します。次にC/C++ Applicationに新規追加します。次にMainタブでプロジェクトに現在のプロジェクト名を入れて、C/C++アプリケーションに先ほどbatファイルで指定したのと同じ実行ファイルを指定します。
次にDebuggerタブで使用するデバッガを設定します。まずは、リモートで実行されているプログラムをデバッグするため、Debuggerでgdbserver Debugを選択します。次にAndroid NDKのものを使用する必要があるため、GDB Debuggerに"Android NDKインストールディレクトリ\build\prebuilt\windows\arm-eabi-4.2.1\bin\arm-eabi-gdb.exe"を指定します。
次にShared Librariesタブからプログラムで使用している共有ライブラリの位置を指定します。使用ライブラリにデバッグ情報が含まれていない場合はあまり意味は無いのですが、今回はliblog.soがあるディレクトリを追加します。
次にConnectionタブからデバッグ対象のプログラムを実行しているリモートマシンへの接続方式を指定します。ここではTCPの5039で接続するため、その旨指定します。(実際には実機にはUSBで接続していますが、フォワーディング設定によってローカルマシンのTCPの5039ポートに転送されますので、接続先はlocalhostになります。)
これだけの設定でも特に問題なく動作させることは可能ですが、デバッグは実機・エミュレータ側の準備とgdbserverへの接続の二つの手順が入るため、面倒です。そこで、実行とデバッグについて標準的な方法で実施できるように設定します。具体的には実行の構成を選択し、Launch Groupに実行用の構成を新規追加し、そこでAddを選択し、前の手順で外部ツール側で設定した起動設定を追加します。
デバッグの追加についても同様にして、リモート側でのプログラムの実施、リモート側のgdbへの接続を指定します。ただし、Launch Groupは上から順に実行されるため、順序は守る必要があります。
デバッグ実行すると以下の様に通常のアプリケーションと同じようにデバッグできる。