Pasar punteros entre C y Java a través de JNI
pointers cuda (7)
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 invocar free
una variable de pila y no puede invocar delete
en la memoria malloc
.
JNI tiene sus propios mecanismos para asignar / liberar memoria JVM:
- NewObject / DeleteLocalRef
- NewGlobalRef / DeleteGlobalRef
- NewWeakGlobalRef / DeleteWeakGlobalRef
Estos siguen la misma regla, la única pega es que los refs 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 finaliza. 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 cómo acceder a la memoria en las invocaciones posteriores, y luego puede usar 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;
...
}
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.
Cuando invoco 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 un tiempo, pensé por mí mismo, esto no debería ser posible, porque los punteros se eliminan después de que la aplicación finaliza (en este caso, cuando termina la función C). ¿Es esto correcto? ¿O simplemente estoy mal en C para ver la solución?
Editar: Bueno, para ampliar la pregunta un poco (o para que sea más clara): ¿La memoria asignada por las funciones nativas de 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 tu contribución :)
Es mejor hacer esto exactamente cómo lo hace Inseguro.
Crea tu objeto y luego tipea en (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 lo hace.
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
Java no sabría qué hacer con un puntero, pero debería poder almacenar un puntero desde el valor de retorno de una función nativa y luego transferirlo a otra función nativa para que lo trate. 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 borrará o no entre las invocaciones de JNI y si habría algún problema.
Sé que esta pregunta ya fue respondida oficialmente, pero me gustaría agregar mi solución: en lugar de intentar pasar un puntero, coloque el puntero en una matriz de 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.
Si bien la respuesta aceptada de @ denis-tulskiy tiene sentido, personalmente he seguido las sugerencias de here .
Por lo tanto, en lugar de usar un tipo de pseudo puntero como jlong
(o jint
si desea guardar algo de espacio en el arco de 32 bits), use en su lugar un ByteBuffer
. Por ejemplo:
MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));
con el que luego puedes volver a utilizar:
jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);
Para casos muy simples, esta solución es muy fácil de usar. Supongamos que tiene:
struct {
int exampleInt;
short exampleShort;
} MyNativeStruct;
En el lado de Java, simplemente necesitas hacer:
public int getExampleInt() {
return bb.getInt(0);
}
public short getExampleShort() {
return bb.getShort(4);
}
¡Lo que le ahorra escribir muchos códigos repetitivos! Sin embargo, uno debe prestar atención al orden de bytes como se explica here .
Si está asignando memoria dinámicamente (en el montón) dentro de la función nativa, no se elimina. En otras palabras, puede retener estado entre llamadas diferentes en funciones nativas, usando punteros, vars estáticos, etc.
Piénselo de otra manera: ¿qué podría hacer de manera segura para mantener una llamada a la función, llamada desde otro 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 corta: siempre que no desasigne el resultado que está regresando a la función de llamada, seguirá siendo válido para volver a ingresar más tarde. Solo asegúrate de limpiarlo cuando termines.
Utilicé el siguiente enfoque:
en su código JNI, cree una estructura que contenga referencias a los objetos que necesita. Cuando crea por primera vez esta estructura, devuelva su puntero a java como un valor long
. Luego, desde Java, simplemente llama a cualquier método con este parámetro como parámetro, y en C lo convierte en un puntero a su estructura.
La estructura estará en el montón, por lo que no se borrará entre las diferentes llamadas JNI.
EDITAR: no creo que puedas usar ptr = (long)&address;
ya 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.