Naumann
Naumann

Reputation: 407

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.

Upvotes: 0

Views: 1850

Answers (2)

Oo.oO
Oo.oO

Reputation: 13375

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

Upvotes: 1

Botje
Botje

Reputation: 30830

Iterating across a List in JNI is the same as in Java, it is just more typing.

jobject list = env->GetObjectField(line, fid_legID);

// Iterator iterator = list.iterator();
jclass cls_List = env->FindClass("java/util/List");
jmethodID mid_List_iterator = env->GetMethodID(cls_List, "iterator", "()Ljava/util/Iterator;");
jobject iterator = env->CallObjectMethod(list, mid_List_iterator);

jclass cls_Iterator = env->FindClass("java/util/Iterator");
jmethodID mid_Iterator_hasNext = env->GetMethodID(cls_Iterator, "hasNext", "()Z");
jmethodID mid_Iterator_next = env->GetMethodID(cls_Iterator, "next", "()Ljava/lang/Object");

while (true) {
    // if !iterator.hasNext() break
    jboolean hasNext = env->CallBooleanMethod(iterator, mid_Iterator_hasNext);
    if (!hasNext)
        break;

    jobject v = env->CallObjectMethod(iterator, mid_Iterator_next);
    // Do something with `v`

    // Avoid overflowing the local reference table if legList gets large
    env->DeleteLocalRef(v);
}

Note that this example still needs error checking.

Upvotes: 1

Related Questions