Передача указателей между C и Java через JNI


на данный момент я пытаюсь создать Java-приложение, которое использует CUDA-функциональность. Связь между CUDA и Java работает нормально, но у меня есть еще одна проблема, и я хотел спросить, если мои мысли об этом верны.

когда я вызываю собственную функцию из Java, я передаю ей некоторые данные, функции вычисляют что-то и возвращают результат. Возможно ли, чтобы первая функция возвращала ссылку (указатель) на этот результат, который я могу передать JNI и вызвать другой функция, которая делает дальнейшие расчеты с результатом?

моя идея заключалась в том, чтобы уменьшить накладные расходы, связанные с копированием данных на GPU и из него, оставив данные в памяти GPU и просто передав ссылку на него, чтобы другие функции могли его использовать.

попробовав некоторое время, я подумал для себя, что это не должно быть возможно, потому что указатели удаляются после завершения приложения (в этом случае, когда C-функция завершается). Это правильно? Или я просто плохой в C чтобы увидеть решение?

изменить: Ну, чтобы немного расширить вопрос (или сделать его более ясным): освобождается ли память, выделенная собственными функциями JNI, когда функция заканчивается? Или я все еще могу получить к нему доступ, пока приложение JNI не закончится или когда я освобожу его вручную?

Спасибо за Ваш вклад :)

7 59

7 ответов:

я использовал следующий подход:

в коде JNI создайте структуру, которая будет содержать ссылки на нужные вам объекты. Когда вы впервые создаете эту структуру, верните ее указатель на java как long. Затем из java вы просто вызываете любой метод с этим long в качестве параметра, А В C приведите его к указателю на вашу структуру.

структура будет находиться в куче, поэтому она не будет очищена между различными вызовами JNI.

EDIT: я не думаю, что вы можете использовать длинный ptr = (long)&address; так как адрес является статической переменной. Используйте его так, как предложил Gunslinger47, т. е. создайте новый экземпляр класса или структуры (используя new или malloc) и передайте его указатель.

в C++ вы можете использовать любой механизм, который вы хотите выделить/освободить память: стек, malloc/free, new/delete или любую другую пользовательскую реализацию. Единственное требование заключается в том, что если вы выделили блок памяти с одним механизмом, вы должны освободить его с тем же механизмом, поэтому вы не можете вызвать free в переменной стека, и вы не можете вызвать delete on 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;
    ...
}

Java не знал бы, что делать с указателем, но он должен иметь возможность хранить указатель из возвращаемого значения собственной функции, а затем передавать его другой собственной функции для его обработки. С указателями не более, чем числовые значения в ядре.

другой contibutor должен был бы сказать вам, будет ли указанная графическая память очищена между вызовами JNI и будут ли какие-либо обходные пути.

Я знаю, что на этот вопрос уже был официально дан ответ, но я хотел бы добавить свое решение: Вместо того, чтобы пытаться передать указатель, поместите указатель в массив Java (с индексом 0) и передайте его в JNI. Код JNI может получить и установить элемент массива с помощью GetIntArrayRegion/SetIntArrayRegion.

В моем коде мне нужен собственный слой для управления файловым дескриптором (открытым сокетом). Класс Java содержит int[1] массив и передает его в собственную функцию. Родная функция может делать с ней все, что угодно (get / set) и верните результат в массив.

Если вы выделяем память динамически (в куче) внутри собственной функции, она не удаляется. Другими словами, вы можете сохранять состояние между различными вызовами в собственные функции, используя указатели, статические vars и т. д.

подумайте об этом по-другому: что вы могли бы безопасно хранить в вызове функции, вызванном из другой программы на C++? То же самое относится и здесь. При выходе из функции все, что находится в стеке для этого вызова функции, уничтожается; но все в куче сохраняется, если вы явно не удалите его.

короткий ответ: до тех пор, пока вы не освободите результат, который вы возвращаете в вызывающую функцию, он будет оставаться действительным для повторного входа позже. Просто убедитесь, что убрали его, когда закончите.

хотя принятый ответ от @denis-tulskiy имеет смысл, я лично следил за предложениями от здесь.

поэтому вместо использования псевдо-указателя типа jlong (или jint если вы хотите сэкономить место на 32bits arch), используйте вместо этого 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);
}

что спасает вас от написания много boilerplate кода ! Однако следует обратить внимание на порядок байтов, как пояснил здесь.

лучше всего сделать это именно так, как небезопасно.выделять память делает.

создайте свой объект, затем введите его в (uintptr_t), который является 32/64 битным целым числом без знака.

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

это единственный правильный способ сделать это.

вот проверка на вменяемость небезопасна.выделять память делает.

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