Click here to Skip to main content
15,867,835 members
Articles / Programming Languages / Java
Tip/Trick

Wrapping Java with C++ Objects - JNI OOP

Rate me:
Please Sign up or sign in to vote.
4.63/5 (6 votes)
27 Mar 2024CPOL3 min read 31.2K   363   13   5
A way to avoid JNI's reflection oriented programming. Wrapping Java with C++

Introduction

JNI was introduced as an interface for allowing Java code tuning on a JVM virtual machine to interact with Native code (C++). The JNI framework's implementation for interacting with Java code is strongly reflection oriented. This means that you get a "pointer" to an object that represents the Virtual Machine (JavaVM*), or a pointer to the "Environment" (JNIEnv*). After that, you need to "ask" the virtual machine or the environment for function handles or method handles on which you perform operations.

This gets very annoying very fast. The amount of repetitive copy-pasted code gets very difficult to handle, especially because of the use of strings in the reflection. This usually prevents programmers to scale-up the code and use C++ and JNI to call Java methods freely.

JNI C++ Wrappers

We need to bring back OOP to JNI. Represent the Java objects as Objects. This way, we can regain all the advantages of object oriented programming, including the reusability and code maintainability which we lost in the reflection-based JNI.

The ability to have a C++ style wrapper to handle Java objects can make JNI production much easier.

Proper interaction with Java classes should be easy to read and intuitive to C++ developers.

Java
CPoint3D myPoint(env);

myPoint.x = 5;
myPoint.y = 4;
mypoint.z = 3;

Just a reminder of the JNI method of doing stuff:

Java
jclass classObj =  env->FindClass("javax/vecmath/Point3d");
jmethodID classConstructor = env->GetMethodID(classObj, "<init>", "()V");
?jobject myPoint = env->NewObject(classObj , classConstructor);

jfieldID fidx = env->GetFieldID(classObj , "x", "I");
env->SetIntField(myPoint , fidx , 5);

jfieldID fidy = env->GetFieldID(classObj , "y", "I");
env->SetIntField(myPoint , fidy , 4);

jfieldID fidz = env->GetFieldID(classObj , "z", "I");
env->SetIntField(myPoint , fidz , 3);

Creating a C++ Wrapper

To create a C++ class wrapper for a Java object, just inherit from CJavaObject. Define all the class members and function members as CJavaMember objects.

Static members and methods can be defined as CJavaStaticMember, and initialize it with mClassTypeReflector instead of this.

Java
class CPoint3D : public CJavaObject
{
public:

    CPoint3D(JNIEnv* env, jclass iClassLoader = NULL) :
        CJavaObject(env, "javax/vecmath/Point3d", iClassLoader),
        x(this, "x", "I"),
        y(this, "y", "I"),
        z(this, "z", "I"),
        distanceFunc(this, "distance", "(Ljavax/vecmath/Point3d;)D")
    {    }

    CPoint3D(JNIEnv* env, jobject obj) : 
        CJavaObject(env, obj),
        x(this, "x", "I"),
        y(this, "y", "I"),
        z(this, "z", "I")
        distanceFunc(this, "distance", "(Ljavax/vecmath/Point3d;)D")
    {    }

    CJavaMember x;
    CJavaMember y;
    CJavaMember z;

    double distance(CPoint3D  p1) 
    { 
       double res;
       distance(&res, (jobject)p1); 
       return res;
    }

private:
    CJavaMember distanceFunc;
};

A C++ Java class wrapper will either create a Java object when constructed, or wrap an existing object instance if it's passed in the CJavaObject constructor.

Using an Anonymous Object

CJavaObject can anonymously access object members.
You can just instantiate a CJavaObject with a Java object (or create a new one by naming the type).
To access the object member fields or object member methods, use the overloaded operator[] with the field or method name and type.
Primitive member fields don't really need the type, it can be inferred by type of the rvalue.

Java
CJavaObject myPoint(env, "javax/vecmath/Point3d");
myPoint["x"] = 1;
myPoint["y"] = 2;
myPoint["z"] = 3;

double res;
myPoint["distance"]["(Ljavax/vecmath/Point3d;)D"](&res, (jobject)myPoint);

//or:
myPoint["distance", "(Ljavax/vecmath/Point3d;)D"](&res, (jobject)myPoint);

Implementation

Download the source header files. Include the JniWrapper.h file wherever you need the wrappers.
The wrappers use C++11 features like variadic templates, make sure you add the right compilation flags for that.

Considerations

  1. DeleteLocalRef - When constructing a Java object in JNI, you generally need to call DeleteLocalRef. I haven't added that to the destructor (like in the RAII idiom). This is because it is rarely really needed (unless it's a callback native thread). In a normal JNI call from Java, the local references are stored on the stack and therefore removed when you go back to the Java layer. And besides that, it can cause problems if the object is the return value of a JNI function.
  2. Class loader - The CJavaObject class can receive a class loader object so you can create new Java objects with a different loader context. (For example: if you're using a native callback function on a newly attached thread with an empty class loader). Here's what Google has to say about caching a class loader: http://developer.android.com/training/articles/perf-jni.html#native_libraries
  3. Error checking - The goal is to wrap Java classes statically. So, hopefully once you got it running once properly, with the right names, JNI names, etc., the wrapper will always work for that Java class. So I don't believe in adding redundant failure checking in the wrapper code itself.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Israel Israel
http://stackoverflow.com/users/536086/yochai-timmer

Comments and Discussions

 
QuestionPossible issue with clan Pin
Bellinghman25-Jan-18 3:07
Bellinghman25-Jan-18 3:07 
BugPrivate members of javaObject might refer to discarded objects Pin
cth0271-Jun-15 8:41
cth0271-Jun-15 8:41 
GeneralRe: Private members of javaObject might refer to discarded objects Pin
Yochai Timmer1-Jun-15 8:46
Yochai Timmer1-Jun-15 8:46 
GeneralRe: Private members of javaObject might refer to discarded objects Pin
cth0271-Jun-15 9:08
cth0271-Jun-15 9:08 
GeneralDeleteLocalRef() and risks of memory leaks Pin
cth0271-Jun-15 8:17
cth0271-Jun-15 8:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.