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

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

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

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

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

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

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

7 ответов


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

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

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

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


в C++ вы можете использовать любой механизм, который хотите выделить / освободить память: стек, malloc/free, new/delete или любую другую пользовательскую реализацию. Единственное требование состоит в том, что если вы выделили блок памяти с одним механизмом, вы должны освободить его с тем же механизмом, поэтому вы не можете вызвать free на переменной стека, и вы не можете вызвать delete on mallocпамять ed.

JNI имеет свои собственные механизмы для выделения / освобождения JVM память:

  • NewObject / DeleteLocalRef
  • NewGlobalRef / DeleteGlobalRef
  • NewWeakGlobalRef / DeleteWeakGlobalRef

они следуют тому же правилу, единственный улов заключается в том, что локальные ссылки могут быть удалены "En masse" либо явно, с 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-это не что иное, как числовые значения в ядре.

другой 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