Last active
November 25, 2019 11:41
-
-
Save bitsnaps/3cfdcf92f6dfef952fbfc5f50c4713b6 to your computer and use it in GitHub Desktop.
Simple example using JNI with MinGW C++
This file contains 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
@echo off | |
echo compile java class | |
javac HelloJNI.java | |
rem list methods signature (-s for signature, -p show privates) | |
rem javap -p -s HelloJNI | |
echo generate header file | |
javah -jni HelloJNI | |
echo compile DLL | |
g++64 -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o HelloCpp.dll main.cpp | |
echo run java program | |
java -Djava.library.path=. HelloJNI | |
echo Clean up | |
del *.class; del *.dll, del *.h | |
pause |
This file contains 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
import java.util.*; | |
class HelloJNI extends Hello { | |
static { | |
System.loadLibrary("HelloCpp"); // HelloCpp.dll (Windows) or libhellocpp.so (*nix) | |
} | |
//instance variable | |
private int number = 10; | |
private String message = "Hello from Java"; | |
//static variable | |
private static double staticNumber = 12.03; | |
private native void modifyInstanceVariable(); | |
private native void modifyStaticVariable(); | |
public native double sayHello(int i, String s); | |
public native void showPrimitives(int i, byte b, short s, long l, float f, double d, char c, boolean bo); | |
public native double[] sumArray(int[] intArray); | |
public native Double[] sumAndAverage(Integer[] intArray); | |
//native method that calls back java methods | |
private native void nativeMethod(); | |
//method to be called back from native code | |
private void callback(){ | |
System.out.println("callback method In java"); | |
} | |
private void callback(String message){ | |
System.out.println("callback method In java with msg: "+message); | |
} | |
private double callbackAverage(int n1, int n2) { | |
return ((double) n1 + n2) / 2.0; | |
} | |
private static String callbackStatic(){ | |
return "From static java method"; | |
} | |
private native void methodException() throws IllegalArgumentException; | |
private void callbackException() throws NullPointerException { | |
throw new NullPointerException("CatchThrow.callback"); | |
} | |
private native ArrayList<Integer> makeList(int i); | |
// Native method that calls back the constructor and return the constructed object. | |
// Return an Integer object with the given int. | |
private native Integer getIntegerObject(int number); | |
public static void main(String[] args){ | |
HelloJNI hello = new HelloJNI(); | |
double d = hello.sayHello(12, "World"); | |
System.out.println(d); | |
hello.showPrimitives(123, (byte) 65, (short) 13, (long) 1234567, 12f, 12345678d, 'A', true); | |
int[] arr = {1, 2, 3}; | |
double[] stat = hello.sumArray(arr); | |
System.out.println("Total: " + stat[0] + ", Average: " + stat[1]); | |
System.out.println("Number value before call: " + hello.number); | |
System.out.println("Message before call: " + hello.message); | |
hello.modifyInstanceVariable(); | |
System.out.println("Number value after call: " + hello.number); | |
System.out.println("Message after call: " + hello.message); | |
System.out.println("Static Number before call: " + staticNumber); | |
hello.modifyStaticVariable(); | |
System.out.println("Static Number after call: " + staticNumber); | |
//call all callBack methods from C++ | |
hello.nativeMethod(); | |
System.out.println("In java number = " + hello.getIntegerObject(123)); | |
Integer[] numbers = {1, 2, 3}; | |
Double[] result = hello.sumAndAverage(numbers); | |
System.out.println("Sum of array = " + result[0] + ", average = " + result[1]); | |
System.out.println(hello.makeList(3)); | |
//rais an exception from native code | |
try { | |
hello.methodException(); | |
} catch (Exception e){ | |
System.out.println("In java:\n\t" + e.getMessage()); | |
} | |
} | |
} | |
class Hello{ | |
private void callBaseClass(){ | |
System.out.println("Message from super BaseClass"); | |
} | |
} |
This file contains 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
#include <iostream> | |
#include <jni.h> | |
#include <stdio.h> | |
#include <windows.h> | |
#include "HelloJNI.h" | |
using namespace std; | |
static jclass intClass; | |
JNIEXPORT jdouble JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj, jint n1, jstring s){ | |
//get the jstring | |
const char *str = env->GetStringUTFChars(s, 0); | |
if (str == NULL) return 0.0; | |
cout << "Hello " << str << ", i = " << n1 << endl; | |
//printf("Hello %s, i=%d\n", str, n1); | |
env->ReleaseStringUTFChars(s, str); | |
/*string outCppStr; | |
cout << "Enter a String: "; | |
cin >> outCppStr; | |
return env->NewStringUTF(outCppStr.c_str()); | |
*/ | |
return n1 + 1.0; | |
} | |
JNIEXPORT void JNICALL Java_HelloJNI_showPrimitives | |
(JNIEnv * env, jobject obj, jint i, jbyte b, jshort s, jlong l, jfloat f, jdouble d, jchar c, jboolean bo){ | |
cout << "Integer:" << i << endl; | |
cout << "Byte:" << b << endl; | |
cout << "Short:" << s << endl; | |
cout << "Long:" << l << endl; | |
cout << "Float:" << f << endl; | |
cout << "Double:" << d << endl; | |
cout << "Char:" << c << endl; | |
cout << "Boolean:" << (bo?"true":"false") << endl; | |
} | |
JNIEXPORT jdoubleArray JNICALL Java_HelloJNI_sumArray | |
(JNIEnv * env, jobject obj, jintArray arr){ | |
double sum = 0.0; | |
//Convert the incoming JNI jintarray to C's jint[] | |
jint *cArray = env->GetIntArrayElements(arr, NULL); | |
if (NULL == arr) return NULL; | |
jsize length = env->GetArrayLength(arr); | |
//Perform operations | |
for (int i = 0; i < length; i++){ | |
sum += cArray[i]; | |
} | |
jdouble average = (jdouble) sum / length; | |
jdouble arrValues[] = {sum, average}; | |
//Release resoruces | |
env->ReleaseIntArrayElements(arr, cArray, 0); | |
//Convert C's array jdouble[] to JNI jdoublearray | |
jdoubleArray outArray = env->NewDoubleArray(2); //allocate | |
if (NULL == outArray) return NULL; | |
env->SetDoubleArrayRegion(outArray, 0, 2, arrValues); //copy portion (from start to length) | |
cout << "Length: " << length << endl; | |
return outArray; | |
} | |
//Modify variable instance | |
JNIEXPORT void JNICALL Java_HelloJNI_modifyInstanceVariable(JNIEnv* env, jobject thisObj){ | |
//get reference to "this" object's class | |
jclass thisClass = env->GetObjectClass(thisObj); | |
//get Field ID of the instance variable "number" | |
jfieldID fieldNumber = env->GetFieldID(thisClass, "number", "I"); //For a Java class, the field descriptor is in the form of "L<fully-qualified-name>;", with dot replaced by forward slash (/), e.g.,, the class descriptor for String is "Ljava/lang/String;". For primitives, use "I" for int, "B" for byte, "S" for short, "J" for long, "F" for float, "D" for double, "C" for char, and "Z" for boolean. For arrays, include a prefix "[", e.g., "[Ljava/lang/Object;" for an array of Object; "[I" for an array of int. You can list the method signature for a Java program via javap utility (Class File Disassembler) with -s (print signature) and -p (show private members) | |
if (NULL == fieldNumber) return; | |
//get int value given by FieldID | |
jint number = env->GetIntField(thisObj, fieldNumber); | |
//cout << "Number:" << number << endl; | |
//change the variable | |
number = 11; | |
env->SetIntField(thisObj, fieldNumber, number); | |
//Get the fieldID of the instance variable "message" | |
jfieldID fieldMessage = env->GetFieldID(thisClass, "message", "Ljava/lang/String;"); | |
if (NULL == fieldMessage) return; | |
//get string variable from fieldMessage | |
jstring message = (jstring) env->GetObjectField(thisObj, fieldMessage); | |
//create a C++ string with JNI string | |
const char* cppStr = env->GetStringUTFChars(message, NULL); | |
if (NULL == cppStr) return; | |
//cout << "Message = " << cppStr << endl; | |
//Release resources | |
env->ReleaseStringUTFChars(message, cppStr); | |
//Create a new cpp String and assign to the JNI string | |
message = env->NewStringUTF("Hello from C++"); | |
if (NULL == message) return; | |
env->SetObjectField(thisObj, fieldMessage, message); | |
} | |
//Modify static variable | |
JNIEXPORT void JNICALL Java_HelloJNI_modifyStaticVariable(JNIEnv *env, jobject thisObj){ | |
//get reference to the class | |
jclass cls = env->GetObjectClass(thisObj); | |
//read variable value | |
jfieldID fieldNumber = env->GetStaticFieldID(cls, "staticNumber", "D"); | |
if (NULL == fieldNumber) return; | |
jdouble number = env->GetStaticDoubleField(cls, fieldNumber); | |
//cout << "Static Number: " << number << endl; | |
number = 3.14; | |
//modifier static variable | |
env->SetStaticDoubleField(cls, fieldNumber, number); | |
} | |
//callback java methods | |
JNIEXPORT void JNICALL Java_HelloJNI_nativeMethod(JNIEnv *env, jobject thisObj){ | |
//get reference to "this" object's class | |
jclass thisClass = env->GetObjectClass(thisObj); | |
jmethodID mCallback = env->GetMethodID(thisClass, "callback", "()V"); | |
if (NULL == mCallback) return; | |
env->CallVoidMethod(thisObj, mCallback); | |
//call "callback(String)" | |
jmethodID mCallbackMsg = env->GetMethodID(thisClass, "callback", "(Ljava/lang/String;)V"); | |
if (NULL == mCallbackMsg) return; | |
env->CallVoidMethod(thisObj, mCallbackMsg, env->NewStringUTF("Hello from ++") ); | |
//call "callbackAverage(int, int)" | |
jmethodID mCallbackAverage = env->GetMethodID(thisClass, "callbackAverage", "(II)D"); | |
if (NULL == mCallbackAverage) return; | |
cout << "Average: " << env->CallDoubleMethod(thisObj, mCallbackAverage, 2, 3) << endl; | |
//call static method "callbackStatic" | |
jmethodID mCallbackStatic = env->GetStaticMethodID(thisClass, "callbackStatic", "()Ljava/lang/String;"); | |
if (NULL == mCallbackStatic) return; | |
//type cast is important here in c++ | |
jstring in_str = (jstring) env->CallStaticObjectMethod(thisClass, mCallbackStatic); | |
const char* out_str = env->GetStringUTFChars(in_str, NULL); | |
if (NULL == out_str) return; | |
cout << "callback static string: " << out_str << endl; | |
env->ReleaseStringUTFChars(in_str, out_str); | |
//call super class method | |
jmethodID mCallbackBaseClassMethod = env->GetMethodID(thisClass, "callBaseClass" ,"()V"); | |
if (NULL == mCallbackBaseClassMethod) return; | |
env->CallNonvirtualVoidMethod(thisObj, thisClass, mCallbackBaseClassMethod); | |
} | |
JNIEXPORT jobject JNICALL Java_HelloJNI_getIntegerObject(JNIEnv *env, jobject thisObj, jint number){ | |
//get a reference for java.lang.Integer class | |
jclass tempIntClass = env->FindClass("java/lang/Integer"); | |
//you can save a integer class reference to a global reference for later use (you can't do that for jmethodID and jfieldID they aren't objects) | |
intClass = (jclass) env->NewGlobalRef(tempIntClass); | |
//no longer need the local reference | |
env->DeleteLocalRef(tempIntClass); | |
//get the methodID of the constructor which takes an int | |
jmethodID mInit = env->GetMethodID(intClass, "<init>", "(I)V"); | |
if (NULL == mInit) return NULL; | |
//call back constructor to allocate a new instance with int argument | |
jobject intObj = env->NewObject(intClass, mInit, number); | |
//call to call ToString() on this object | |
jmethodID mToString = env->GetMethodID(intClass, "toString", "()Ljava/lang/String;"); | |
if (NULL == mToString) return NULL; | |
jstring in_str = (jstring) env->CallObjectMethod(intObj, mToString); | |
const char* out_str = env->GetStringUTFChars(in_str, NULL); | |
cout << "Number in C++: " << out_str << endl; | |
return intObj; | |
} | |
//call a method with Array of Object argument | |
JNIEXPORT jobjectArray JNICALL Java_HelloJNI_sumAndAverage(JNIEnv *env, jobject thisObj, jobjectArray intArray){ | |
//reference to java's Integer | |
jclass intClass = env->FindClass("java/lang/Integer"); | |
//use Integer.intValue() to retreive the int | |
jmethodID mIntValue = env->GetMethodID(intClass, "intValue", "()I"); | |
if (NULL == mIntValue) return NULL; | |
//get the size of object array | |
jsize length = env->GetArrayLength(intArray); | |
jint sum = 0; | |
for (int i = 0; i < length; i++){ | |
jobject intObj = env->GetObjectArrayElement(intArray, i); | |
if (NULL == intObj) return NULL; | |
sum += env->CallIntMethod(intObj, mIntValue); // may need a cast! | |
} | |
double average = (double) sum / length; | |
jclass classDouble = env->FindClass("java/lang/Double"); | |
//allocate a jobjectArray of 2 Double | |
jobjectArray outArray = env->NewObjectArray(2, classDouble, NULL); | |
jmethodID mDoubleInit = env->GetMethodID(classDouble, "<init>", "(D)V"); | |
if (NULL == mDoubleInit) return NULL; | |
jobject objSum = env->NewObject(classDouble, mDoubleInit, (double) sum); | |
jobject objAve = env->NewObject(classDouble, mDoubleInit, average); | |
//set to the jobjectArray | |
env->SetObjectArrayElement(outArray, 0, objSum); | |
env->SetObjectArrayElement(outArray, 1, objAve); | |
return outArray; | |
} | |
JNIEXPORT void JNICALL Java_HelloJNI_methodException(JNIEnv *env, jobject obj){ | |
jthrowable exc; | |
jclass cls = env->GetObjectClass(obj); | |
jmethodID mid = env->GetMethodID(cls, "callbackException", "()V"); | |
if (mid == NULL) return; | |
//try { | |
env->CallVoidMethod(obj, mid); | |
//} catch (const std::exception& e){ | |
//cout << "Exception thrown from native code" << endl; | |
//} | |
exc = env->ExceptionOccurred(); | |
if (exc) { | |
jclass newCls = env->FindClass("java/lang/IllegalArgumentException"); | |
if (newCls == NULL) return; //unable to find exception, so just leave | |
env->ExceptionDescribe(); | |
env->ExceptionClear(); | |
cout << "Exception thrown from native code" << endl; | |
//raise the an IllegalArgumentException | |
//env->ThrowNew(newCls, "Exception thrown from native code"); | |
} | |
} | |
//call method with ArrayList type argument | |
JNIEXPORT jobject JNICALL Java_HelloJNI_makeList(JNIEnv *env, jobject thisObj, jint i){ | |
jclass arrClass = env->FindClass("java/util/ArrayList"); | |
if (arrClass == NULL) return NULL; | |
jmethodID initArr = env->GetMethodID(arrClass, "<init>", "(I)V"); | |
if (initArr == NULL) return NULL; | |
jmethodID arrSize = env->GetMethodID (arrClass, "size", "()I"); | |
if (arrSize == NULL) return NULL; | |
jobject arr = env->NewObject(arrClass, initArr); | |
jmethodID arrAdd = env->GetMethodID(arrClass, "add", "(Ljava/lang/Object;)Z"); | |
if (arrAdd == NULL) return NULL; | |
jmethodID arrGet = env->GetMethodID(arrClass, "get", "(I)Ljava/lang/Object;"); | |
if (arrGet == NULL) return NULL; | |
for (int j = 1; j <= i; ++j) { | |
jclass intClass = env->FindClass("java/lang/Integer"); | |
jmethodID mInit = env->GetMethodID(intClass, "<init>", "(I)V"); | |
jobject intObj = env->NewObject(intClass, mInit, j); | |
jboolean reslt = (jboolean) env->CallBooleanMethod(arr, arrAdd, intObj); | |
if (!reslt) | |
return NULL; | |
} | |
jint size = (jint) env->CallIntMethod(arr, arrSize); | |
cout << "ArrayList size: " << size << endl; | |
return arr; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There's a typo in
Java_HelloJNI_methodException
should be