Last active
October 29, 2016 05:57
-
-
Save dhilst/6c3f3d3525a803870bf2a8091c70f7ae to your computer and use it in GitHub Desktop.
JNIExceptionMain.java
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
// 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 |
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
#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 |
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 -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