web-dev-qa-db-fra.com

Comment passer des structures C d'avant en arrière à Java dans JNI?

J'ai quelques fonctions C que j'appelle via JNI qui prennent un pointeur vers une structure, et quelques autres fonctions qui alloueront/libéreront un pointeur vers le même type de structure afin qu'il soit un peu plus facile de gérer mon wrapper . Étonnamment, la documentation JNI en dit très peu sur la façon de gérer les structures C.

Mon fichier d'en-tête C ressemble à ceci:

typedef struct _MyStruct {
  float member;
} MyStruct;

MyStruct* createNewMyStruct();
void processData(int *data, int numObjects, MyStruct *arguments);

Le fichier wrapper JNI C correspondant contient:

JNIEXPORT jobject JNICALL
Java_com_myorg_MyJavaClass_createNewMyStruct(JNIEnv *env, jobject this) {
  return createNewMyStruct();
}

JNIEXPORT void JNICALL
Java_com_myorg_MyJavaClass_processData(JNIEnv *env, jobject this, jintArray data,
                                       jint numObjects, jobject arguments) {
  int *actualData = (*env)->GetIntArrayElements(env, data, NULL);
  processData(actualData, numObjects, arguments);
  (*env)->ReleaseIntArrayElements(env, data, actualData, NULL);
}

... et enfin, la classe Java Java correspondante:

public class MyJavaClass {
  static { System.loadLibrary("MyJniLibrary"); }

  private native MyStruct createNewMyStruct();
  private native void processData(int[] data, int numObjects, MyStruct arguments);

  private class MyStruct {
    float member;
  }

  public void test() {
    MyStruct foo = createNewMyStruct();
    foo.member = 3.14159f;
    int[] testData = new int[10];
    processData(testData, 10, foo);
  }
}

Malheureusement, ce code plante la JVM juste après avoir appuyé sur createNewMyStruct(). Je suis un peu nouveau pour JNI et je n'ai aucune idée du problème.

Edit: Je dois noter que le code C est très Vanilla C, est bien testé et a été porté à partir d'un projet iPhone fonctionnel. En outre, ce projet utilise le cadre Android NDK, qui vous permet d'exécuter du code C natif à partir d'un projet Android depuis JNI. Cependant, je ne le fais pas). pense que c'est strictement un problème NDK ... cela ressemble à une erreur de configuration/initialisation JNI de ma part.

63
Nik Reiman

Vous devez créer une classe Java avec les mêmes membres que C struct, et les "mapper" dans le code C via les méthodes env-> GetIntField, env-> SetIntField, env-> GetFloatField, env-> SetFloatField, et ainsi de suite - en bref, beaucoup de travail manuel, j'espère qu'il existe déjà des programmes qui le font automatiquement: JNAerator ( http://code.google.com/p/jnaerator ) et SWIG ( http://www.swig.org/ ). Les deux ont leurs avantages et leurs inconvénients, à vous de choisir.

41
iirekm

Il se bloque car Java_com_myorg_MyJavaClass_createNewMyStruct Est déclaré pour renvoyer jobject, mais renvoie en fait struct MyStruct. Si vous avez exécuté cela avec CheckJNI activé, la fonction VM se plaindrait fort et s'interromprait. Votre fonction processData() va également être assez bouleversée par ce qu'elle reçoit _ arguments.

Un jobject est un objet sur le tas managé. Il peut contenir des éléments supplémentaires avant ou après les champs déclarés, et les champs n'ont pas besoin d'être disposés en mémoire dans un ordre particulier. Vous ne pouvez donc pas mapper une structure C au-dessus d'une classe Java.

La façon la plus simple de gérer cela était identifiée dans une réponse précédente: manipuler le jobject avec les fonctions JNI. Allouez les objets de Java ou avec NewObject, Get/Set les champs d'objet avec les appels appropriés.

Il existe différentes façons de "tricher" ici. Par exemple, vous pouvez inclure un byte[] Dans votre Java contenant sizeof(struct MyStruct) octets, puis utiliser GetByteArrayElements pour obtenir un pointeur vers Un peu moche, surtout si vous voulez accéder aux champs du côté Java aussi.

10
fadden

La structure C est la collection de variables (certaines sont des pointeurs de fonction). Passer à Java n'est pas une bonne idée. En général, c'est le problème de savoir comment passer un type plus complexe à Java, comme un pointeur.

Dans le livre JNI, pour conserver le pointeur/la structure en natif et exporter la manipulation vers Java est recommandé. Vous pouvez lire quelques articles utiles. Guide et spécification du programmeur d'interface native JavaTM J'ai lu. 9.5 Classes de pairs ai une solution pour y faire face.

7
qrtt1
  1. Créez la classe des deux côtés Java et C++, en insérant simplement les variables membres. Les structures C++ ne sont vraiment que des classes avec des membres de données publiques. Si vous êtes vraiment en C pur, arrêtez de lire maintenant.
  2. Utilisez vos IDE pour créer automatiquement des setters et des getters pour les variables membres.
  3. Utilisez javah pour générer le fichier d'en-tête C à partir de la classe Java.
  4. Faites quelques modifications du côté C++ pour que les setters et les getters correspondent au fichier d'en-tête généré.
  5. Mettez le code JNI.

Ce n'est pas une solution idéale, mais cela peut vous faire gagner un peu de temps et cela vous donnera au moins un squelette que vous pouvez modifier. Cette fonctionnalité pourrait être ajoutée à un IDE, mais sans une grande demande, cela ne se produira probablement pas. La plupart des IDE ne prennent même pas en charge les projets de langues mixtes, sans parler de les faire se parler.

0
Bill