忍者ブログ
  • 2024.10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.12
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/11/22 20:20 】 |
JNI
Android5.0から、VMが変わった(Darvik -> ART)
その為、過去のコードだと実行時例外がポンポン発生するように

以下のページをよく見て
公式


JNIはベンダーに依存しない。
動的にshared libraryをロードする。
時に扱いづらいが、概ね効率的だ。


JNIには、2つの重要なデータ構造がある
JavaVM
JNIEnv

どちらも、関数テーブルやメンバー関数へのポインタを保持するクラス。

JavaVMクラスは、"invocation interface" functions を提供し、それを使ってJavaVMクラスを生成、破棄できる
理屈的には、JavaVMクラスはプロセスごとに1つ持たせることができるが、Androidは1つしか許可していない

JNIEnvクラスは、JNIのほとんどのメソッドを提供している
ネイティブ関数呼び出しは、必ず第1引数がJNIEnvになる

JNIEnvはスレッド固有に保持する為、スレッド間共有は不可
どうしても必要な場合は、JavaVMの方を共有し、JavaVM.GetEnv()で取得すること

CとC++では、JavaVM,JNIEnvの定義が異なる。
jni.hの中身を見るとわかる。
C/C++が混在する場合、ヘッダファイルからjni.hをインクルードするときは要注意



スレッドについて

スレッドとは、Linuxスレッドである。
通常はコード上から作成する(java.lang.Thread.start)。
それ以外の手で作られたスレッドの場合、JNIに明示的にアタッチする必要がある(AttachCurrentThread または AttachCurrentThreadAsDaemon関数。なおすでにアタッチ済みのスレッドに対してAttachCurrentThreadを実行しても、何も起こらない)。
そうしないと、そのスレッドにはJNIEnvが生成されず、JNI呼び出しができない。


ネイティブコードからJavaオブジェクトのフィールドにアクセスする場合
FindClass
GetFieldID
GetIntField
の順に使用する

メソッドを呼び出す場合
FindClass
GetMethodID
の順に使用する

IDとは通常ポインタのことである。
文字列比較によって検索している。最初は検索するので遅いが、2度目以降は早い
はじめに一度検索しておき、結果をキャッシュしておくことは有効だ

クラス参照は、クラスがアンロードされるまでは保証される。クラスがアンロードされることは滅多にないが、Androidでは起きうる。ClassLoaderに関連づいているクラスが全てガベージコレクトされた時にアンロードされる。
jclass オブジェクトは、クラス参照である


フィールドやメソッドのIDをキャッシュし、クラスがアンロード&リロードされた時に再キャッシュする正しいコードは以下

private static native void nativeInit();

static {
nativeInit();
}





LocalReference と GlobalReference
ネイティブのメソッドに引数として渡ってくる変数は、ローカル参照
そのメソッドが終了すると向こうになる。普通のローカル変数と同じ。(参照元のJavaオブジェクトが生存していても、参照は無効になる)
jobject の子クラスである jclass, jstring, jarray などは全てこれ。

グローバル参照は、明示的に削除しない限りずっと参照を保持し続ける

使用例

jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast(env->NewGlobalRef(localClass));


まずローカル参照を取得し、それをグローバル参照に変換する。
ローカル参照かグローバル参照かで型が変わるわけではない。

全てのJNIメソッドは、ローカル参照もグローバル参照も引数として受け付ける(型が同じだから当然だろう)。

複数の参照が同じオブジェクトを指しているかどうかを判定するには、IsSameObject メソッドを使用すること。==で判定してはいけない。

jfieldIDとjmethodIDは、オブジェクトへの参照ではないため、NewGlobalRefしてはいけない
GetStringUTFChars や GetByteArrayElementsも同様である

ローカル参照は、明示的にdeleteする必要がある。
ローカル参照が自動的にdeleteされるのは、スレッドが削除された時である。



UTFについて

JavaはUTF16を使っている。
JNIはM-UTF8も対応している

M-UTF8についてはこちら参照。簡単に言うとJava独自のUTF8。
M-UTF8は、0x0000を0xc0, 0x80という2文字に変換する。
これの利点は、通常のC文字列同様、NULL文字(0x00)を文字列の終端として扱うことができる。(0x0000のままだと、この文字をC文字列終端と認識してしまうから)
欠点は、UTF8からの変換の手間が必要な事。

可能なら、UTF16を使うのが早い。

GetStringCharsは、jstringをUnicode配列に変換する
GetStringUTFChars は、jstringをMUTF8に変換する

NewStringUTFに渡す文字列は、MUTF8でないといけない。
ここが要注意。ASCII文字列しかないとわかっていれば良いが、そうでなければ変換をする事
でないと、内部でUTF16変換した時、望んでいない結果になる



例外について

例外処理を怠ると、実行時エラーで落とされる

if (methodInfo.env->ExceptionCheck()) {
methodInfo.env->ExceptionDescribe();
methodInfo.env->ExceptionClear();
}


jclass _clazz = (jclass) env->CallObjectMethod(cocos2d::JniHelper::classloader,
cocos2d::JniHelper::loadclassMethod_methodID,
_jstrClassName);

if (nullptr == _clazz) {
LOGE("Classloader failed to find class of %s", className);
env->ExceptionClear();
}


など。

PR
【2016/07/25 15:13 】 | Android | 有り難いご意見(0)
<<cocos2d-xのライブラリを修正する時は | ホーム | AndroidStudioでcocos2d-xのプロジェクトを動かした話 その2>>
有り難いご意見
貴重なご意見の投稿














<<前ページ | ホーム | 次ページ>>