Передача указателей между C и Java через JNI - PullRequest
64 голосов
/ 27 октября 2009

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

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

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

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

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

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

Ответы [ 7 ]

44 голосов
/ 28 октября 2009

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

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

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

РЕДАКТИРОВАТЬ: Я не думаю, что вы можете использовать long ptr = (long)&address;, так как адрес является статической переменной. Используйте его так, как предложил Gunslinger47, то есть создайте новый экземпляр класса или структуры (используя new или malloc) и передайте его указатель.

15 голосов
/ 19 марта 2010

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

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;
    ...
}
10 голосов
/ 27 октября 2009

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

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

7 голосов
/ 28 ноября 2011

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

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

6 голосов
/ 17 апреля 2015

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

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

6 голосов
/ 28 октября 2009

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

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

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

0 голосов
/ 19 августа 2015

Лучше всего делать это точно так, как это делает 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...