フィールドの取得
ネイティブコードからクラスやオブジェクトのフィールドを取得する.
説明は後にしていきなりサンプルを示す.特にネイティブコードの最初の関数で雰囲気を掴めると思う.まず問題のフィールドを保持しているクラス.
そしてネイティブコード.
以上を以下のクラスから呼び出す.
これを実行すると以下が出力される.
publicFieldShort = 0 pkgPrivateFieldInt = 1 protectedFieldLong = 2 privateFieldDouble = 3.0 publicStaticFieldBoolean = true pkgPrivateStaticFieldByte = 4 protectedStaticFieldChar = a privateStaticFieldFloat = 5.0このサンプルは以下を示している.
- インスタンスフィールドとスタティックフィールドの取得方法の違い
- 型によるフィールドの参照方法の違い
- インスタンスネイティブメソッドとスタティックネイティブメソッドの違い
全てのネイティブメソッドの第一引数は
JNIEnv *envである.もちろん変数名は任意だが,envとするのがはやりかもしれない.
インスタンスネイティブメソッドの第二引数は
jobject thisである.やはり変数名は任意である.ここではthisとしてみた.というのは,第二引数はそのメソッドをメンバに持つインスタンスへの参照だからである.Javaではインスタンスメソッドを呼ぶとき,
object.method();などとするが,このobjectへの参照がネイティブメソッドの第二引数として渡される.同じインスタンスへのメソッドを呼ぶとは,
method();と書けるが,これはコンパイラが
this.method();として解釈する.そしてこのthisへの参照がやはりネイティブメソッドの第二引数として渡される.したがって,変数名をthisとするのが,私は好きだ.
さて,スタティックネイティブメソッドの第二引数は
jclass clazzである.変数名はやっぱり任意(くどい).ここでもclazzとしてみた.スタティックメソッドは
Clazz.staticMethod();などとするが,この指定したクラスへの参照がネイティブメソッドの第二引数として渡される.また,
object.staticMethod();として呼び出した場合もobjectのクラスへの参照が第二引数として渡される.これはサンプルのFieldsDriver.javaの
System.out.println("protectedStaticFieldChar = "
+ fields.getProtectedStaticFieldChar());
から分かる.
すべてのネイティブメソッドの第一引数はJNIEnv *である.JNIEnv *は関数ポインタを差しているポインタへのポインタである.関数ポインタが差している関数がネイティブメソッドとJavaVM環境との橋渡しとなる関数郡である.これらの関数を使用することで,ネイティブメソッドから簡単にオブジェクトのフィールドを取得,設定したりメソッドを呼び出したりできる.
で,このJNIEnvを使用して呼び出せる多くの関数のうち最初に紹介すべきは,
jclass (JNICALL *GetObjectClass)
(JNIEnv *env, jobject obj);
ではないだろうか.厳密に言えばGetObjectClassは関数へのポインタなのだが,ここでは便宜上関数と呼ぶ(以降も同様).で,GetObjectは引数に指定したオブジェクト(jobject obj)からそのオブジェクトのクラス(jclass)を返す.jclassは以下に説明するフィールドIDやメソッドIDを取得するために必要である.これはサンプルFields.cでも何度も出て来る.
(*env)->GetObjectClass(env, this);の部分である.なお,C++では,
jclass GetObjectClass(jobject obj) {
return functions->GetObjectClass(this,obj);
}
とすることで,
env->GetObjectClass(this);などと書くことができる.ところで,JNIEnv *は全てのネイティブメソッドの第一引数である.すなわち各ネイティブメソッドを呼び出す度に渡される.これは,JNIEnv *は現在のスレッドでのみ有効な値だからである.スレッドが異なればJNIEnv *が指す先も異なる.だから,ネイティブメソッドの呼び出し毎に指定する必要があるのである.
フィールドの値を取得するには,JNIEnv *にフィールドIDを指定して取得する.したがって,フィールドの値を取得する前にフィールドIDを取得する必要がある.フィールドIDの取得には,取得したいフィールドがインスタンスフィールドかスタティックフィールドかによって別の関数を使用する.インスタンスフィールドのフィールドIDを取得するには,
シグネチャ一覧
なぜかlongのシグネチャはLではなくJである.クラス,配列,メソッドのシグネチャについては別項で記述する.
jfieldID (JNICALL *GetFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
を使用する.サンプルFields.cでは
jfieldID fid = (*env)->GetFieldID(env, clazz, "publicFieldShort", "S");などとしている.そしてスタティックフィールドのフィールドIDを取得するには,
jfieldID (JNICALL *GetStaticFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
を使用する.引数の説明を以下に示す.
- 第一引数はJNIEnv *であり,これはネイティブメソッドの第一引数をそのまま渡せばよい.
- 第二引数では,どのクラスのフィールドIDを取得したいかを指定する.あるオブジェクトのフィールドを取得したい場合には,JNIEnvのGetObjectClassを使用してjclassを取得してそれを渡す.
- 第三引数はJavaコードでのフィールドの変数名をそのまま渡す.
- 第四引数はフィールドの型をシグネチャで指定する.Javaでシグネチャというと,メソッド名,引数の個数,各引数の型の組み合わせだが,ネイティブメソッドではフィールドにもシグネチゃがあり,メソッドのシグネチャの意味もJavaとは異なる.
| 型 | シグネチャ |
|---|---|
| byte | B |
| char | C |
| double | D |
| float | F |
| int | I |
| long | J |
| short | S |
| void | V |
| boolean | Z |
| クラス | L<クラスの完全修飾名>; |
| 配列 | [シグネチャ |
| メソッドのシグネチャ | (<引数のシグネチャのリスト>)<返り値のシグネチャ> |
さて,フィールドIDの取得方法が分かったところで,ようやくフィールドの値を取得することができる.フィールドの値はJNIEnv *からたどることができる関数を使用して取得できる.
まず,インスタンスフィールドの値は
<type> (JNICall *Get<Type>Field) (JNIEnv *env, jobject obj, jfieldID fieldID);のパターンで示すことのできる関数を使用する.例えばint型のインスタンスフィールドの値を取得したければ,
jint val = (*env)->GetIntField(env, this, fid);などとする.typeにフィールドの型を当てはめるのである.すなわち取得したいフィールドの型によって関数名と返り値が異なる.第二引数はフィールド値を取得したいインスタンスを指定する.そして第三引数にフィールドIDを指定するのである.
スタティックフィールドの値は
<type> (JNICALL *GetStatic<Type>Field)
(JNIEnv *env, jclass clazz, jfieldID fieldID);
のパターンで示すことのできる関数を使用する.例えばchar型のスタティックフィールドの値を取得したければ,
jchar val = (*env)->GetStaticCharField(env, clazz, fid);などとする.関数名にStaticが入ること以外はインスタンスフィールドと同様である.
ちょっと説明がややこしくなったきがするので,まとめとしてフィールドの値取得までの簡単な流れを書いておく.
<Type>にはInt,Short,Byteなどが入る.
| インスタンスフィールドの場合 | スタティックフィールドの場合 |
|---|---|
| 必要があればGetObjectClassでjclassを取得する | |
| GetFieldIDでフィールドIDを取得 | GetStaticFieldIDでフィールドIDを取得 |
| Get<Type>Fieldでフィールドの値を取得 | GetStatic<Type>Fieldでフィールドの値を取得 |

