Last active
October 18, 2016 01:05
-
-
Save dhilst/009632d6922bf309561e6d7979617815 to your computer and use it in GitHub Desktop.
Java 2 C++ class maping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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()); | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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=) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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