Skip to content

Instantly share code, notes, and snippets.

@dhilst
Last active October 29, 2016 05:57
Show Gist options
  • Save dhilst/6c3f3d3525a803870bf2a8091c70f7ae to your computer and use it in GitHub Desktop.
Save dhilst/6c3f3d3525a803870bf2a8091c70f7ae to your computer and use it in GitHub Desktop.
JNIExceptionMain.java
// At this example I will show how to throw and handle exceptions from native
// code. And how you can map the native return codes to different Exceptions in
// Java. I follow principles here.
//
// First. Less is more. The less classes you have tied to native code, the
// better. You can simply think this as encapsulation. The native code should
// be encapsuled in the smallest number of classes as possible.
//
// Sencond. Play safe. Surround all the error report so that at last one
// exception arrises. Even when native code return unexpected values. This
// makes easier to detect "tweak here break there" problem.
//
// Third. Head up, heel down. Keep the smart part to higher levels, and working
// wells down. Native code is paintfull to distribute. The less you have it,
// the better.
// This and the next represent the exceptions that the programmer is exporting
// to other Java programmers. These represent real problems.
class JNIException extends Exception {
JNIException(Throwable e) {
super(e);
}
}
class JNIException2 extends Exception {
JNIException2(Throwable e) {
super(e);
}
}
// This is one RuntimeException. All native methods that may fail fails by
// throwing one of this. This way our native code has to know only one class
// for error reporting. One int is used to return native code error. With this
// aproach you can make your native methods return any value for int or Object
// (even null) and report any possible error by throw and RuntimeException. The
// wrappers are responsible to handle native errors and rethrowing when it
// can't.
class JNINativeException extends RuntimeException {
int code;
JNINativeException(int c, String msg) {
super(msg);
code = c;
}
public int getCode() {
return code;
}
}
public class JNIExceptionMain {
static {
System.loadLibrary("exception");
}
// A simple method to exemplificate about exception handling on native
// code. Our native library will call this and hadle that exception.
static void javaMethod() throws Exception {
throw new Exception("Exception from Java world");
}
// This is our native method. If it fails it throws one RuntimeException.
// the wrappers can decide what Exceptions to throw. This way the rule
// about what exception has to be handled by user is all keeped on Java
// layer.
static native void nativeMethod(int i) throws JNINativeException;
// This is our wrapper. We shouldn't let user call native code directly
// since if something in native behaviour changes the fix can be maded
// at this level instead of at JNI layer. This make our encapsulation
// even better since processing can be added before and after crossing
// the Java/JNI border.
public static void nativeWrapper(int i)
// Here I chose what exceptions are visible to my user and what
// not. With this pattern if native return code changes the
// RuntimeException is throw but it is not needed to be handled
// explicitly by user. Notice that JniNativeException does not
// need to be handled explicitly by user. This encapsulates
// native error report from user.
throws JNIException, JNIException2 {
try {
nativeMethod(i);
} catch (JNINativeException e) {
// Here I handle the error code returned by native code. If the
// native code changes the default is executed. This can be easily
// spotted and fixed with right handling by adding a new case. :-)
switch (e.getCode()) {
case -1:
throw new JNIException(e);
case -2:
throw new JNIException2(e);
default:
throw e;
}
}
}
public static void main(String[] args)
throws JNIException2 {
try {
nativeWrapper(Integer.parseInt(args[0]));
} catch (JNIException e) {
System.out.println("JNIException handled");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Usage: java JNIExceptionMain NUMBER");
}
}
}
// vim: sw=4:ts=4
#include <iostream>
#include <sstream>
#include "JNIExceptionMain.h"
using namespace std;
// This function represent all the native code functions that we are
// used to deal with. It returns 0 to success and some other value
// for error. The error code may vary.
int native_do_something(int i)
{
return i;
}
extern "C"
JNIEXPORT void JNICALL Java_JNIExceptionMain_nativeMethod
(JNIEnv* env, jclass cls, jint i)
{
// The next block will call JNIExceptionMain::javaMethod() that
// throws Exception("Exception from Java world").
cout << "Let's handle some exception first ..." << endl;
jmethodID javaMethod = env->GetStaticMethodID(cls, "javaMethod", "()V");
if (!javaMethod) {
cerr << "javaMethod not found" << endl;
return;
}
env->CallStaticVoidMethod(cls, javaMethod);
// After a call to some function that may throw some exception the
// native code should call ExceptionOcurred. It returns a jthrowable
// if some exception has been raised.
if (env->ExceptionOccurred()) {
// This will print the exception to stderr.
env->ExceptionDescribe();
// And this will clear the exception. If this one isn't called,
// the exception continues throwing.
env->ExceptionClear();
}
int status = native_do_something(i);
if (status) {
// Throwing one Exception with Exception(String s) constructor
// can be handled by ThrowNew. That function will instantiate
// and throw a exception using a string.
//
// But we're seeking for something more powerful here. To use any
// another exception the only way is to instantiate it from native
// code. This is not hard, but need some more steps.
cout << endl << "Now lets throw some exception ... " << endl;
// Need the class
jclass JNINativeException = env->FindClass("JNINativeException");
if (!JNINativeException) {
cerr << "JNINativeException not found" << endl;
return;
}
// and the constructor ...
jmethodID JNINativeException_ctr = env->GetMethodID(JNINativeException,
"<init>", "(ILjava/lang/String;)V");
if (!JNINativeException_ctr) {
cerr << "JNINativeException_ctr not found" << endl;
return;
}
// Then is just a matter of calling NewObject to instantiate
// the exception as any other object. Pay attention that I'm
// using the error code returned from native call to construct
// the JNINativeException. This ties the native function
// native_do_something with JNINativeException handling for
// this method. Other methods would have completly other meanings
// for the same code, used with this same exception. This aproach
// is flexible enough to encapsulate all error reporting in one class.
// After this, all the hard work goes to the Java layer wrappers.
ostringstream errmsg;
errmsg << "native_do_something exited with status " << status;
jobject e = env->NewObject(JNINativeException,
JNINativeException_ctr, status,
env->NewStringUTF(errmsg.str().c_str()));
if (!e) {
cerr << "Error instantiating JNINativeException" << endl;
return;
}
// With object in hands we can throw it.
env->Throw(reinterpret_cast<jthrowable>(e));
}
}
// vim: ts=4:sw=4
JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
CXXFLAGS += -Wall -std=c++11
CXXFLAGS += -I. -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
all: libexception.so
clean:
rm *.class *.so *.o *.h
libexception.o: JNIExceptionMain.h libexception.cc
libexception.so: libexception.o
JNIExceptionMain.h: JNIExceptionMain.class
JNIExceptionMain.class: JNIExceptionMain.java
%.o: %.cc
$(CXX) $(CXXFLAGS) $(LDFLAGS) -fPIC -c $<
%.so: %.o
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $<
%.class: %.java
javac $<
%.h: %.class
javah $(<:.class=)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment