Skip to content

Instantly share code, notes, and snippets.

@bitsnaps
Last active November 25, 2019 11:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bitsnaps/3cfdcf92f6dfef952fbfc5f50c4713b6 to your computer and use it in GitHub Desktop.
Save bitsnaps/3cfdcf92f6dfef952fbfc5f50c4713b6 to your computer and use it in GitHub Desktop.
Simple example using JNI with MinGW C++
@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
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");
}
}
#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;
}
@jessepav
Copy link

There's a typo in Java_HelloJNI_methodException

if (newCls = NULL) return; //unable to find exception, so just leave

should be

if (newCls == NULL) return; //unable to find exception, so just leave

@bitsnaps
Copy link
Author

bitsnaps commented Nov 25, 2019

There's a typo in Java_HelloJNI_methodException

@jessepav Fixed, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment