S-JIS[2006-06-18/2008-02-07] 変更履歴

JNIのC言語/C++側のコーディング


jni.hの中でC言語/C++で使える構造体 (クラス)が定義されているが、
コンパイラーがC言語の場合とC++の場合では定義のされ方が異なり、使い方も少し異なる。

JniJikken.c: JniJikken.cpp:

#include "JniJikken.h"

JNIEXPORT jbyteArray JNICALL Java_JniJikken_copy
  (JNIEnv *env, jobject me, jstring srcj)
{
	/* ここにコーディングしていく */
}

JNIEnvという構造体に、C言語/C++に渡されたJavaのオブジェクトを扱う関数が定義されている。
その関数は、以下のような形式で呼び出す。

C言語の場合 C++の場合
 (*env)->関数(env, 引数…)   env->関数(引数…)  

envの使い方が違うだけで、関数や関数独自の引数は同じ。

関数の一覧および解説は、Javadoc?にある。


コーディング例

渡された文字列の中身をバイト配列に入れて返す例。

※実行時に不用意にNULLを処理したりするとDLLやSOが落ちる(JavaのようにNullPointerExceptionになったりはしない)ので、エラーチェックはきちんとした方がよい
(JNI関数でNULLが返ってくるような事態は、例外発生状態だと思ってよいのではないだろうか)

C言語の場合 C++の場合 Javaで同様のものを書いたイメージ(サンプル)
JniJikken.c: JniJikken.cpp: JniJikken.java:
#include "JniJikken.h"

JNIEXPORT
jbyteArray JNICALL Java_JniJikken_copy
  (JNIEnv *env, jobject me, jstring srcj)
{
    const char *src;
    jsize len;
    jbyteArray dstj;
    jbyte *dst;
    int i;

    src = (*env)->GetStringUTFChars(env, srcj, NULL);
    if (src == NULL) return NULL;
    len = (*env)->GetStringUTFLength(env, srcj);

    dstj = (*env)->NewByteArray(env, len);
    if (dstj == NULL) {
        (*env)->ReleaseStringUTFChars(env, srcj, src);
        return NULL;
    }
    dst = (*env)->GetByteArrayElements(env, dstj, NULL);
    if (dst == NULL) {
        (*env)->ReleaseStringUTFChars(env, srcj, src);
        return NULL;
    }

    for(i = 0; i < len; i++){
        dst[i] = src[i];
    }

    (*env)->ReleaseByteArrayElements(env, dstj, dst, 0);
    (*env)->ReleaseStringUTFChars(env, srcj, src);

    return dstj;
}

 

#include "JniJikken.h"

JNIEXPORT
jbyteArray JNICALL Java_JniJikken_copy
  (JNIEnv *env, jobject me, jstring srcj)
{






    const char* src = env->GetStringUTFChars(srcj, NULL);
    if (src == NULL) return NULL;
    jsize len = env->GetStringUTFLength(srcj);

    jbyteArray dstj = env->NewByteArray(len);
    if (dstj == NULL) {
        env->ReleaseStringUTFChars(srcj, src);
        return NULL;
    }
    jbyte* dst = env->GetByteArrayElements(dstj, NULL);
    if (dst == NULL) {
        env->ReleaseStringUTFChars(srcj, src);
        return NULL;
    }

    for(int i = 0; i < len; i++){
        dst[i] = src[i];
    }

    env->ReleaseByteArrayElements(dstj, dst, 0);
    env->ReleaseStringUTFChars(srcj, src);

    return dstj;
}

 



public static
byte[] Java_JniJikken_copy
  (Object me, String srcj) throws Exception
{






    byte[] src = srcj.getBytes("UTF-8");

    int len = src.length;

    byte[] dstj = new byte[len];




    byte[] dst = new byte[dstj.length]; System.arraycopy(dstj,0,dst,0,dst.len);





    for (int i = 0; i < len; i++) {
        dst[i] = src[i];
    }

    System.arraycopy(dst,0,dstj,0,dstj.len); dst = null;
    src = null;

    return dstj;
}

呼び出す側の例。

JniJikken.java

public class JniJikken {

	private native byte[] copy(String src);

	public static void main(String[] args) {
		String src = "ABCテスト";

		JniJikken me = new JniJikken();
		byte[] dst = me.copy(src);		//→Java_JniJikken_copy(me, src)という感じで呼ばれる
	
		System.out.println(dst.length);
		for (int i = 0; i < dst.length; i++) {
			System.out.println(i + ":" + hex(dst[i]));
		}
	}

	private static String hex(byte n) {
		StringBuffer sb = new StringBuffer(2);
		sb.append("0123456789abcdef".charAt((n >> 4) & 15));
		sb.append("0123456789abcdef".charAt(n & 15));
		return sb.toString();
	}
}

JNI関数の概要

プリミティブ型・参照型

Javaのプリミティブ型(intやbyte)に対し、(先頭に「j」を付けた)jintやjbyteといったプリミティブに該当する型が用意されている。
サイズや符号の有無も一致するように定義されている。(booleanだけは違う)

オブジェクト型は、jobjectやjclassなどが用意されている。
C言語においては全てjobjectの別名(typedef)であり、C++では派生関係になっている。
実際のところ jobject等はポインターなので、代入したりNULLと比較したりすることが出来る。


String型

Javaは文字列をUNICODEで扱っているので、JNIでも基本的にはUNICODEで操作する。
ただし名前に「UTF」と付いているJNI関数があって、これはUTF-8で操作する。
半角文字(ASCII)ならUTF-8でもSJIS(MS932)でも同じ文字コードだから、UTF付きの関数で扱うのが楽。

操作 Java JNI(C++の例) 備考
新規文字列生成 String strj = new String("abc"); jstring strj = env->NewString(UNICODEの配列, 文字数);
jstring strj = env->NewStringUTF("abc");
 
文字列の長さ int lenj = strj.length(); jsize lenj = env->GetStringLength(strj);
jsize lenj = env->GetStringUTFLength(strj);
 
バイト列取得 byte[] bytes = strj.getBytes("UTF-8"); const char *bytes = env->GetStringUTFChars(strj, NULL); 最後に解放する必要がある。
バイト列解放 bytes = null; env->ReleaseStringUTFChars(strj, bytes);  
文字取得 char cj = strj.charAt(n); const jchar* chars = env->GetStringChars(strj, NULL);
jchar cj = chars[n];
env->ReleaseStringChars(strj, chars);
 

でもやっぱりSJISの全角文字を扱いたいなーということで、SJIS用の関数を作ってみた。[2006-06-24]

操作 自作SJIS関数(C++用) 備考(同等のJava)
新規文字列生成 jstring strj = NewStringMS932(env, "あいうえお"); String strj = new String(「あいうえお」のバイト配列, "MS932");
SJIS文字列取得 int len;
const char *sjis = GetStringMS932Chars(env, strj, &len);
const char *sjis = GetStringMS932Chars(env, strj, NULL);
int len = strlen(sjis);
byte[] sjis = strj.getBytes("MS932");
int len = sjis.length;
SJIS文字列解放 ReleaseStringMS932Chars(env, strj, sjis); sjis = null;

これらの関数は、例外発生中でも使える。
ただし、これらの関数内部で新たに例外が発生した場合、新しい方が有効となる。
(古い方を有効にしたいなら、これらの関数呼び出し前にExceptionOccurredを呼んで保持しておいて、事後に戻す)
また、何かエラーが発生した場合にはNULLを返す。

GetStringMS932Charsで取得した文字列は、ReleaseStringMS932Charsで解放する必要がある。
見ての通り、引数にはReleaseStringUTFCharsと 同様にenvや元のJava文字列を渡すようにしてあるが、実際はfree()しているだけ(笑))

/*
	SJIS文字列から、新しいStringを生成する。
	2006-11-09 ローカル参照の終了を追加
*/
jstring NewStringMS932(JNIEnv *env, const char *sjis)
{
	if (sjis==NULL) return NULL;

	jthrowable ej = env->ExceptionOccurred();
	if (ej!=NULL) env->ExceptionClear(); //発生中の例外をクリア

	int len = 0;
	jbyteArray arrj = NULL;
	jstring encj    = NULL;
	jclass clsj     = NULL;
	jmethodID mj    = NULL;
	jstring strj    = NULL;

	len = strlen(sjis);
	arrj = env->NewByteArray(len);
	if (arrj==NULL) goto END;
	env->SetByteArrayRegion(arrj, 0, len, (jbyte*)sjis);

	encj = env->NewStringUTF("MS932");
	if (encj==NULL) goto END;

	clsj = env->FindClass("Ljava/lang/String;");
	if (clsj==NULL) goto END;
	mj = env->GetMethodID(clsj, "<init>", "([BLjava/lang/String;)V");
	if (mj==NULL) goto END;

	strj = (jstring)env->NewObject(clsj, mj, arrj, encj);
	if (strj==NULL) goto END;

	if (ej!=NULL) env->Throw(ej); //発生していた例外を戻す
END:
	env->DeleteLocalRef(ej);
	env->DeleteLocalRef(encj);
	env->DeleteLocalRef(clsj);
	env->DeleteLocalRef(arrj);

	return strj;
}

/*
	SJISの文字列を返す。
	この文字列はヌルターミネートされている('\0'で終わっている)。
	使用後は、取得した文字列をReleaseStringMS932Charsで解放すること。
	2006-11-09 ローカル参照の終了を追加
*/
const char* GetStringMS932Chars(JNIEnv *env, jstring strj, int *pLen)
{
	if (strj==NULL) return NULL;

	jthrowable ej = env->ExceptionOccurred();
	if (ej!=NULL) env->ExceptionClear(); //発生中の例外をクリア

	int len = 0;
	char *sjis = NULL;
	jclass     clsj = NULL;
	jbyteArray arrj = NULL;
	jmethodID  mj   = NULL;

	jstring encj = env->NewStringUTF("MS932");
	if (encj==NULL) goto END;

	clsj = env->GetObjectClass(strj);
	if (clsj==NULL) goto END;

	mj = env->GetMethodID(clsj, "getBytes", "(Ljava/lang/String;)[B");
	if (mj==NULL) goto END;
	arrj = (jbyteArray)env->CallObjectMethod(strj, mj, encj);
	if (arrj==NULL) goto END;

	len = env->GetArrayLength(arrj);
	if (len<0) goto END;
	sjis = (char*)malloc(len + 1);
	if (sjis==NULL){
		//env->FatalError("malloc");
		goto END;
	}
	env->GetByteArrayRegion(arrj, 0, len, (jbyte*)sjis);
	sjis[len] = '\0';
	if (pLen!=NULL) *pLen = len;

	if (ej!=NULL) env->Throw(ej); //発生していた例外を戻す
END:
	env->DeleteLocalRef(ej);
	env->DeleteLocalRef(encj);
	env->DeleteLocalRef(clsj);
	env->DeleteLocalRef(arrj);

	return sjis;
}

/*
	GetStringMS932Charsによって取得した文字列を解放する。
*/
void ReleaseStringMS932Chars(JNIEnv *env, jstring strj, const char *sjis)
{
	free((void*)sjis);
}

配列型

Javaのプリミティブ型の配列は、byte[]・int[]等に応じて jbyteArray・jintArrayといった型が用意されている。
これらは、C++ではjarrayの派生クラスであり、C言語ではjarrayの別名(typedef)である。

配列を操作する関数は、関数名に型の名前が入っているものがそれぞれ用意されている。

操作 Java JNI(C++の例) 備考
新規配列生成 byte[] arrj = new byte[8]; jbyteArray arrj = env->NewByteArray(8);  
配列の長さ int ct = arrj.length; jsize ct = env->GetArrayLength(arrj);  
要素列取得 byte[] elems = new byte[arrj.length];
System.arraycopy(arrj, 0, elems, 0, arrj.length);
jbyte* elems = env->GetByteArrayElements(arrj, NULL); 最後に解放する必要がある。
要素列解放 elems = null; env->ReleaseByteArrayElements(arrj, elems, 0); mode参照。
一部要素取得 byte[] elems = new byte[4];
System.arraycopy(arrj, 2, elems, 0, 4);
env->GetByteArrayRegion(arrj, 2, 4, elems);  
一部要素設定 System.arraycopy(elems, 0, arrj, 2, 4); env->SetByteArrayRegion(elems, 0, arrj, 2, 4);  

Get〜ArrayElements関数(やGetStringUTFChars関数等)では、最後の引数isCopyに値が返ってくる。(必要ない場合はNULLを渡せばよい)

	jboolean isCopy;
	jbyte* elems = env->GetByteArrayElements(arrj, &isCopy);

isCopyの意味は、以下のようなもの。

isCopy 説明
JNI_TRUE(1) 生成された(返された)要素列は、Java配列のコピーである。
したがって、要素列の内容を変更しても、Java配列の中は直接変更されない。
反映させるにはRelease時にモードを0にする。(Stringは不変なのでモードは無い)
JNI_FALSE(0) 生成された(返された)要素列は、Java配列のコピーではない。
したがって、要素列の内容を変更すると、Java配列の中が直接変更される。たぶん


Release関数では、最後の引数にモードを指定する。

mode 説明 備考
0 要素列をJava配列に反映させ、要素列バッファを解放する。 反映されるのは、要素列がJava配列の内容のコピーの場合のみ。
JNI_COMMIT 要素列をJava配列に反映させるが、要素列バッファを解放しない。
JNI_ABORT 要素列をJava配列に反映させず、要素列バッファを解放する。  

プリミティブ型でないクラスの配列は、少々面倒かもしれない。

操作 Java JNI(C++の例) 備考
新規配列生成 Object[] arrj = new java.lang.Object[8]; jclass clsj = env->FindClass("Ljava/lang/Object;");
if (clsj==NULL) return;
jobjectArray arrj = env->NewObjectArray(8, clsj, NULL);
 
配列の長さ int ct = arrj.length; jsize ct = env->GetArrayLength(arrj); プリミティブ型の配列と共通
要素取得 Object elemj = arrj[2]; jobject elemj = env->GetObjectArrayElement(arrj, 2);  
要素設定 arrj[2] = elemj; env->SetObjectArrayElement(arrj, 2, elem);  

クラスの取得インスタンス生成

オブジェクト型はJNIではjobjectしか用意されていない(当たり前と言えば当たり前だが)ので、具体的なオブジェクトに対してメソッドを呼んだり フィールドの取得/設定を行うにはJavaのリフレクションと同様の操作をする必要がある。

その際、真っ先に必要なのがクラスを取得すること。クラスはJNIではjclassとなる。

操作 Java JNI(C++の例) 備考
クラス名からクラス取得 Class clsj = Class.forName("java.lang.Object"); jclass clsj = env->FindClass("Ljava/lang/Object;");
if (clsj==NULL) return;
取得に失敗した場合はNULLが返ると共に例外がセットされる。
また、例外発生中に呼び出されるとエラー(NULLが返る)。
GetMethodID()GetFieldID()等のjclassにNULLを渡すとJavaVMが落ちるので、エラーチェックはちゃんとすべき。[2008-02-02]
インスタンスからクラス取得 Class clsj = obj.getClass(); jclass clsj = env->GetObjectClass(obj);  
インスタンス生成 Object obj = new java.lang.Object(); jclass clsj = env->FindClass("Ljava/lang/Object;");
if (clsj==NULL) return;
jmethodID cns = env->GetMethodID(clsj, "<init>", "()V");
if (cns==NULL) return;
jobject obj = env->NewObject(clsj, cns);
引数無しのコンストラクターでも、コンストラクター(のメソッド)を取得しなければならない。
「<init>」は、コンストラクターのメソッド名。
Object obj = new SampleObject(123); jclass clsj = env->FindClass("LSampleObject;");
if (clsj==NULL) return;
jmethodID cns = env->GetMethodID(clsj, "<init>", "(I)V");
if (cns==NULL) return;
jobject obj = env->NewObject(clsj, cns, 123);
デフォルトパッケージのクラスで、
引数がintのコンストラクターの例。
(C言語/C++なので、NewObjectの引数は可変長)
種類の判断 if (obj instanceof java.lang.Exception) { 〜 } class clsj = env->FindClass("Ljava/lang/Exception;");
if (clsj==NULL) return;
if (obj!=NULL && env->IsInstanceOf(obj, clsj) { 〜 }
objnullの場合、instanceoffalseだがenv->IsInstanceOf()JNI_TRUE(1)

JNIの場合、クラス名をシグニチャーと呼ばれる形式で表す。
パッケージ付きのクラス名に対し、区切りの「.」(ピリオド)を「/」(スラッシュ)に置き換え、先頭に「L」、末尾に「;」(セミコロン)を付けたもの。


オブジェクトのメソッド呼び出し

メソッドを呼び出すには、リフレクションと同様の手順を踏む必要がある。
(しかしリフレクションとは違って、privateなメソッドも呼び出すことが出来る。しかもスーパークラスのprivateメソッドまで取得できる。[2008-02-07]

リフレクションと異なり、メソッド呼び出しには戻り値の型に応じてJNI関数が用意されている。
オブジェクト型が返る場合はCallObjectMethod()、何も返さない場合はCallVoidMethod()、など。
staticなメソッドを呼び出す場合はCallStaticObjectMethod()、そうでない場合はCallObjectMethod()

操作 Java JNI(C++の例) 備考
メソッド取得 Method mj = clsj.getMethod("メソッド名", 引数の型の配列); jmethodID mj = env->GetMethodID(clsj, "メソッド名", "メソッドのシグニチャー");
if (mj==NULL) return;
 
jmethodID mj = env->GetStaticMethodID(clsj, "メソッド名", "メソッドのシグニチャー");
if (mj==NULL) return;
メソッド呼出 int rj = mj.invoke(obj, 引数の値の配列); jint rj = env->CallIntMethod(obj, mj, 引数…); 引数は可変個数(さすがC/C++!)
int rj = mj.invoke(null, 引数の値の配列); jint rj = env->CallStaticIntMethod(clsj, mj, 引数…);

jmethodIDは JavaのMethodクラス相当のポインターではあるが、jobjectの派生ではない。

メソッドを表すシグニチャーは、慣れないとややこしい。
(引数のシグニチャー)返り型のシグニチャー」 (引数の型を表すシグニチャーを丸括弧でくくり、直後に返り値の型のシグニチャーを付ける)という構造をしているが、単純なものはともかく、引数が複数になったりクラスが入ってきたりするとすぐ複雑になって 面倒。

メソッドのシグニチャーの例
Javaのメソッド メソッドのシグニチャー
public void sub(){ 〜 } ()V
public int getValue(){ 〜 } ()I
public void setValue(int val){ 〜 } (I)V
public String change(String str){ 〜 } (Ljava/lang/String;)Ljava/lang/String;

こういうときは、javapというコマンドがシグニチャーを表示してくれるので便利。

>cd クラスが在るディレクトリ
>javap -s クラス名			…指定したクラス内のメソッド・フィールドのシグニチャーを表示する

オブジェクトのフィールド操作

フィールド(プロパティ)を操作するにもメソッド呼び出しと同様の手順が必要となる。

リフレクションではフィールド名だけでフィールドを取得できるが、JNIの場合はフィールドのシグニチャーが必要。
フィールドのシグニチャーは単純で、intの場合は「I」。

値の取得・設定には型名が入ったJNI関数があるので、フィールドの型に応じた関数を呼ぶ。

操作 Java JNI(C++の例)
フィールドID取得 Field fj = clsj.getField("フィールド名"); jfieldID fj = env->GetFieldID(clsj, "フィールド名", "I");
if (fj==NULL) return;
jfieldID fj = env->GetStaticFieldID(clsj, "フィールド名", "I");
if (fj==NULL) return;
フィールド値取得 int rj = fj.getInt(obj); jint rj = env->GetIntField(obj, fj);
int rj = fj.getInt(null); jint rj = env->GetStaticIntField(clsj, fj);
フィールド値設定 fj.setInt(obj, rj); env->SetIntField(obj, fj, rj);
fj.setInt(null, rj); env->SetStaticIntField(clsj, fj, rj);

jfieldIDは JavaのFieldクラス相当のポインターではあるが、jobjectの派生ではない。


[2006-11-09]

ローカル参照の終了

通常のJavaソースの場合、ローカル変数にnullを代入することで、その変数(に入っていたインスタンス)への参照を終了させることが出来る。
あるいはメソッドが終了すれば、そのメソッド内部のローカル変数の参照は全て終了される。

JNIはC/C++のソースなので、同じようにNULLを代入したり関数を終了したりするだけでは、Java(JNI)としては参照が終了したのかどうか判断できない。

この為に、DeleteLocalRef()というJNI関数がある。この関数を呼ぶと、ローカル参照が終了したとみなされる。

	env->DeleteLocalRef(obj);
	obj = NULL;

ローカル参照は、「ローカル参照領域」というような場所に保持されているらしい。
当然このエリアには限りがあるので、オブジェクトを使わなくなったら積極的にDeleteLocalRef()を呼び出すのがいいと思う。
もっとも、JNIの呼び出しが終了すればローカル参照は全て解放されるので、すぐ終了する関数ならそんなに気にすることはないだろうけど。

グローバル参照


例外処理

JNIでの実行中は 例外が発生したかどうかの状態を保持しているが、例外が発生した時点で関数を抜けるようなことは無い。
(C++ならともかくCでは無理だろう…? でもlongjmpとか使えば出来なくはないのではないかと。
でも取得したメモリのReleaseとかをしないといけないから無理かなー。
それより、途中でcatchして処理したり出来ないから、絶対無理か)

try〜catchに当たる部分は 自分でコーディングする必要がある。
例外が発生したままJava側に制御が戻ると、普通に例外が発生したかのように扱われる。

static void throw_test(JNIEnv *env, jobject argj)
{
	if (argj == NULL) {
		jclass clsj = env->FindClass("Ljava/lang/RuntimeException;");
		if (clsj==NULL) return;
		env->ThrowNew(clsj, "argj is null");
		printf("throw exception\n");
		env->DeleteLocalRef(clsj);
		return;		//明示的にreturnしないと、後続がそのまま実行されてしまう
	}
	printf("throw_test end\n");
}

JNIEXPORT void JNICALL Java_JniJikken_throwTest
  (JNIEnv *env, jobject me)
{
	throw_test(env, NULL);
	if (env->ExceptionCheck()) {		//例外が発生したかどうかチェック(catchに相当)
		printf("exception occured\n");
		return;
	}
	printf("throwTest end");
}
操作 Java JNI(C++の例) 備考 更新日
例外を発生させる throw e; env->Throw((jthrowable)e);
return;
ThrowやThrowNewでは例外発生状態になるだけで、自動的に元の関数に帰るわけではない。
したがって、自分でreturnする必要がある。
2006-11-09
throw new RuntimeException("message"); jclass clsj = env->FindClass("Ljava/lang/RuntimeException;");
if (clsj==NULL) return;
env->ThrowNew(clsj, "message");
env->DeleteLocalRef(clsj);
return;
再throwする try{ 〜 }catch(Throwable e){ throw e; } if (env->ExceptionCheck()) return; ExceptionOccurred()がオブジェクトを返すかどうか」でも判断できるが、その場合は取得した例外インスタンスはローカル参照扱いなので、DeleteLocalRef()を呼ばないとリソースを無駄に使ってしまう。 2006-11-09
特定の種類の例外をcatchする try{ 〜 }catch(RuntimeException e){ 〜 } jclass reClsj = env->FindClass("Ljava/lang/RuntimeException;");
if (reClsj==NULL) return;

jthrowable ej = env->ExceptionOccurred();
if (ej!=NULL && env->IsInstanceOf(ej, reClsj){ 〜 }
比較対象のjclassは事前に取得しておくのがいいかも。
FindClassは例外発生中には使えないし、何箇所かで使うかもしれないから。
でも使い終わったらDeleteLocalRef()を呼ぼう。
2006-11-09
例外を無視する try{ 〜 }catch(Throwable e){ ; } env->ExceptionClear();    
致命的エラーを発生させる   env->FatalError("malloc() is null"); JavaVMでの回復を期待しないエラー。
これは自分でreturnしなくても、即座にJavaまで戻る。(Javaも終了する)
きっとlongjmpを使ってるんだ。
 

引数がString以外のコンストラクターの例外を投げる例。(C言語(C99))[2006-06-23]

/*
	throw new ArgIntException((int)value); …デフォルトパッケージ
*/
static void throwArgIntException(int value, JNIEnv *env)
{
	jclass cls = (*env)->FindClass(env, "LArgIntException;");
	if (cls == NULL) return;
	jmethodID cns = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
	if (cns == NULL) {
		env->DeleteLocalRef(cls);
		return;
	}
	jobject e = (*env)->NewObject(env, cls, cns, value);
	env->DeleteLocalRef(cls);
	if (e == NULL) return;
	(*env)->Throw(env, (jthrowable)e);
}

ちなみに、この関数では、JNIEnvが引数の並びの先頭ではない。
これは何故かというと、上記のC言語のソースをC++に変換するのがやりやすいから。

C++に変換する場合、「(*env)->」を「env->」に置換し、「(env, 」を「(」に置換するとC++のソースになる。
この際、自分で作った関数の先頭をJNIEnvにしてしまうと、その関数の呼び出し部分も置換されてしまってコンパイルエラーになる。だから先頭以外に置いておけば置換対象外となって楽というわけ。
(ちなみに引数なしのJNI関数だと「(*env)->関数(env)」という形式だから、上記の置換だけでは足りない。自分の関数もenv以外の引数が無い場合はJNI関数と区別できないから、これに関してだけは単純な置換は無理)

なお、伝統的な古いC言語では変数宣言を関数の途中で行うことはできないし、C++形式コメント「//」を使うこともできないが、C99規格なら問題ない。(そしてVC++.NETでのC言語モードは、古い…。C言語のソースをそのまま使うことを考えるより、C++に変換しちゃった方が楽なときがあるかもしれない)


一旦例外が起きると、それをクリアするまで、ExceptionOccurred・ExceptionDescribe・ExceptionClear以外のJNI関数は呼び出せなくなる(呼び出してもエラーになる(戻り値がNULLになる))。

試してみるとIsInstanceOfは使えるようだったが、仕様に「安全」と書かれていない以上、使うべきではないだろう。[2006-06-23]
したがって単純なcatchの羅列は、以下のようになる。

try{
	〜
}








catch(RuntimeException e){
	throw e;


}



catch(Exception e){









	throw new RuntimeException(e);

}






static void rethrow(JNIEnv *env)
{
	jthrowable e = env->ExceptionOccurred();
	if (e==NULL) return; //例外が発生していない場合は抜ける

	env->ExceptionClear(); //例外をクリアーしておかないと、FindClassもGetMethodIDも駄目

	jclass clsR = env->FindClass("Ljava/lang/RuntimeException;");
	if (clsR==NULL) return;
	if (env->IsInstanceOf(e, clsR)) {
		env->Throw(e);
		env->DeleteLocalRef(clsR);
		return;
	}

	jclass clsE = env->FindClass("Ljava/lang/Exception;");
	if (clsE==NULL) return;
	if (env->IsInstanceOf(e, clsE)) {
		env->DeleteLocalRef(clsE);
		jmethodID cns = env->GetMethodID(clsR, "<init>", "(Ljava/lang/Throwable;)V");
		if (cns==NULL) {
			env->DeleteLocalRef(clsR);
			return;
		}
		e = (jthrowable)env->NewObject(clsR, cns, e);
		env->DeleteLocalRef(clsR);
		if (e==NULL) return;
		env->Throw(e);
		return;
	}
	env->DeleteLocalRef(clsE);

	env->Throw(e); //例外をクリアしたので、その他の例外を再スローする必要あり
}

例外発生時に使ってもよい関数 [2007-10-13]


参考


JNIの作成手順へ戻る / ネイティブからの呼び出しへ行く / JNIエラーへ行く / Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま