2009-10-27 14 views
58

Por el momento, intento crear una aplicación Java que use la funcionalidad CUDA. La conexión entre CUDA y Java funciona bien, pero tengo otro problema y quería preguntar, si mi opinión al respecto es correcta.Pasando punteros entre C y Java a través de JNI

Cuando llamo a una función nativa de Java, le paso algunos datos, las funciones calculan algo y devuelven un resultado. ¿Es posible, dejar que la primera función devuelva una referencia (puntero) a este resultado que puedo pasar a JNI y llamar a otra función que hace cálculos adicionales con el resultado?

Mi idea era reducir la sobrecarga que proviene de copiar datos hacia y desde la GPU al dejar los datos en la memoria de la GPU y simplemente pasarle una referencia para que otras funciones puedan usarla.

Después de intentarlo, pensé que esto no debería ser posible, ya que los punteros se eliminan cuando finaliza la aplicación (en este caso, cuando termina la función C). ¿Es esto correcto? ¿O simplemente estoy mal en C para ver la solución?

Edit: Bueno, para ampliar la pregunta un poco (o para que sea más claro): ¿La memoria asignada por funciones nativas JNI se desasigna cuando la función finaliza? ¿O puedo acceder a él hasta que la aplicación JNI finalice o cuando la libere de forma manual?

Gracias por su entrada :)

+0

también https://stackoverflow.com/q/5802340/632951 – Pacerier

Respuesta

41

He utilizado el siguiente enfoque:

en el código JNI, crear una estructura que contendría referencias a los objetos que necesita. Cuando crea esta estructura por primera vez, devuelva su puntero a java como long. Luego, desde Java, simplemente llama a cualquier método con este long como parámetro, y en C lo convierte en un puntero a tu estructura.

La estructura estará en el montón, por lo que no se borrará entre las diferentes llamadas JNI.

EDITAR: No creo que se pueda usar long ptr = (long)&address; dado que la dirección es una variable estática. Úselo de la forma sugerida por Gunslinger47, es decir, cree una nueva instancia de clase o una estructura (usando new o malloc) y pase su puntero.

+0

Así es como lo hice ahora: envío el puntero a Java como un largo y lo paso a C. Pero, ¿cómo le digo a C que el tiempo que acaba de recibir es una dirección? long * ptr = (largo *) & dirección; Eso es lo que probé, pero no obtengo el valor que debería estar en la dirección, solo obtengo la dirección en sí (u otras direcciones pero no valor :() – Volker

+0

... debe haber asteriscos antes de ptr y después del segundo largo ... – Volker

+8

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

10

Java no sabría qué hacer con un puntero, pero debe ser capaz de almacenar un puntero del valor de retorno de una función nativa luego entregarlo a otra función nativa de para tratar Los punteros C no son más que valores numéricos en el núcleo.

Otro proveedor tendría que decirle si la memoria de gráficos apuntada se eliminaría o no entre las invocaciones de JNI y si habría alguna solución temporal.

+2

Acerca de su segundo párrafo: La única cosa a tener en cuenta es asegurarse de que cualquier memoria asignada se cancela la asignación también. El enfoque recomendado es tener algún tipo de método close/dispose() en el objeto que contiene la referencia. los finalizadores son tentadores, pero vienen con un par de inconvenientes que hacen que valga la pena evitarlos si es posible. – Fredrik

+0

No debería haber escrito "lo único" por cierto ... JNI está lleno de pozos en los que caer. – Fredrik

+0

Ya incluí las funciones para liberar la memoria asignada, así que no debería haber problema :) El principal problema sigue siendo: ¿la memoria se queda asignada si no la tengo? Quiero decir, incluyendo direcciones y valores ... sé que obtendré fugas de memoria si no lo hago, entonces ya lo incluí ;-) – Volker

6

Si está asignando memoria dinámicamente (en el montón) dentro de la función nativa, no se elimina. En otras palabras, puede retener el estado entre diferentes llamadas en funciones nativas, usando punteros, vars estáticos, etc.

Piénselo de otra manera: ¿qué podría hacer de manera segura en una llamada de función, llamada desde otra Programa C++? Lo mismo aplica aquí. Cuando se sale de una función, cualquier cosa en la pila para esa llamada de función se destruye; pero se conserva todo en el montón a menos que lo elimine explícitamente.

Respuesta breve: siempre que no desasigne el resultado que está volviendo a la función de llamada, seguirá siendo válido para volver a ingresar más tarde. Solo asegúrate de limpiarlo cuando termines.

13

En C++ puede usar cualquier mecanismo que desee asignar/memoria libre: la pila, malloc/free, new/delete o cualquier otra implementación personalizada. El único requisito es que si asignó un bloque de memoria con un mecanismo, debe liberarlo con el mismo mecanismo, por lo que no puede llamar al free en una variable de pila y no puede llamar al delete en malloc memoria ed.

JNI tiene sus propios mecanismos de asignación/liberación de memoria JVM:

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

Estos siguen la misma regla, la La única pega es que las referencias locales se pueden eliminar "en masa" explícitamente, con PopLocalFrame, o implícitamente, cuando el método nativo se cierra.

JNI no sabe cómo asignó su memoria, por lo que no puede liberarla cuando su función finalice. Las variables de pila obviamente se destruirán porque todavía estás escribiendo C++, pero tu memoria GPU seguirá siendo válida.

El único problema es, entonces, cómo acceder a la memoria en las invocaciones posteriores, y entonces usted puede utilizar la sugerencia de 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

No debería '(* pObject) lp;' be '(MyClass *) lp;'? – donturner

6

Sé que esta pregunta ya fue respondida oficialmente, pero me gustaría añadir mi solución: En lugar de intentar pasar un puntero, coloque el puntero en una matriz Java (en el índice 0) y páselo a JNI. El código JNI puede obtener y establecer el elemento de matriz usando GetIntArrayRegion/SetIntArrayRegion.

En mi código, necesito la capa nativa para administrar un descriptor de archivo (un socket abierto). La clase Java contiene una matriz int[1] y la pasa a la función nativa. La función nativa puede hacer lo que quiera con ella (obtener/establecer) y devolver el resultado en la matriz.

+0

Cómo convertir un puntero largo transferido a una matriz en java –

6

Si bien la respuesta aceptada de @ denis-tulskiy tiene sentido, he seguido personalmente las sugerencias de here.

Así que en lugar de utilizar un tipo de pseudo-puntero como jlong (o jint si quieres ahorrar algo de espacio en el arco de 32 bits), utilice en su lugar un ByteBuffer. Por ejemplo:

MyNativeStruct* data; // Initialized elsewhere. 
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct)); 

que se puede volver a utilizar más adelante:

jobject bb; // Initialized elsewhere. 
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb); 

para casos muy simples, esta solución es muy fácil de usar. Suponga que tiene:

struct { 
    int exampleInt; 
    short exampleShort; 
} MyNativeStruct; 

En el lado de Java, sólo hay que hacer:

public int getExampleInt() { 
    return bb.getInt(0); 
} 

public short getExampleShort() { 
    return bb.getShort(4); 
} 

Qué le ahorra escribir montón de código reutilizable! Sin embargo, uno debe prestar atención a la ordenación de bytes como se explica en here.

0

Lo mejor es hacer esto exactamente cómo lo hace USAfe.allocateMemory.

Crea tu objeto y escríbelo como (uintptr_t) que es un entero sin signo de 32/64 bits.

return (uintptr_t) malloc(50); 

void * f = (uintptr_t) jlong; 

Esta es la única forma correcta de hacerlo.

Aquí está la comprobación de cordura insegura. AllocateMemory does.

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

Esto es ** no ** la definición estándar de 'uintptr_t'. Es una ** muy ** mala idea hacer eso. Se define como lo suficientemente grande como para contener cualquier puntero, y puede tener ** cualquier ** longitud que esto requiera. Típicamente en un sistema de 64 bits esto sería de 64 bits, pero el estándar ni siquiera permite esa suposición. Nunca debe hacer suposiciones sobre el tamaño de 'uintptr_t'. – StephenG

+1

Así es como la JVM asigna memoria a los sistemas de 32 y 64 bits. La asignación de uintptr_t permite una conversión limpia a jlong. No voy a debatir si es una buena manera o no, pero es la forma en que lo hace la JVM. – bond

Cuestiones relacionadas