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

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

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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Они следуют одному и тому же правилу, единственный улов - локальные ссылки могут быть удалены "en masse" либо явно, либо с помощью PopLocalFrame, либо неявно, когда нативный метод завершается.

JNI не знает, как вы выделили свою память, поэтому она не может освободить ее, когда ваша функция завершается. Переменные стека, очевидно, будут уничтожены, потому что вы все еще пишете С++, но ваша память 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;
    ...
}

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

Хотя принятый ответ от @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);
}

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

Ответ 7

Лучше всего это сделать, как это делает 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