Development Tip

JNI를 통해 C와 Java간에 포인터 전달

yourdevel 2020. 11. 8. 11:23
반응형

JNI를 통해 C와 Java간에 포인터 전달


현재 CUDA 기능을 사용하는 Java 응용 프로그램을 만들려고합니다. CUDA와 Java 간의 연결은 정상적으로 작동하지만 다른 문제가 있으며 이에 대한 내 생각이 올바른지 묻고 싶습니다.

Java에서 기본 함수를 호출 할 때 일부 데이터를 전달하면 함수가 무언가를 계산하고 결과를 반환합니다. 첫 번째 함수가이 결과에 대한 참조 (포인터)를 반환하도록하여 JNI에 전달할 수 있고 결과로 추가 계산을 수행하는 다른 함수를 호출 할 수 있습니까?

제 생각은 데이터를 GPU 메모리에 남겨두고 다른 함수가 사용할 수 있도록 데이터를 GPU 메모리에 그대로 두어 GPU에서 데이터를 복사 할 때 발생하는 오버 헤드를 줄이는 것이 었습니다.

얼마 동안 시도한 후, 응용 프로그램이 종료 된 후 포인터가 삭제되기 때문에 가능하지 않을 것이라고 생각했습니다 (이 경우 C 기능이 종료 될 때). 이 올바른지? 아니면 해결책을보기 위해 C에서 나쁘나요?

편집 : 글쎄, 질문을 조금 확장하려면 (또는 더 명확하게) : JNI 네이티브 함수에 의해 할당 된 메모리는 함수가 끝날 때 할당 해제됩니까? 아니면 JNI 애플리케이션이 종료되거나 수동으로 해제 할 때까지 계속 액세스 할 수 있습니까?

귀하의 의견에 감사드립니다 :)


다음 접근 방식을 사용했습니다.

JNI 코드에서 필요한 객체에 대한 참조를 보유하는 구조체를 만듭니다. 이 구조체를 처음 만들 때 java에 대한 포인터를 long. 그런 다음 Java에서 this long를 매개 변수로 사용하여 메서드를 호출 하고 C에서 구조체에 대한 포인터로 캐스팅합니다.

구조는 힙에 있으므로 서로 다른 JNI 호출간에 지워지지 않습니다.

편집 : (long)&address;주소가 정적 변수이기 때문에 긴 ptr = 사용할 수 있다고 생각하지 않습니다 . Gunslinger47이 제안한 방식으로 사용합니다. 즉, 클래스 또는 구조체의 새 인스턴스를 만들고 (new 또는 malloc 사용) 포인터를 전달합니다.


C ++에서는 스택, malloc / free, new / delete 또는 기타 사용자 정의 구현과 같이 메모리를 할당 / 해제하려는 모든 메커니즘을 사용할 수 있습니다. 유일한 요구 사항은 당신이 하나의 메커니즘으로 메모리 블록을 할당하는 경우, 당신은 당신이 호출 할 수 있도록, 같은 메커니즘을 확보해야한다는 것입니다 free스택 변수에 당신이 호출 할 수 없습니다 deletemalloc에드 메모리.

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;
    ...
}

자바는 포인터로 무엇을해야하는지 알지 못하지만, 네이티브 함수의 반환 값에서 포인터를 저장 한 다음 처리 할 다른 네이티브 함수로 전달할 수 있어야합니다. C 포인터는 핵심의 숫자 값에 지나지 않습니다.

다른 contibutor는 그래픽 메모리를 가리키는 것이 JNI 호출 사이에 지워질 지 여부와 해결 방법이 있는지 여부를 알려야합니다.


이 질문에 이미 공식적으로 대답 한 것을 알고 있지만 솔루션을 추가하고 싶습니다. 포인터를 전달하는 대신 포인터를 Java 배열 (인덱스 0)에 놓고 JNI에 전달합니다. JNI 코드는 GetIntArrayRegion/를 사용하여 배열 요소를 가져오고 설정할 수 있습니다 SetIntArrayRegion.

내 코드에서 파일 설명자 (오픈 소켓)를 관리하려면 네이티브 레이어가 필요합니다. Java 클래스는 int[1]배열을 보유하고 이를 원시 함수에 전달합니다. 네이티브 함수는 그것으로 무엇이든 할 수 있고 (get / set) 그 결과를 배열에 되돌릴 수 있습니다.


원시 함수 내에서 동적으로 (힙에) 메모리를 할당하는 경우 삭제되지 않습니다. 즉, 포인터, 정적 변수 등을 사용하여 네이티브 함수에 대한 서로 다른 호출간에 상태를 유지할 수 있습니다.

다른 방식으로 생각해보십시오. 다른 C ++ 프로그램에서 호출 된 함수 호출에서 안전하게 무엇을 유지할 수 있습니까? 여기에도 같은 것이 적용됩니다. 함수가 종료되면 해당 함수 호출에 대한 스택의 모든 항목이 삭제됩니다. 그러나 힙의 모든 것은 명시 적으로 삭제하지 않는 한 유지됩니다.

짧은 대답 : 호출 함수로 반환하는 결과를 할당 해제하지 않는 한 나중에 다시 입력 할 수 있도록 유효합니다. 완료되면 정리하십시오.


@ denis-tulskiy의 대답은 의미가 있지만 여기 에서 제안을 개인적으로 따랐 습니다 .

따라서 jlong(또는 jint32 비트 아치에서 약간의 공간을 절약하려는 경우) 와 같은 의사 포인터 유형을 사용하는 대신 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;

이것이 유일한 올바른 방법입니다.

Here is the sanity checking Unsafe.allocateMemory does.

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

참고URL : https://stackoverflow.com/questions/1632367/passing-pointers-between-c-and-java-through-jni

반응형