Search code examples
java-native-interface

Passing a list/arraylist of reference type objects contained in a class to JNI/C++


I wrote a piece of code that manipulates reference type objects contained inside a class, now am having trouble while converting this instance to a list of objects.

Here is the code snippet;

 classes.java
=================
public class Leg {  
    public int id;
    public String name;
    // a few other primitive data types 
    ....
}    
public class Line {
    public int id;
    ....
    //public Leg mLeg; this worked well
    public List<Leg> mLegList; 
}
Driver.java
============
public native void setLine(Line jobj);

Line line = new Line();
line.id =200;

line.mLegList = new ArrayList<Leg>(1); // using only one for brevity     
Leg obj = new Leg(200, "test", ...);
line.mLegList.add(obj);

Native CPP function
==================
JNIEXPORT void JNICALL Java_Driver_setLine (JNIEnv * env, jobject jobj1, jobject jobj)
{
    jclass cls = env->GetObjectClass(jobj);
    if (cls == NULL)
        return;     
    jmethodID ctorID = env->GetMethodID(cls, "<init>","()V");
    if (!ctorID)
       return ;

    // I did for one instance like this 
    /*************************************************************
    *  jfieldID fid_legID = env->GetFieldID(cls, "mLeg", "LLeg;");
    *  if (!fid_legID)
    *     return;
    *  jobject legObject = env->GetObjectField(jobj, fid_legID);
    *  jclass clsObject = env->GetObjectClass(legObject);
    *  if (clsObject) {
    *   // Get all fields of Leg inside Line ....
    *************************************************************/
    // Now as i changed it to list/arraylist of Legs, it isn't Working
    jfieldID fid_legID = env->GetFieldID(cls, "mLegList", "[LLeg;");

    if (!fid_legID)
        env->ExceptionDescribe();

    // Exception in thread "main" java.lang.NoSuchFieldError: mLegList      
}

My questions are:

  1. how to make it working with a list/ArrayList here. I need to retrieve a set of fields contained in this list.
  2. Why isn't the native function signature included jobjectarray (or something similar for jobj1?) Is that causing issues? Thanks for reading the post.

Update I was able to construct the object successfully as using Arraylist/List require to use fieldID fid_legID = env->GetFieldID(cls, "mLegList", "Ljava/util/List;"); instead. but now I'm wondering how to iterate through this list and read through all the fields.


Solution

  • Let's say your source code tree looks like this

    |-- c
    |   `-- Driver.c
    |-- lib
    |-- mypackage
    |   |-- Driver.java
    |   |-- Leg.java
    |   `-- Line.java
    `-- target
    

    and then, you have following files:

    mypackage/Driver.java

    package mypackage;
    
    import java.util.ArrayList;
    
    public class Driver {
    
      static {
        System.loadLibrary("Driver");
      }
    
      public void list(ArrayList<Leg> list) {
    
      }
    
      public native void setLine(Line line);
    
      public static void main(String [] arg) {
    
        Driver d = new Driver();
    
        Line line = new Line();
        line.id   = 200;
        line.mLegList = new ArrayList<Leg>(1);
    
        Leg obj_1 = new Leg(200, "test_1");
        Leg obj_2 = new Leg(300, "test_2");
        Leg obj_3 = new Leg(400, "test_2");
    
        line.mLegList.add(obj_1);
        line.mLegList.add(obj_2);
        line.mLegList.add(obj_3);
    
        d.setLine( line );
    
      }
    }
    

    mypackage/Leg.java

    package mypackage;
    
    class Leg {
        public int id;
        public String name;
    
        public Leg(int id, String name) {
          this.id = id;
          this.name = name;
        }
    }
    

    mypackage/Line.java

    package mypackage;
    
    import java.util.List;
    
    public class Line {
        public int id;
        public List<Leg> mLegList;
    }
    

    You can access elements of the List following way

    c/Driver.c

    #include "mypackage_Driver.h"
    
    JNIEXPORT void JNICALL Java_mypackage_Driver_setLine(JNIEnv * env, jobject jobj, jobject line)
    {
      jclass   cls_Line     = (*env)->GetObjectClass (env, line);
    
      jfieldID fid_mLegList = (*env)->GetFieldID (env, cls_Line, "mLegList", "Ljava/util/List;");
    
      jobject  list         = (*env)->GetObjectField (env, line, fid_mLegList);
      jclass   cls_list     = (*env)->GetObjectClass (env, list);
    
      int      listSize     = (*env)->GetArrayLength (env, list);
    
      for (int i = 0; i < listSize; i++) {
    
        jmethodID midGet   = (*env)->GetMethodID (env, cls_list, "get",
                                                "(I)Ljava/lang/Object;");
    
        jstring   obj      = (*env)->CallObjectMethod (env, list, midGet, i);
    
        jclass    cls_Leg  = (*env)->GetObjectClass(env, obj);
    
        jfieldID  fid_name = (*env)->GetFieldID( env, cls_Leg, "name", 
                                                 "Ljava/lang/String;");
    
        jobject   name     = (*env)->GetObjectField (env, obj, fid_name);
    
        const char *c_string = (*env)->GetStringUTFChars (env, name, 0);
    
        printf ("[value] = %s\n", c_string);
    
        (*env)->ReleaseStringUTFChars (env, obj, c_string);
      }
    
    }
    

    If you want to compile all the stuff, you can use following script

    #!/bin/bash
    
    mkdir -p target
    mkdir -p lib
    
    ARCH=`uname -s | tr '[:upper:]' '[:lower:]'`
    EXT=
    
    if [[ "${ARCH}" == "darwin" ]]; then
      EXT=dylib
    else
      EXT=so
    fi
    
    echo $ARCH
    
    javac -h c -d target mypackage/*.java
    
    cc -g -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/${ARCH} c/Driver.c -o lib/libDriver.${EXT}
    
    ${JAVA_HOME}/bin/java -Djava.library.path=${LD_LIBRARY_PATH}:./lib -cp target mypackage.Driver
    

    Once you run it, you will get what you are looking for :)

    > ./compile.sh
    darwin
    [value] = test_1
    [value] = test_2
    [value] = test_2
    

    Have fun with JNI :)

    Update

    1. accessing array elements using get method: recipeNo067
    2. accessing array elements using Iterator: recipeNo068
    3. passing ArrayList as Object []: recipeNo069

    Usage

    > git clone https://github.com/mkowsiak/jnicookbook.git
    > export JAVA_HOME=your_JDK_installation
    > cd jnicookbook/recipes/recipeNo067
    > make
    > make test