Search code examples
java-native-interfacejniwrapper

How to call a Java function in JNI with custom class type argument


I'm trying to call a Java function from C++ with a custom class type argument. The issue is that the Java function gets garbage values when called from C++ (code shown below). If I use the argument of any other data type (String, float, int) the same function printClassVar works correctly.

This is the Java code

/*Filename: CallbacksExample.java*/

public class CallbacksExample {
    static {
        System.loadLibrary("myjni");
    }
    
    public static void main(String[] args) {
        new CallbacksExample().callback();
    }

    public native void callback(); 

    public static void printClassVar(Person person)
    {
        System.out.println("Got callback from C++: " + person.age );
    }
}
/*Filename: Person.java*/

public class Person {

    public int age;

    public Person() {
        this.age = 20;
    }

    public int value() {
        return age;
    }
}

And here is the JNI code

/*Filename: CallbacksExample.cpp*/

#include <iostream>
#include <jni.h>
#include "CallbacksExample.h"

using namespace std;

class Person
{
    int age;

public:
    Person()
    {
        age = 5;
    }

    int value()
    {
        return age;
    }
};

JNIEXPORT void JNICALL
Java_CallbacksExample_callback(JNIEnv *env, jobject jthis)
{
    jclass thisClass = env->GetObjectClass(jthis);

    Person _per;
    Person* class_ptr = &_per;

    std::cout << class_ptr->value() << std::endl;     

    jmethodID printClassVar = env->GetStaticMethodID(thisClass, "printClassVar", "(LPerson;)V");
    if (NULL == printClassVar)
        return;

    env->CallVoidMethod(jthis, printClassVar, &class_ptr);
}

The above code returns

Got callback from C++: 1679598160 (Or any garbage signed int value)


Solution

  • Here is the proper way to call a Java function with class type argument in C++

    /*Filename: CallbacksExample.java*/
    
    public class CallbacksExample {
        static {
            System.loadLibrary("myjni");
        }
        
        public static void main(String[] args) {
            new CallbacksExample().callback();
        }
    
        public native void callback(); 
    
        public static void printClassVar(Person person)
        {
            System.out.println("Got callback from C++: " + person.age );
        }
    }
    
    /*Filename: Person.java*/
    
    public class Person {
    
        public int age;
    
        public Person() {
            this.age = 20;
        }
    
        public void set(int x)
        {
            age = x;
        }
    
        public int value() {
            return age;
        }
    
    }
    
    /*Filename: CallbacksExample.cpp*/
    
    #include <iostream>
    #include <jni.h>
    #include "CallbacksExample.h"
    
    JNIEXPORT void JNICALL
    Java_CallbacksExample_callback(JNIEnv *env, jobject jthis)
    {
        jclass thisClass = env->GetObjectClass(jthis);
    
        jclass personClass = env->FindClass("Person");
    
        jmethodID class_constructor = env->GetMethodID(personClass, "<init>", "()V"); // no parameters
        jobject personObj = env->NewObject(personClass, class_constructor);
    
        auto personMethod = env->GetMethodID(personClass, "set", "(I)V");
        env->CallVoidMethod(personObj, personMethod, 15);
    
        jmethodID printClassVar = env->GetStaticMethodID(thisClass, "printClassVar", "(LPerson;)V");
        if (NULL == printClassVar)
            return;
    
        env->CallVoidMethod(jthis, printClassVar, personObj);
    }