• スポンサードリンク

JNI_OnLoad・・・って何やねん〜その1

Android NDK

検索履歴を見ていると、JNI_OnLoadで検索している人がポツポツいたのでちょっとだけメモ(^^)v

JavaからJNI経由でnativeコードを呼びだそうとして共有ライブラリをビルドしていざ実行すると

みたいなメッセージがlogCatに表示されることが有ります。
先輩魔法使い達の意見は違うかもしれませんが、共有ライブラリのロード時に初期化が必要なければ、自分的には気にしないで無視していいんじゃないかと思います。
もっとも自分が共有ライブラリを作る場合にはほぼ必ずと行っていいほどこの関数を定義しています。

そもそも何やねんこいつは

・・・ってそのまんまです(-_-;)Javaから共有ライブラリを読み込んだ時に1度だけ実行される初期化用の関数です。JNI_OnLoadが定義されていない共有ライブラリをJavaから読み込んだ時に、上記の「No JNI_OnLoad found in …」が表示されます。
ちなみに、終了処理を行うためのJNI_OnUnloadも有りますが、自分では定義したことはありません。だってアプリが終了する時以外アンロードしないんだもん。アプリが終了する時は、少なくとも実機の場合はVMごとバッサリ破棄されてしまうらしくアンロード処理もくそもありません。エミュレータの場合はもしかした呼ばれていたかも。

どうやって・何しに使うん?その1

JNI関数の定義その1

ネットで見かけるJNI関係のコードではたいていの場合、手作業またはjavahというユーティリティーコマンドを使ってJNIの呼び出し規約に沿った名称の関数名のプロトタイプ宣言を作成しています。
この場合には、共有ライブラリの読込みの際に、VMが自動的にJavaのクラス・メソッドとnative側の関数の対応付けを行ってくれるので、JNI_OnLoadはあまり必要ないかもしれません。・・・自動的に対応付けを行うためにわざわざJNIの呼び出し規約が作られていると言ってもいいかもしれません。

例えば、com.example.testapp.MainActivityというJavaのクラスに、public int hoge(int arg)というネイティブメソッドを定義しようとすると、Java側は簡単で

のように、関数名の前にnativeを付加するだけです。
後はスタティックイニシャライザでもってSystem#loadLibraryで共有ライブラリを呼び出せばOKです。
共有ライブラリ名が「libJniHoge.so」であれば、こんな感じになります。

対応するnative側の関数のプロトタイプ宣言は

となります。Java_を先頭につけてドット”.”をアンダーバー”_”で置き換えて全てをつなぎ合わせた形になります。
こうやってドットをアンダーバーに置き換えてしまうので、少なくともJNIを使う場合には、クラス名・関数名にアンダーバーを含めることは避けたほうが無難です。
この、長ったらしいnative側の関数のプロトタイプ宣言を、Javaのソースファイルから生成してくれるのがjavahというユーティリティーらしいです。もちろん手動で作ってもいいのですが関数名が長くミスタイプしやすいのでjavahにお任せするのが定番です。上の関数は手動で変換したので…間違ってないかなぁ(-_-;)。もっとも自分はこの方法は殆ど使わないのでjavahも殆ど使ったことは無いです。

後は、native側で関数の中身を記入するだけです。
あ、よく忘れるのは、Cの関数として定義しないといけないということです。C++の関数としてはだめです。一見ちゃんと関数が定義されているように見えて実際にはUnsatisfied link errorが発生します。確実にCの関数にするには、ヘッダファイルで関数プロトタイプをextern “C”で囲ってあげます。
こんな感じ。

でも、この方法は定義する関数・メソッドが1個2個というレベルならいいですけど、十個二十個となるとコーディング・デバッグが大変です。まるでウォーリーを探せのように殆ど同じ名前の関数がズラリと勢揃いして目がチカチカしてきます。表現が古い?(^_^;)

JNI関数の定義その2

Javaのクラス・メソッドとnative側の関数の対応付けを行う方法はもう1つ有ります。
その方法は、プログラムから動的に対応付けを行う方法で、Androidの場合は、JNIEnv#RegisterNativesを使います・・・が一手間必要なのでヘルパー関数経由で使います。
自分の場合は、AOSPの中のJNIHelp.h/JNIHelp.cppからコピペしてきたこんな関数を使っています。

使う時は、こんな感じ。

native側の関数名は何でもいいんですが、今回はnativeHogeにしてみました。1つのファイルに複数のJavaクラスのnative関数を含める場合には、Java側のクラスがわかるように、例えばMainActivity_hogeみたいな感じで名前を付けるのがいいと思います。いずれにしてもJava側は最初の方法と全く同じです。

この登録用の関数(今回の例ではregister_hoge)をJNI_OnLoadから呼び出せばJavaのクラス・メソッドとnative関数の対応付けが出来ます。関数が増えた場合には上の例のmethods配列に関数を追加していきます。やっとJNI_OnLoadが出てきましたね\(^o^)/

こちらの方法の何がいいって、関数名を自由に決めれることです。当然C++の関数でも問題無いです。ただし、C++のクラス内のstaticではない関数は呼び出せません。なお、JNI_OnLoaddだけはCの関数にする必要があります。
自分の場合は殆どの場合こちらの方法を使うのでJNI_OnLoadが必須となります。

いっぱいコーディングしないといけないので面倒臭そうですか?でも1回作れば後は殆どコピペですからね。native関数が増えてくれば有り難みがわかると思います。AOSPやSDKに含まれるJavaクラスのnative側の関数はたいていこちらの方法で登録されています。

どうやって・何しに使うん?その2

もう1つ、JNI_OnLoadが必要になる場面、それはnative側で生成したスレッド内からJavaのメソッド・フィールドへアクセスする場合です。これはちょっとボリュームがある・・・はずなので、次回にします。
お疲れ様でした。

« »

  • スポンサードリンク