2009-10-27 8 views
58

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

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

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

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

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

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

+0

Также https://stackoverflow.com/q/5802340/632951 – Pacerier

ответ

41

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

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

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

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

+0

Вот как я это сделал сейчас - я отправляю указатель обратно в java как длинный и передаю его на C. Но как мне сказать C, что длинный, который он только что получил, является адресом? длинный * ptr = (длинный *) & адрес; Это то, что я пытался, но я не получаю значение, которое должно быть по адресу, я получаю только адрес (или некоторые другие адреса, но не значение :() – Volker

+0

... там должны быть звездочки перед ptr и после второго длинного ... – Volker

+8

'MyClass * pObject = ...; long lp = (long) pObject; pObject = (* pObject) lp;' – Gunslinger47

10

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

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

+2

О вашем втором пункте: Единственное, что нужно обратить внимание, чтобы убедиться, что любая память, выделенная будет освобождён, а также. Рекомендуемый подход - иметь какой-то метод close/dispose() для объекта, содержащего ссылку. финализаторы заманчивы, но они приходят с несколькими недостатками, поэтому стоит избегать их, если это возможно. – Fredrik

+0

Я не должен был писать «единственное», кстати ... JNI полон ям, чтобы попасть. – Fredrik

+0

Я уже включил функции для освобождения выделенной памяти, так что это не должно быть проблемой :) Основная проблема по-прежнему заключается в том, что память остается выделенной, если я бесплатно ее освобождаю? Я имею в виду, включая адреса и значения ... Я знаю, что я получу утечки памяти и так далее, если я этого не сделаю, поэтому я уже включил это ;-) – Volker

6

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

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

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

13

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

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

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

Они следуют тому же правилу, то только catch - это то, что локальные ссылки могут быть удалены «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; 
    ... 
} 
+0

Не должен '(* pObject) lp;' be '(MyClass *) lp;'? – donturner

6

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

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

+0

Как преобразовать перенесенный длинный указатель в массив в java –

6

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

Таким образом, вместо того, чтобы использовать тип псевдо-указатель, такие как 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); 
} 

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

0

Его наилучшим образом сделать это точно как 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 
+0

Это ** не ** стандартное определение 'uintptr_t'. Это очень плохой идеей. Он определяется как достаточно большой, чтобы удерживать любой указатель, и может быть ** любой ** длиной, которую это требует. Как правило, в 64-битной системе это было бы 64 бита, но стандарт даже не допускает этого предположения. Вы никогда не должны делать предположений о размере 'uintptr_t'. – StephenG

+1

Так JVM выделяет память на 32 и 64-разрядных системах. Назначение uintptr_t допускает чистое преобразование в jlong. Я не буду обсуждать, является ли это хорошим способом или нет, но именно так работает JVM. – bond