JNI を介して C と Java の間でポインタを渡す
-
06-07-2019 - |
質問
現時点では、CUDA 機能を使用する Java アプリケーションを作成しようとしています。CUDA と Java 間の接続は正常に機能しますが、別の問題が発生したため、それについての私の考えが正しいかどうかを尋ねたいと思いました。
Java からネイティブ関数を呼び出すと、それにデータを渡し、関数は何かを計算して結果を返します。最初の関数がこの結果への参照 (ポインタ) を返し、それを JNI に渡し、その結果を使ってさらに計算を行う別の関数を呼び出すことは可能ですか?
私のアイデアは、データを GPU メモリに残し、参照を渡すだけで他の関数がそれを使用できるようにすることで、GPU との間でデータをコピーすることで生じるオーバーヘッドを削減することでした。
しばらく試した後、ポインタはアプリケーションの終了後 (この場合、C 関数の終了時) に削除されるため、これは不可能であるはずだと自分で思いました。これは正しいです?それとも解決策を見つけるにはC言語が苦手なだけでしょうか?
編集:さて、質問を少し拡張して(またはより明確に)、次のようにします。JNI ネイティブ関数によって割り当てられたメモリは、関数の終了時に割り当て解除されますか?それとも、JNI アプリケーションが終了するか手動で解放するまでアクセスできますか?
ご意見ありがとうございます:)
解決
次のアプローチを使用しました:
JNIコードで、必要なオブジェクトへの参照を保持する構造体を作成します。この構造体を最初に作成するとき、javaへのポインターを long
として返します。次に、Javaからこの long
をパラメーターとして使用してメソッドを呼び出し、Cでそれを構造体へのポインターにキャストします。
構造はヒープ内にあるため、異なるJNI呼び出し間でクリアされません。
編集:アドレスは静的変数であるため、long ptr = (long)& address;
を使用できるとは思わない。 Gunslinger47が提案した方法で使用します。つまり、クラスまたは構造体の新しいインスタンスを作成し(newまたはmallocを使用)、ポインターを渡します。
他のヒント
C ++では、スタック、malloc / free、new / delete、またはその他のカスタム実装など、メモリを割り当て/解放するメカニズムを使用できます。唯一の要件は、1つのメカニズムでメモリのブロックを割り当てた場合、同じメカニズムでそれを解放する必要があるため、スタック変数で free
を呼び出せず、呼び出すことができないということです。 malloc
edメモリ上の delete
。
JNIには、JVMメモリの割り当て/解放のための独自のメカニズムがあります:
- NewObject / DeleteLocalRef
- NewGlobalRef / DeleteGlobalRef
- NewWeakGlobalRef / DeleteWeakGlobalRef
これらは同じルールに従いますが、唯一の問題は、ローカル参照を「まとめて」削除できることです。 PopLocalFrame
を使用して明示的に、またはネイティブメソッドが終了するときに暗黙的に。
JNIはメモリの割り当て方法を知らないため、関数の終了時にメモリを解放できません。スタック変数は、まだC ++を書いているために明らかに破壊されますが、GPUメモリは有効のままです。
その後の唯一の問題は、以降の呼び出しでメモリにアクセスする方法です。そしてGunslinger47の提案を使用できます:
JNIEXPORT jlong JNICALL Java_MyJavaClass_Function1() {
MyClass* pObject = new MyClass(...);
return (long)pObject;
}
JNIEXPORT void JNICALL Java_MyJavaClass_Function2(jlong lp) {
MyClass* pObject = (MyClass*)lp;
...
}
Javaはポインターの処理方法を知りませんが、ネイティブ関数の戻り値からポインターを格納し、別のネイティブ関数に渡して処理できるようにする必要があります。 Cポインターはコアの数値に過ぎません。
別の貢献者は、JNIの呼び出しの間にグラフィックメモリへのポインタがクリアされるかどうか、および回避策があるかどうかを通知する必要があります。
この質問はすでに正式に回答されていますが、解決策を追加したいと思います。
ポインターを渡そうとする代わりに、ポインターをJava配列(インデックス0)に入れてJNIに渡します。 JNIコードは、 GetIntArrayRegion
/ SetIntArrayRegion
を使用して配列要素を取得および設定できます。
私のコードでは、ファイル記述子(オープンソケット)を管理するためにネイティブレイヤーが必要です。 Javaクラスは int [1]
配列を保持し、それをネイティブ関数に渡します。ネイティブ関数は、それを使用して何でも(get / set)実行し、結果を配列に戻すことができます。
ネイティブ関数内で(ヒープ上に)メモリを動的に割り当てる場合、メモリは削除されません。つまり、ネイティブ関数への異なる呼び出し間で、ポインター、静的変数などを使用して状態を保持できます。
別の方法を考えてみてください:別のC ++プログラムから呼び出された関数呼び出しで安全に何ができますか?ここでも同じことが当てはまります。関数が終了すると、その関数呼び出しのスタック上のすべてが破棄されます。ただし、ヒープ上のすべては、明示的に削除しない限り保持されます。
簡単な答え:呼び出し元の関数に返す結果の割り当てを解除しない限り、後で再入力するために有効のままになります。完了したら、必ずクリーンアップしてください。
@denis-tulskiyからの受け入れられた回答は理にかなっていますが、私は個人的にからの提案に従いました ここ.
したがって、次のような疑似ポインタ型を使用する代わりに、 jlong
(または jint
32 ビット アーチのスペースを節約したい場合)、代わりに ByteBuffer
. 。例えば:
MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));
これは後で次のようにして再利用できます。
jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);
非常に単純なケースでは、このソリューションは非常に使いやすいです。次のものがあるとします。
struct {
int exampleInt;
short exampleShort;
} MyNativeStruct;
Java 側では、次のことを行うだけです。
public int getExampleInt() {
return bb.getInt(0);
}
public short getExampleShort() {
return bb.getShort(4);
}
これで書く手間が省けます たくさん 定型コードの!ただし、説明したようにバイト順序に注意する必要があります。 ここ.
Unsafe.allocateMemoryとまったく同じ方法でこれを行うのが最善です。
オブジェクトを作成してから、32/64ビットの符号なし整数である(uintptr_t)に入力します。
return (uintptr_t) malloc(50);
void * f = (uintptr_t) jlong;
これが唯一の正しい方法です。
Unsafe.allocateMemoryが行う健全性チェックは次のとおりです。
inline jlong addr_to_java(void* p) {
assert(p == (void*)(uintptr_t)p, "must not be odd high bits");
return (uintptr_t)p;
}
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
UnsafeWrapper("Unsafe_AllocateMemory");
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
if (sz == 0) {
return 0;
}
sz = round_to(sz, HeapWordSize);
void* x = os::malloc(sz, mtInternal);
if (x == NULL) {
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
//Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
return addr_to_java(x);
UNSAFE_END