web-dev-qa-db-ja.com

CとJavaの間でJNIを介してポインターを渡す

現時点では、CUDA機能を使用するJavaアプリケーションを作成しようとしています。 CUDAとJavaの間の接続は正常に機能しますが、別の問題があり、それについての私の考えが正しいかどうか尋ねました。

Javaからネイティブ関数を呼び出すと、データを渡すと、関数は何かを計算して結果を返します。最初の関数にこの結果への参照(ポインタ)を返させて、それをJNIに渡して、その結果でさらに計算を行う別の関数を呼び出すことは可能ですか?

私のアイデアは、データをGPUメモリに残し、他の関数が使用できるように参照を渡すだけで、GPUとの間でデータをコピーすることによるオーバーヘッドを削減することでした。

しばらく試してみましたが、アプリケーションの終了後に(この場合はC関数が終了したときに)ポインターが削除されるため、これは不可能だと思いました。これは正しいです?または、私は解決策を見るためにCで悪いだけですか?

編集:さて、質問を少し拡大する(またはより明確にする)ために:JNIネイティブ関数によって割り当てられたメモリは、関数の終了時に割り当て解除されますか?または、JNIアプリケーションが終了するか、手動で解放するまでアクセスできますか?

ご意見ありがとうございます:)

67
Volker

次のアプローチを使用しました。

jNIコードで、必要なオブジェクトへの参照を保持する構造体を作成します。この構造体を最初に作成するとき、ポインタをJavaとしてlongとして返します。その後、Java longをパラメーターとして使用し、Cでは構造体へのポインターにキャストします。

構造はヒープ内にあるため、異なるJNI呼び出し間でクリアされません。

編集:アドレスは静的変数なので、長いptr = (long)&address;を使用できるとは思わない。 Gunslinger47が提案した方法で使用します。つまり、クラスまたは構造体の新しいインスタンスを作成し(newまたはmallocを使用)、そのポインターを渡します。

44
Denis Tulskiy

C++では、スタック、malloc/free、new/delete、またはその他のカスタム実装など、メモリを割り当て/解放するメカニズムを使用できます。唯一の要件は、1つのメカニズムでメモリのブロックを割り当てた場合、同じメカニズムでそれを解放する必要があるため、スタック変数でfreeを呼び出せず、deleteedメモリでmallocを呼び出せないことです。

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;
    ...
}
15
Dan Berindei

Javaはポインターの処理方法を知りませんが、ネイティブ関数の戻り値からポインターを保存し、処理するために別のネイティブ関数に引き渡すことができるはずです。 Cポインターは、コアでの数値にすぎません。

別のコントリビューターは、JNI呼び出しの間にグラフィックメモリへのポインタがクリアされるかどうか、および回避策があるかどうかを通知する必要があります。

10
Gunslinger47

私はこの質問がすでに正式に回答されていることを知っていますが、解決策を追加したいと思います:ポインタを渡すのではなく、ポインタをJava配列(インデックス0で)に入れて渡しますJNIコードは、GetIntArrayRegion/SetIntArrayRegionを使用して配列要素を取得および設定できます。

私のコードでは、ファイル記述子(オープンソケット)を管理するためのネイティブレイヤーが必要です。 Javaクラスはint[1]配列を作成し、それをネイティブ関数に渡します。ネイティブ関数は、それを使用して(get/set)何でもでき、結果を配列に戻すことができます。

7
noamtm

@ denis-tulskiyから受け入れられた答えは理にかなっていますが、私は here からの提案に個人的に従ってきました。

したがって、jlong(または32ビットArchのスペースを節約したい場合はjint)などの疑似ポインタータイプを使用する代わりに、代わりに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);
}

これにより、定型コードのlotsを書く必要がなくなります!ただし、説明されているように、バイトの順序に注意する必要があります here

6
malat

ネイティブ関数内でメモリを動的に(ヒープ上で)割り当てている場合、メモリは削除されません。つまり、ポインター、静的変数などを使用して、ネイティブ関数への異なる呼び出し間で状態を保持できます。

別の方法で考えてください:別のC++プログラムから呼び出された関数呼び出しで安全に何ができるでしょうか?ここでも同じことが当てはまります。関数が終了すると、その関数呼び出しのスタック上のすべてが破棄されます。ただし、ヒープ上のすべてのものは、明示的に削除しない限り保持されます。

簡単な答え:結果の割り当てを解除しない限り、呼び出し元の関数に戻りますが、後で再入力しても有効のままです。完了したら、必ずクリーンアップしてください。

6
Marc Paradise

Unsafe.allocateMemoryの方法とまったく同じ方法でこれを行うのが最善です。

オブジェクトを作成し、(uintptr_t)に入力します。これは32/64ビットの符号なし整数です。

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
0
bond