Skip to content

Instantly share code, notes, and snippets.

@dhilst
Last active October 18, 2016 01:05
Show Gist options
  • Select an option

  • Save dhilst/009632d6922bf309561e6d7979617815 to your computer and use it in GitHub Desktop.

Select an option

Save dhilst/009632d6922bf309561e6d7979617815 to your computer and use it in GitHub Desktop.
Java 2 C++ class maping
#ifndef LIBPEOPLE_H
#define LIBPEOPLE_H
#include <string>
using std::string;
// This is our native (C++) class. Is almost identical
// to the java class with the exception that here we declare
// the object attributes (age & name). The class
// is whole inlined to simplify the example. In real
// world you will have to link the _jni.so to the
// .so that implements the class and its methods.
class People {
private:
// our attributes
int age;
string name;
public:
// our constructor uses member initialization to
// initialize our object.
People(int age, string name) : age(age), name(name) {};
// Our getters, simply return attribute's values.
int getAge() { return age; }
string getName() { return name; }
};
#endif
// This is the JNI wrapper over People (C++) class.
// If you look for C++ JNI docs you will find nothing
// but this [1]. The difference between C and C++
// JNI calls is that JNIEnv can be used as a object
// pointer instead of raw pointer as in C. So the call:
//
// (*env)->JniFunction(env, ...);
//
// can be written as:
//
// env->JniFunction(...) in C++;
//
// This is the only difference and nothing else. The
// real JNI implementation is written in C and the
// notation above is a simple mapping to the (*env)->F(env, ...)
// form. In another words, syntax sugar. This is
// why you (and I) can't find any official JNI C++ API.
//
// [1] http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp224
#include <iostream>
#include <string>
#include <stdint.h>
// This is the above header declaring and
// initializing People class. As said before on real
// world applications you would need to link this
// file with the application's library.
#include "libpeople.h"
// This header is generate by javah tool.
#include "People.h"
using namespace std;
// This is the:
// private native long nativeNew(int age, String name)
// method. We allocate a new object with new operator.
// Pay attention that there is no delete call on this
// code so once the GC collect People object its
// native counterpart keeps in memory resulting in
// memory leak. Fixing this is left to user as an
// exercice.
extern "C"
JNIEXPORT jlong JNICALL Java_People_nativeNew
(JNIEnv *env, jobject self, jint age, jstring name)
{
// Boiler plate code to convert java String
// to C++ string. Notice that we have to
// convert jstring to (char *) then to std::string.
const char *raw = env->GetStringUTFChars(name, NULL);
string _name(raw);
env->ReleaseStringUTFChars(name, raw);
// Here we allocate our new object and return
// its pointer casted as a jlong;
People *p = new People(static_cast<int>(age), _name);
return reinterpret_cast<jlong>(p);
}
// This function is a helper providing the boiler
// plate code to return the native object from
// Java object. The "nativeObjectPointer" is reached
// from this code, casted to People's pointer and
// returned. This will be used in all our native
// methods wrappers to recover the object before
// invoking it's methods.
static People *getObject(JNIEnv *env, jobject self)
{
jclass cls = env->GetObjectClass(self);
if (!cls)
env->FatalError("GetObjectClass failed");
jfieldID nativeObjectPointerID = env->GetFieldID(cls, "nativeObjectPointer", "J");
if (!nativeObjectPointerID)
env->FatalError("GetFieldID failed");
jlong nativeObjectPointer = env->GetLongField(self, nativeObjectPointerID);
return reinterpret_cast<People *>(nativeObjectPointer);
}
// Here is our native methods wrappers, we simply recover
// native Poeple's instance invoke the requested method
// and return its return value. jint can be casted to
// Java's int. The string is a case apart. Since String
// is an object and not a primitive type we have to
// return it as reference (not by value). This is safe
// since, as a Java String object the JVM can deallocate
// it when is not being used anymore.
extern "C"
JNIEXPORT jint JNICALL Java_People_getAge
(JNIEnv *env, jobject self)
{
People *_self = getObject(env, self);
return static_cast<jint>(_self->getAge());
}
extern "C"
JNIEXPORT jstring JNICALL Java_People_getName
(JNIEnv *env, jobject self)
{
People *_self = getObject(env, self);
// Pay attention that we need to cast std::string
// to C string before constructing the Java String.
// This is because all JNI types are mapped to C
// and not to C++.
return env->NewStringUTF(_self->getName().c_str());
}
JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
CXXFLAGS += -Wall
CXXFLAGS += -I. -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
all: libpeople_jni.so
clean:
rm People.class libpeople_jni.so People.h
libpeople_jni.o: People.h libpeople_jni.cpp
libpeople_jni.so: libpeople_jni.o
People.h: People.class
People.class: People.java
%.o: %.cpp
$(CXX) $(CXXFLAGS) $(LDFLAGS) -fPIC -c $<
%.so: %.o
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $<
%.class: %.java
javac $<
%.h: %.class
javah -cp . $(<:.class=)
public class People {
static {
// Loads the libpeople_jni.so This is the place where our
// native methods reside.
System.loadLibrary("people_jni");
}
// This is a long here (in Java) but is used as a pointer to hold the
// address of our native object at "native world", i.e.,
// libpeople_jni.c.
private long nativeObjectPointer;
// This method is used to allocate an instance of this class at native
// world and return the address of it.
private native long nativeNew(int age, String name);
// Our constructor. The nativeNew() method is called to allocate a new
// instance of our native object and return its address. The address is
// assigned to nativeObjectPointer. Just as a note, Java forbiddes
// native constructors, so we need a native method to allocate our
// native object.
public People(int age, String name) {
nativeObjectPointer = nativeNew(age, name);
}
// These are our native methods. Calling any of then will:
// -> Recover native object from nativeObjectPointer.
// -> Translate parameters to native equivalents. This is
// not done here since we are not receiving parameters.
// -> Call the native equivalent method with native equivalent
// arguments.
// -> Translate its return value to Java equivalent.
// -> Return to java.
public native int getAge();
public native String getName();
public static void main(String[] args) {
// Allocate a new native object.
People me = new People(30, "Daniel Hilst Selli");
// Call native methods.
System.out.println("My name is " + me.getName() + " and I'm " + me.getAge() + " years old.");
// Note that we do not declare the attributes here (name & age),
// in this class but left it to the native code to do it.
// This satisfies the OO encapsulation principle. Next we'll see
// the native code declaring this same class.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment