7

How can I get the values of a jobject in C?
I use JNI and call a java function in C. The parameter is a jobject and it should look like that: {"John", "Ganso", 5}.
Now I want to get the values from that object but I dont know how. Do you have any suggestions how to solve that?
My struct in C looks like my class in java.

My code looks like that:

JNIEXPORT void JNICALL
Java_model_JNIResultSet_printToFile(JNIEnv *env, jobject obj,
    jobject o) {

// How can I get values of jobject o?
}
mafioso
  • 1,630
  • 4
  • 25
  • 45

3 Answers3

17
/* PassObject.java */
package recipeNo020;

public class PassObject {

    /* This is the native method we want to call */
    public static native void displayObject(Object obj);

    /* Inside static block we will load shared library */
    static {
            System.loadLibrary("PassObject");
    }

    public static void main(String[] args) {
    /* This message will help you determine whether
        LD_LIBRARY_PATH is correctly set
    */
    System.out.println("library: "
        + System.getProperty("java.library.path"));

    /* Create object to pass */
    CustomClass cc = new CustomClass();
    cc.iVal = 1;
    cc.dVal = 1.1;
    cc.cVal = 'a';
    cc.bVal = true;
    cc.sVal = "Hello from the CustomClass";
    cc.oVal = new OtherClass();
    cc.oVal.sVal = "Hello from the OtherClass";

    /* Call to shared library */
        PassObject.displayObject(cc);
  }
}

/* CustomClass.java */
package recipeNo020;

public class CustomClass {
    public int iVal;
    public double dVal;
    public char cVal;
    public boolean bVal;
    public OtherClass oVal;
    public String sVal;
}

/* OtherClass.java */
package recipeNo020;

public class OtherClass {
    public String sVal;
}

/* recipeNo020_PassObject.c */

#include <stdio.h>
#include "jni.h"
#include "recipeNo020_PassObject.h"

JNIEXPORT void JNICALL Java_recipeNo020_PassObject_displayObject
  (JNIEnv *env, jclass obj, jobject objarg) {

    /* Get objarg's class - objarg is the one we pass from
       Java */
    jclass cls = (*env)->GetObjectClass(env, objarg);

    /* For accessing primitive types from class use
           following field descriptors

           +---+---------+
           | Z | boolean |
           | B | byte    |
           | C | char    |
           | S | short   |
           | I | int     |
           | J | long    |
           | F | float   |
           | D | double  |
           +-------------+
    */

    /* Get int field

       Take a look here, we are passing char* with
           field descriptor - e.g. "I" => int
        */
    jfieldID fidInt = (*env)->GetFieldID(env, cls, "iVal", "I");
    jint iVal = (*env)->GetIntField(env, objarg, fidInt);
    printf("iVal: %d\n", iVal);

    /* Get double field */
    jfieldID fidDouble = (*env)->GetFieldID(env, cls, "dVal", "D");
    jdouble dVal = (*env)->GetIntField(env, objarg, fidDouble);
    printf("dVal: %f\n", dVal);

    /* Get boolean field */
    jfieldID fidBoolean = (*env)->GetFieldID(env, cls, "bVal", "Z");
    jboolean bVal = (*env)->GetIntField(env, objarg, fidBoolean);
    printf("bVal: %d\n", bVal);

    /* Get character field */
    jfieldID fidChar = (*env)->GetFieldID(env, cls, "cVal", "C");
    jboolean cVal = (*env)->GetIntField(env, objarg, fidChar);
    printf("cVal: %c\n", cVal);

    /* Get String field */
    jfieldID fidString = (*env)->GetFieldID(env, cls, "sVal", "Ljava/lang/String;");
    jobject sVal = (*env)->GetObjectField(env, objarg, fidString);

    // we have to get string bytes into C string
    const char *c_str;
    c_str = (*env)->GetStringUTFChars(env, sVal, NULL);
    if(c_str == NULL) {
            return;
    }

    printf("sVal: %s\n", c_str);

    // after using it, remember to release the memory
    (*env)->ReleaseStringUTFChars(env, sVal, c_str);

    /* Get OtherClass */
    jfieldID fidOtherClass = (*env)->GetFieldID(env, cls, "oVal", "LrecipeNo020/OtherClass;");
    jobject oVal = (*env)->GetObjectField(env, objarg, fidOtherClass);

    jclass clsOtherClass = (*env)->GetObjectClass(env, oVal);

    /* Once we have OtherClass class and OtherClass object
       we can access OtherClass'es components
    */

    /* Get String field from OtherClass */
    jfieldID fidStringOtherClass = (*env)->GetFieldID(env, clsOtherClass, "sVal", "Ljava/lang/String;");
    jobject sValOtherClass = (*env)->GetObjectField(env, oVal, fidStringOtherClass);

    // we have to get string bytes into C string
    const char *c_str_oc;
    c_str_oc = (*env)->GetStringUTFChars(env, sValOtherClass, NULL);
    if(c_str_oc == NULL) {
        return;
    }

    printf("OtherClass.sVal: %s\n", c_str_oc);

    // after using it, remember to release the memory
    (*env)->ReleaseStringUTFChars(env, sValOtherClass, c_str_oc);
}

/* Make file */

include ../Makefile.common

all: compilejava compilec

compilec:
    cc -g -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/$(ARCH) c/recipeNo020_PassObject.c -o lib/libPassObject.$(EXT)


compilejava:
    $(JAVA_HOME)/bin/javac -cp target -d target java/recipeNo020/OtherClass.java
    $(JAVA_HOME)/bin/javac -cp target -d target java/recipeNo020/CustomClass.java
    $(JAVA_HOME)/bin/javac -cp target -d target java/recipeNo020/PassObject.java
    $(JAVA_HOME)/bin/javah -jni -d c -cp target recipeNo020.PassObject

test:
    $(JAVA_HOME)/bin/java -Djava.library.path=$(LD_LIBRARY_PATH):./lib -cp target recipeNo020.PassObject

.PHONY: clean
clean:
    -rm -rfv target/*
    -rm c/recipeNo020_PassObject.h
    -rm -rfv lib/*

/* directory structure */
.
├── Makefile
├── c
│   └── recipeNo020_PassObject.c
├── java
│   └── recipeNo020
│       ├── CustomClass.java
│       ├── OtherClass.java
│       └── PassObject.java
├── lib
└── target

/* execution */
make
make test

You can find it easier to checkout the project and compile it:

https://github.com/mkowsiak/jnicookbook

Oo.oO
  • 12,464
  • 3
  • 23
  • 45
  • 2
    .oOo. np ;) Have fun with JNI! .oOo. – Oo.oO Oct 11 '17 at 20:02
  • 1
    Got idea to get an arrayobject from one class to another from your answer. Thanku soo much.. – Shachi Oct 17 '18 at 09:18
  • thank you so much for this answer. I have small question when we want to call c++ function from Java. For example this method is in C++: `int myFunction(JNIEnv *env, jclass obj, jint a, MyCppClass* object)` If I use Register Narratives method inside `JNI_Onload` i should have `static JNINativeMethod methods[] = {{"myFunction", "(I?)I", (void *)&addOne}}`. What should be in the place of `?` for the MyCppClass* argument? Thank you so much in advance – peco Oct 20 '20 at 13:24
  • 1
    You can’t mix C++ classes with JNI types. You can pass pointers between C++ and Java, but you can’t pass C++ class. In case of C++ code I suggest using adapter pattern: https://github.com/mkowsiak/jnicookbook/tree/master/recipes/recipeNo025 – Oo.oO Oct 20 '20 at 14:51
3

Whether you're writing native methods or embedding a JVM in your C program, you really ought to read the JNI documentation. It contains quite a bit of information you will need to know, including details of the JNI functions for accessing the fields of a Java object.

In brief, to get the value of a field, you

  1. Obtain its field ID via GetFieldID(). This will require that you also get (or already have) a jclass object representing the class to which the field belongs; you might obtain that via GetObjectClass() or FindClass().

  2. Obtain the field's value with the one of the GetXXXField() methods for which XXX is appropriate to the field's declared type. For Java Strings, that would be GetObjectField(); for Java ints it would be GetIntField().

If you want to look at the details of the strings, you will need to also use some of the String manipulation functions, such as GetStringUTFChars() and GetStringUTFLength(). And do not overlook the important distinction between those functions, which operate in terms of modified UTF-8, and the analogous functions that operate in terms of "Unicode characters" (which really means UTF-16).

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • GetFieldId() expects the field name and the data type of the field, what if we don't know what these two things are ? is there a way to get them ? – kumarD Feb 16 '17 at 14:54
  • @kumarD, you can use Java's reflection API (`java.lang.Class`, `java.lang.reflect.Field`, etc.), more easily from the Java side but from the native side if necessary, to glean information about the fields of a class. JNI has [functions that can bridge between reflection API objects and JNI](http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#reflection_support). But if you find yourself wanting to do this then you should probably have a good long think. If that doesn't disabuse you of the notion, then perhaps a bonk on the head will help. – John Bollinger Feb 16 '17 at 16:52
  • Actually i want to capture the state of the application like method variable values, class variable values etc when an exception occurs, this is the only way in which that can be achieved right ? – kumarD Feb 17 '17 at 05:03
  • @kumarD, you need much more than this if you want to examine the values of local variables of methods on the call stack, or the values of fields of the objects / classes on which those methods were invoked. Such activities go far beyond what the OP asked about, and are not (directly) supported by JNI. Perhaps you want to look into [JVMTI](https://docs.oracle.com/javase/8/docs/technotes/guides/jvmti/). – John Bollinger Feb 17 '17 at 14:57
1

I agree with John Bollinger. Here is an example based on another [question]:JNI. How to get jstring from jobject and convert it to char*

In your case if the class in java is Person and the fields are firstName, lastName, and age then you might try the following code:

    // How can I get values of jobject o?
    jclass personClass = (*env)->GetObjectClass(env, o);
    jfieldID firstNameId = (*env)->GetFieldID(env,personClass,"firstName","S");
    jstring firstNameString = (jstring)(*env)->GetObjectField(env, o, firstNameId);
    jfieldID lastNameId = (*env)->GetFieldID(env,personClass,"lastName","S");
    jstring lastNameString = (jstring)(*env)->GetObjectField(env, o, lastNameId);
    jfieldID ageId = (*env)->GetFieldID(env,personClass,"age","I");
    jint age = (*env)->GetIntField(env,o,ageId);

Now you can use that data to fill your struct.

Community
  • 1
  • 1
user1759789
  • 169
  • 2
  • 15