フィールドの取得

ネイティブコードからクラスやオブジェクトのフィールドを取得する.
サンプル
インスタンスネイティブメソッドとスタティックネイティブメソッドの違い
JNIEnv
jobjectからjclassを取得する
フィールドIDの取得
フィールド値の取得
まとめ

サンプル

説明は後にしていきなりサンプルを示す.特にネイティブコードの最初の関数で雰囲気を掴めると思う.まず問題のフィールドを保持しているクラス.

public class Fields{
    /* �󥹥��ƥ��å��ʥե������ */
    public short publicFieldShort = 0;
    int pkgPrivateFieldInt = 1;
    protected long protectedFieldLong = 2L;
    private double privateFieldDouble = 3.0;

    /* �����ƥ��å��ʥե������ */
    public static boolean publicStaticFieldBoolean = true;
    static byte pkgPrivateStaticFieldByte = 4;
    protected static char protectedStaticFieldChar = 'a';
    private static float privateStaticFieldFloat = 5.0f;

    /* �󥹥��ƥ��å��ʥե�����ɤ�������륤�󥹥��󥹥᥽�å� */
    native short getPublicFieldShort();
    native int getPkgPrivateFieldInt();
    native long getProtectedFieldLong();
    native double getPrivateFieldDouble();

    /* �����ƥ��å��ʥե�����ɤ�������륤�󥹥��󥹥᥽�å� */
    native boolean getPublicStaticFieldBoolean();
    native byte getPkgPrivateStaticFieldByte();

    /* �����ƥ��å��ʥե�����ɤ�������륹���ƥ��å��᥽�å� */
    native static char getProtectedStaticFieldChar();
    native static float getPrivateStaticFieldFloat();

}

そしてネイティブコード.

#include "Fields.h"

/* �ʲ�6�Ĥϥ��󥹥��󥹥᥽�å� */
/* ���󥹥��󥹥᥽�åɤΰ�����JNIEnv *��jobject */
JNIEXPORT jshort JNICALL Java_Fields_getPublicFieldShort
(JNIEnv *env, jobject this){
  /* ���֥������ȤΥ��饹����� */
  jclass clazz = (*env)->GetObjectClass(env, this);
  /* �����������饹����ե������ID����� */
  jfieldID fid = (*env)->GetFieldID(env, clazz, "publicFieldShort", "S");
  /* �ե������ID����Ѥ��ƥ��֥������Ȥ���ե�����ɤ��ͤ���� */
  jshort val = (*env)->GetShortField(env, this, fid);
  return val;
}

JNIEXPORT jint JNICALL Java_Fields_getPkgPrivateFieldInt
(JNIEnv *env, jobject this){
  jclass clazz = (*env)->GetObjectClass(env, this);
  jfieldID fid = (*env)->GetFieldID(env, clazz, "pkgPrivateFieldInt", "I");
  jint val = (*env)->GetIntField(env, this, fid);
  return val;
}

JNIEXPORT jlong JNICALL Java_Fields_getProtectedFieldLong
(JNIEnv *env, jobject this){
  jclass clazz = (*env)->GetObjectClass(env, this);
  jfieldID fid = (*env)->GetFieldID(env, clazz, "protectedFieldLong", "J");
  jlong val = (*env)->GetLongField(env, this, fid);
  return val;
}

JNIEXPORT jdouble JNICALL Java_Fields_getPrivateFieldDouble
(JNIEnv *env, jobject this){
  jclass clazz = (*env)->GetObjectClass(env, this);
  jfieldID fid = (*env)->GetFieldID(env, clazz, "privateFieldDouble", "D");
  jdouble val = (*env)->GetDoubleField(env, this, fid);
  return val;
}

JNIEXPORT jboolean JNICALL Java_Fields_getPublicStaticFieldBoolean
(JNIEnv *env, jobject this){
  jclass clazz = (*env)->GetObjectClass(env, this);
  jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "publicStaticFieldBoolean", "Z");
  jboolean val = (*env)->GetStaticBooleanField(env, clazz, fid);
  return val;
}

JNIEXPORT jbyte JNICALL Java_Fields_getPkgPrivateStaticFieldByte
(JNIEnv *env, jobject this){
  /* ���֥������ȤΥ��饹����� */
  jclass clazz = (*env)->GetObjectClass(env, this);
  /* �����������饹����ե������ID����� */
  jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "pkgPrivateStaticFieldByte", "B");
  /* �ե������ID����Ѥ��ƥ��饹����ե�����ɤ��ͤ���� */
  jbyte val = (*env)->GetStaticByteField(env, clazz, fid);
  return val;
}

/* �ʲ���Ĥϥ����ƥ��å��᥽�å� */
/* �����ƥ��å��᥽�åɤΰ�����JNIEnv *��jclass */
JNIEXPORT jchar JNICALL Java_Fields_getProtectedStaticFieldChar
(JNIEnv *env, jclass clazz){
  /* ���饹����ե������ID����� */
  jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "protectedStaticFieldChar", "C");
  /* �ե������ID����Ѥ��ƥ��饹����ե�����ɤ��ͤ���� */
  jchar val = (*env)->GetStaticCharField(env, clazz, fid);
  return val;
}

JNIEXPORT jfloat JNICALL Java_Fields_getPrivateStaticFieldFloat
(JNIEnv *env, jclass clazz){
  jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "privateStaticFieldFloat", "F");
  jfloat val = (*env)->GetStaticFloatField(env, clazz, fid);
  return val;
}

以上を以下のクラスから呼び出す.

public class FieldsDriver{
    static{
	/* libFields.so������� */
	System.loadLibrary("Fields");
    }

    public static void main(String args[]){
	Fields fields = new Fields();

	/* �󥹥��ƥ��å��ʥե�����ɤ��ͤ���� */
	System.out.println("publicFieldShort = "
			   + fields.getPublicFieldShort());
	System.out.println("pkgPrivateFieldInt = "
			   + fields.getPkgPrivateFieldInt());
	System.out.println("protectedFieldLong = "
			   + fields.getProtectedFieldLong());
	System.out.println("privateFieldDouble = "
			   + fields.getPrivateFieldDouble());

	/* �����ƥ��å��ʥե�����ɤ��ͤ򥤥󥹥��󥹥᥽�åɤǼ��� */
	System.out.println("publicStaticFieldBoolean = "
			   + fields.getPublicStaticFieldBoolean());
	System.out.println("pkgPrivateStaticFieldByte = "
			   + fields.getPkgPrivateStaticFieldByte());

	/* �����ƥ��å��ʥե�����ɤ��ͤ򥹥��ƥ��å��᥽�åɤǼ��� */
	/* ���󥹥��󥹤���ƤӽФ� */
	System.out.println("protectedStaticFieldChar = "
			   + fields.getProtectedStaticFieldChar());
	/* ���饹����ƤӽФ� */
	System.out.println("privateStaticFieldFloat = "
			   + Fields.getPrivateStaticFieldFloat());

    }
}
これを実行すると以下が出力される.
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 *である.JNIEnv *は関数ポインタを差しているポインタへのポインタである.関数ポインタが差している関数がネイティブメソッドとJavaVM環境との橋渡しとなる関数郡である.これらの関数を使用することで,ネイティブメソッドから簡単にオブジェクトのフィールドを取得,設定したりメソッドを呼び出したりできる.

jobjectからjclassを取得する

で,この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 *が指す先も異なる.だから,ネイティブメソッドの呼び出し毎に指定する必要があるのである.

フィールドIDの取得

フィールドの値を取得するには,JNIEnv *にフィールドIDを指定して取得する.したがって,フィールドの値を取得する前にフィールドIDを取得する必要がある.フィールドIDの取得には,取得したいフィールドがインスタンスフィールドかスタティックフィールドかによって別の関数を使用する.インスタンスフィールドのフィールドIDを取得するには,
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とは異なる.
各型に対応するシグネチャを以下に示す.
シグネチャ一覧
シグネチャ
byteB
charC
doubleD
floatF
intI
longJ
shortS
voidV
booleanZ
クラスL<クラスの完全修飾名>;
配列[シグネチャ
メソッドのシグネチャ(<引数のシグネチャのリスト>)<返り値のシグネチャ>

なぜかlongのシグネチャはLではなくJである.クラス,配列,メソッドのシグネチャについては別項で記述する.

フィールド値の取得

さて,フィールド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が入ること以外はインスタンスフィールドと同様である.

まとめ

ちょっと説明がややこしくなったきがするので,まとめとしてフィールドの値取得までの簡単な流れを書いておく.

インスタンスフィールドの場合スタティックフィールドの場合
必要があればGetObjectClassでjclassを取得する
GetFieldIDでフィールドIDを取得GetStaticFieldIDでフィールドIDを取得
Get<Type>Fieldでフィールドの値を取得GetStatic<Type>Fieldでフィールドの値を取得

<Type>にはInt,Short,Byteなどが入る.

This article was written by Fujiko