今日もプログラミング

IT技術とかプログラミングのこととか特にJavaを中心に書いていきます

JNIでC側のポインタを保持する方法について

Cのポインタを保持したい

現在embulk-output-oracleを高速化するために、JNI(Java Native Interface)を使ってプログラミングしている。

 

その時ちょっと悩んだのが、Cのポインタをどうやって保持するか、だ。

フローとしては、

Java C
初期化処理 必要なメモリを確保
処理本体 メモリを使っていろいろ処理
後処理 メモリを解放

のような感じだ。

初期化時に確保したメモリのポインタを、後続の処理に引き継ぐ必要がある。

 

どうやってポインタを保持するか

まず、C側でグローバル変数で持つ方法。

これだと並列処理に対応できないからだめだ。

 

Java側にポインタを返し、後続の処理で渡し直してもらう方法がいいだろう。

と思ったが、Javaにはポインタ型というのが無い。intとかlongにキャストすればいいのかな?とも思ったが、処理系依存になるし気持ち悪いなあ。

 

ちょっと面倒になるが、Java側にはintのIDを返し、C側でIDとポインタとのマップを管理する方法も考えられる。

STL(Standard Template Library)を使えば、mapも簡単。

と思ったが…、どうもスレッドセーフではないっぽい。別のライブラリで排他制御を行わないといけないようだ。

Cで排他制御をおこなうライブラリは…?うーん、めんどくさくなってきた。

 

バイト配列で保持すればいいんじゃないだろうか

結局、処理系によってポインタのサイズは違うかもしれないが、バイト配列で持っちゃえばいいんじゃないかな?と考えたのだ。

 

ポインタからバイト配列の変換はこんな感じ。

JNIEXPORT jbyteArray JNICALL initialize(JNIEnv *env, jobject)
{
    void *buffer = ...
    jbyteArray pointer = env->NewByteArray(sizeof(void*));
    env->SetByteArrayRegion(pointer, 0, sizeof(void*), (jbyte*)&buffer);
    return pointer;
}

 

Java側では byte[] pointer のように持つことができる。

 

バイト配列をポインタに戻すのはこんな感じ。

JNIEXPORT void JNICALL release(JNIEnv *env, jobject, jbyteArray pointer)
{
    void *buffer;
    env->GetByteArrayRegion(pointer, 0, sizeof(buffer), (jbyte*)&buffer);
    ...
}

 

一手間掛かるけど、これなら処理系によらずJava側のコードは統一でよい、はず。たぶん。