Passage de pointeurs entre C et Java via JNI

En ce moment, j’essaie de créer une application Java qui utilise les fonctionnalités de CUDA. La connexion entre CUDA et Java fonctionne bien, mais j’ai un autre problème et je voulais demander si mes pensées à ce sujet étaient correctes.

Lorsque j’appelle une fonction native à partir de Java, je lui passe des données, la fonction calcule quelque chose et renvoie un résultat. Est-il possible de laisser la première fonction renvoyer une référence (pointeur) à ce résultat que je peux transmettre à JNI et appeler une autre fonction effectuant d’autres calculs avec le résultat?

Mon idée était de réduire la surcharge liée à la copie des données vers et depuis le GPU en laissant les données dans la mémoire du GPU et en ne faisant que lui faire référence afin que d’autres fonctions puissent l’utiliser.

Après avoir essayé quelque temps, je me suis dit que cela ne devrait pas être possible, car les pointeurs sont supprimés à la fin de l’application (dans ce cas, lorsque la fonction C se termine). Est-ce correct? Ou est-ce que je suis juste trop mauvais en C pour voir la solution?

Edit: Eh bien, pour développer un peu la question (ou la rendre plus claire): La mémoire allouée par les fonctions natives JNI est-elle libérée lorsque la fonction se termine? Ou puis-je toujours y accéder jusqu’à la fin de l’application JNI ou lorsque je la libère manuellement?

Merci pour votre consortingbution 🙂

J’ai utilisé l’approche suivante:

dans votre code JNI, créez une structure contenant des références aux objects dont vous avez besoin. Lorsque vous créez cette structure pour la première fois, retournez son pointeur vers Java en tant que long . Ensuite, à partir de Java, vous appelez n’importe quelle méthode avec ce paramètre en tant que paramètre, et en C, vous la transformez en un pointeur sur votre structure.

La structure sera dans le tas, donc elle ne sera pas effacée entre les différents appels JNI.

EDIT: Je ne pense pas que vous pouvez utiliser long ptr = (long)&address; puisque l’adresse est une variable statique. Utilisez-le comme suggéré par Gunslinger47, c’est-à-dire créer une nouvelle instance de classe ou une structure (en utilisant new ou malloc) et passer son pointeur.

En C ++, vous pouvez utiliser n’importe quel mécanisme que vous souhaitez allouer / libérer de la mémoire: la stack, malloc / free, new / delete ou toute autre implémentation personnalisée. La seule exigence est que si vous allouez un bloc de mémoire avec un seul mécanisme, vous devez le libérer avec le même mécanisme. Vous ne pouvez donc pas appeler free une variable de stack et vous ne pouvez pas appeler delete sur la mémoire malloc .

JNI a ses propres mécanismes pour allouer / libérer de la mémoire JVM:

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

Celles-ci suivent la même règle, le seul inconvénient est que les références locales peuvent être supprimées “en masse” soit explicitement, avec PopLocalFrame , soit implicitement, quand la méthode native se PopLocalFrame .

JNI ne sait pas comment vous avez alloué votre mémoire, il ne peut donc pas le libérer lorsque votre fonction se ferme. Les variables de stack seront évidemment détruites car vous écrivez toujours en C ++, mais votre mémoire GPU restra valide.

Le seul problème est alors comment accéder à la mémoire lors des invocations suivantes, et ensuite vous pouvez utiliser la suggestion 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; ... } 

Java ne saurait pas quoi faire avec un pointeur, mais il devrait être capable de stocker un pointeur à partir de la valeur de retour d’une fonction native, puis de le transférer à une autre fonction native. Les pointeurs C ne sont rien de plus que des valeurs numériques au centre.

Un autre participant devrait vous indiquer si la mémoire graphique pointée serait ou non effacée entre les invocations JNI et s’il y aurait des solutions de rechange.

Je sais que cette question a déjà reçu une réponse officielle, mais j’aimerais append ma solution: au lieu d’essayer de passer un pointeur, placez le pointeur dans un tableau Java (à l’index 0) et transmettez-le à JNI. Le code JNI peut obtenir et définir l’élément de tableau à l’aide de GetIntArrayRegion / SetIntArrayRegion .

Dans mon code, j’ai besoin de la couche native pour gérer un descripteur de fichier (un socket ouvert). La classe Java contient un tableau int[1] et le transmet à la fonction native. La fonction native peut faire n’importe quoi avec (get / set) et remettre le résultat dans le tableau.

Si vous allouez dynamicment de la mémoire (sur le tas) à l’intérieur de la fonction native, celle-ci n’est pas supprimée. En d’autres termes, vous pouvez conserver l’état entre différents appels dans des fonctions natives, en utilisant des pointeurs, des vars statiques, etc.

Pensez-y différemment: que pouvez-vous faire en toute sécurité dans un appel de fonction, appelé depuis un autre programme C ++? Les mêmes choses s’appliquent ici. Lorsqu’une fonction est sortie, tout object de la stack correspondant à cet appel de fonction est détruit; mais tout ce qui est sur le tas est conservé sauf si vous le supprimez explicitement.

Réponse courte: tant que vous ne désallouez pas le résultat que vous retournez à la fonction d’appel, il restra valide pour une ré-entrée ultérieure. Assurez-vous de le nettoyer lorsque vous avez terminé.

Bien que la réponse acceptée de @ denis-tulskiy ait du sens, j’ai personnellement suivi les suggestions d’ ici .

Donc, au lieu d’utiliser un pseudo-pointeur tel que jlong (ou jint si vous voulez économiser de l’espace sur l’arc 32bits), utilisez plutôt un ByteBuffer . Par exemple:

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

que vous pouvez réutiliser ultérieurement avec:

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

Pour des cas très simples, cette solution est très facile à utiliser. Supposons que vous ayez:

 struct { int exampleInt; short exampleShort; } MyNativeStruct; 

Côté Java, il vous suffit de faire:

 public int getExampleInt() { return bb.getInt(0); } public short getExampleShort() { return bb.getShort(4); } 

Ce qui vous évite d’écrire beaucoup de code passe-partout! Il faut cependant faire attention à l’ordre des octets comme expliqué ici .

Il est préférable de le faire exactement comme le fait Unsafe.allocateMemory.

Créez votre object puis tapez-le dans (uintptr_t) qui est un entier non signé 32/64 bits.

 return (uintptr_t) malloc(50); void * f = (uintptr_t) jlong; 

C’est la seule façon correcte de le faire.

Voici le test de cohérence effectué par 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