In a Java project that uses C code via JNI I have a piece of native C code that obtains references to an object and one of its methods, then starts a native thread, passing these references to it in a struct. When the thread tries to call the method, the code crashes with a SIGSEGV. Calling that same method from the main thread works.
Doing some research I learned that the env reference is only valid within the thread and that any other native thread must be attached first. I did this but the code still crashes on the first call to the method.
Strangely, when I call the same method from the main thread before I create the other thread (just uncomment the line in the main code), things work for some time. The output thread loops for some 10,000 times before it crashes.
The method call is to DataOutputStream.writeShort(). The thread in question is the only one writing to the DataOutputStream. However, the DataOutputStream is connected to a DataInputStream.
Simplified native code:
static void write_output(struct output_state *s) {
int i;
jint sample;
for (i = 0; i < 2 * s->result_len; i += 2) {
sample = (s->result[i] << 8) + s->result[i+1];
(*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
}
}
static void *output_thread_fn(void *arg)
{
struct output_state *s = arg;
(*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
while (!do_exit) {
// use timedwait and pad out under runs
safe_cond_wait(&s->ready, &s->ready_m);
pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
write_output(s);
pthread_rwlock_unlock(&s->rw);
}
(*(s->jvm))->DetachCurrentThread(s->jvm);
return 0;
}
JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
(JNIEnv *env, jobject self) {
jclass clsSelf = (*env)->GetObjectClass(env, self);
jfieldID fTunerOut = (*env)->GetFieldID(env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
jobject tunerOut = (*env)->GetObjectField(env, self, fTunerOut);
jclass cls = (*env)->GetObjectClass(env, tunerOut);
jmethodID writeShortID = (*env)->GetMethodID(env, cls, "writeShort", "(I)V");
if (!writeShortID || !cls)
return 0;
(*env)->GetJavaVM(env, &(output.jvm));
output.tunerOut = tunerOut;
output.writeShort = writeShortID;
// (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing
pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
usleep(100000);
pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
return 1;
}
Are JNI references such as jclass, jfieldID, jobject and jmethodID subject to the same limitations as JNIEnv, i.e. valid only within the same thread?
Suspecting this, I moved the JNI reference stuff from open() to output_thread(), right after the call to AttachCurrentThread(). However, I still need to pass a jobject reference (self) across thread borders, and the call to GetObjectClass() crashes.
What is the correct way to create a thread native code and have that thread call a particular method of a given class instance?
Turns out my suspicion was correct: jobject and jclass references are indeed local, i.e. valid only within the same thread and only until the current native method returns. See http://developer.android.com/training/articles/perf-jni.html#local_and_global_references.
My approach of moving the reference-related code to the thread function was correct, except that I need to first convert self into a global reference by calling NewGlobalRef().
Updated code:
static void write_output(struct output_state *s) {
int i;
jint sample;
for (i = 0; i < 2 * s->result_len; i += 2) {
sample = (s->result[i] << 8) + s->result[i+1];
(*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
}
}
static void *output_thread_fn(void *arg)
{
struct output_state *s = arg;
(*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
jclass clsSelf = (*(s->env))->GetObjectClass(s->env, s->self);
jfieldID fTunerOut = (*(s->env))->GetFieldID(s->env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
s->tunerOut = (*(s->env))->GetObjectField(s->env, s->self, fTunerOut);
jclass cls = (*(s->env))->GetObjectClass(s->env, s->tunerOut);
s->writeShort = (*(s->env))->GetMethodID(s->env, cls, "writeShort", "(I)V");
while (!do_exit) {
// use timedwait and pad out under runs
safe_cond_wait(&s->ready, &s->ready_m);
pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
write_output(s);
pthread_rwlock_unlock(&s->rw);
}
(*(s->jvm))->DetachCurrentThread(s->jvm);
return 0;
}
JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
(JNIEnv *env, jobject self) {
jclass clsSelf = (*env)->GetObjectClass(env, self);
if (!writeShortID || !cls)
return 0;
output.self = (*env)->NewGlobalRef(env, self);
(*env)->GetJavaVM(env, &(output.jvm));
(*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing
pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
usleep(100000);
pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
return 1;
}
One thing still missing is a call to DeleteGlobalRef() when the output thread is done. This is to make sure the global reference is released when it is no longer needed, so that the garbage collector can pick it up.
Related
ArrayList<String> myArraylist;
public ArrayList<String> getData(){
myArraylist = new ArrayList<String>();
myArraylist.add("1267982563");
myArraylist.add("2345678");
myArraylist.add("5432789");
return myArraylist;
}
How to get the each items from the above method in JNI side and Push to vector and return from the JNI to other CPP calls in the JNI layer.
Convert ArrayList to std::vector<std::string>:
jclass java_util_ArrayList;
jmethodID java_util_ArrayList_;
jmethodID java_util_ArrayList_size;
jmethodID java_util_ArrayList_get;
jmethodID java_util_ArrayList_add;
thread_local JNIEnv *env;
void init() {
java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
java_util_ArrayList_size = env->GetMethodID (java_util_ArrayList, "size", "()I");
java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
}
std::vector<std::string> java2cpp(jobject arrayList) {
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i=0; i<len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char *pchars = env->GetStringUTFChars(element, nullptr);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
}
Push ArrayList from JNI back to Java
If you don't modify this list in JNI, then the best strategy would be to simply keep a global reference to it somewhere in your native code. If you modify it a little, keep this jobject always up-to-date (you will probably need the methods java_util_ArrayList_add or java_util_ArrayList_set).
If you choose to reconstruct the list from scratch the vector, you will unwind the above method:
jobject cpp2java(std::vector<std::string> vector) {
jobject result = env->NewObject(java_util_ArrayList, java_util_ArrayList_, vector.size());
for (std::string s: vector) {
jstring element = env->NewStringUTF(s.c_str());
env->CallBooleanMethod(result, java_util_ArrayList_add, element);
env->DeleteLocalRef(element);
}
return result;
}
At any rate, be careful with threads when you work with Jni, always attach your native threads and detach before the native thread is destroyed.
I got c++ structs in header file,
struct StatusLine
{
static jclass Class; // Lorg/apache/http/StatusLine;
static jmethodID GetStatusCode; // ()I
};
struct ByteArrayOutputStream
{
static jclass Class; // Ljava/io/ByteArrayOutputStream;
static jmethodID Constructor; // ()V
static jmethodID Close; // ()V
static jmethodID ToByteArray; // ()[B
};
struct HttpEntity
{
static jclass Class; // Lorg/apache/http/HttpEntity;
static jmethodID WriteTo; // (Ljava/io/OutputStream;)V
static jmethodID GetContent; // ()Ljava/io/InputStream;
};
and cpp file is
#define JAVA_STATUS_LINE_CLASS "org/apache/http/StatusLine"
#define JAVA_HTTP_ENTITY_CLASS "org/apache/http/HttpEntity"
#define JAVA_BYTE_ARRAY_OUTPUT_STREAM_CLASS "java/io/ByteArrayOutputStream"
jclass StatusLine::Class = 0;
jmethodID StatusLine::GetStatusCode = 0;
jclass ByteArrayOutputStream::Class = 0;
jmethodID ByteArrayOutputStream::Constructor = 0;
jmethodID ByteArrayOutputStream::Close = 0;
jmethodID ByteArrayOutputStream::ToByteArray = 0;
jclass HttpEntity::Class = 0;
jmethodID HttpEntity::WriteTo = 0;
jmethodID HttpEntity::GetContent = 0;
void initializeJniPointers()
{
StatusLine::Class = GetJniEnv()->FindClass(JAVA_STATUS_LINE_CLASS);
StatusLine::GetStatusCode = GetJniEnv()->GetMethodID(StatusLine::Class, "getStatusCode", "()I");
ByteArrayOutputStream::Class = GetJniEnv()->FindClass(JAVA_BYTE_ARRAY_OUTPUT_STREAM_CLASS);
ByteArrayOutputStream::Constructor = GetJniEnv()->GetMethodID(ByteArrayOutputStream::Class, "<init>", "()V");
ByteArrayOutputStream::Close = GetJniEnv()->GetMethodID(ByteArrayOutputStream::Class, "close", "()V");
ByteArrayOutputStream::ToByteArray = GetJniEnv()->GetMethodID(ByteArrayOutputStream::Class, "toByteArray", "()[B");
HttpEntity::Class = GetJniEnv()->FindClass(JAVA_HTTP_ENTITY_CLASS);
HttpEntity::WriteTo = GetJniEnv()->GetMethodID(HttpEntity::Class, "writeTo", "(Ljava/io/OutputStream;)V");
HttpEntity::GetContent = GetJniEnv()->GetMethodID(HttpEntity::Class, "getContent", "()Ljava/io/InputStream;");
}
function initializeJniPointers() crushes on line StatusLine::GetStatusCode = GetJniEnv()->GetMethodID(); because StatusLine::Class is NULL.
But! I notice that:
If I write this in some java file of the project
StatusLine l = new StatuLine()
{
...
}
Function crushes on ByteArrayOutputStream::Constructor because ByteArrayOutputStream::Class is NULL, if I create an object of ByteArrayOutputStream in java, function will go further to the next object, etc... I notice that: If I just declare a variable of ByteArrayOutputStream, findClass will return NULL.
Could someone explain me what to do? BTW I use Android 2.3.5 device Samsung GT-S5363, I tried other vertions of android (elder) and devices and it works fine.
Basically this can occur if the thread where you ask FindClass is not the main thread and in your thread system does not build a map of java class IDs.
Check this out, probably you have to ask FindClass first in the main thread (when JNI loads or somewhere else), and then you will have ability to do that in any thread.
http://discuss.cocos2d-x.org/t/jni-findclass-cannot-find-a-class-if-its-called-by-a-new-pthread/1873/4
Also try this out, this worked for me:
https://svn.apache.org/repos/asf/mesos/branches/0.10.x/src/java/jni/convert.cpp
The solution (taken from link above) is finding a Java class loader from your app in JNI_OnLoad and ask him to find class then from any thread. Otherwise, after calling env->FindClass JNI can fall back to the system class loader which loads only system classes like String.
Another technique that has worked for me is to instantiate an object of the needed class, Java-side, just before the native method call (e.g. in AsyncTask doInBackground()).
We are implementing a one-to-one mapping of some winapi methods, for the xidobi serial port project. The mapping of the C methods to java works as expected, but for an unknown reason GetLastError() get cleared.
Here is the C-Code:
// CreateFile ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_CreateFile(JNIEnv *env, jobject this,
jstring lpFileName,
jint dwDesiredAccess,
jint dwShareMode,
jint lpSecurityAttributes,
jint dwCreationDisposition,
jint dwFlagsAndAttributes,
jint hTemplateFile) {
const char* fileName = (*env)->GetStringUTFChars(env, lpFileName, NULL);
HANDLE handle = CreateFile(fileName,
dwDesiredAccess,
dwShareMode,
(LPSECURITY_ATTRIBUTES) lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
(HANDLE) hTemplateFile);
(*env)->ReleaseStringUTFChars(env, lpFileName, fileName);
return (jint) handle;
}
// GetLastError ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_GetLastError(JNIEnv *env, jobject this) {
return (jint) GetLastError();
}
In Java we call the mapped native methods in like this:
int handle = os.CreateFile("\\\\.\\" + portName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (handle != INVALID_HANDLE_VALUE)
return handle;
int lastError= os.GetLastError(); //-> sometimes 0 (ERROR_SUCCESS)
We figured out that if we call GetLastError() in C right after CreateFile(..) the correct error code is returned. Since the one-to-one mapping is dead simple we assume that JNI or the VM calls SetLastError() itself and clears our last error.
We don't want to give up the one-to-one mapping design, so what can we do to solve this puzzle?
Here is a similar question that doesn't help in this case: CreateFile() returns INVALID_HANDLE_VALUE but GetLastError() is ERROR_SUCCESS
There is no guarantee that GetLastError() will survive whatever the JVM is doing between your native call to CreateFile and your native call to GetLastError. Therefore you should immediately call GetLastError after CreateFile and save the value in a thread local slot of your own.
Then your implementation of GetLastError will retrieve it from wherever you stored it.
You may wish to rename it LastXidobiError or some such as it will only retrieve an error which was set by a call in your library.
// Space to store last error ////////////////////////////////////////////
static DWORD dwTlsIndexLastError = 0;
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved
){
switch(fdwReason){
case DLL_PROCESS_ATTACH:
dwTlsIndexLastError = TlsAlloc();
break;
case DLL_PROCESS_DETACH:
TlsFree(dwTlsIndexLastError);
dwTlsIndexLastError = 0;
break;
}
return TRUE;
}
///// Save the last error.
///// Call this function after the "real" function whose error you want to report.
void SaveLastError()
{
TlsSetValue(dsTlsIndexLastError, (LPVOID)(DWORD_PTR)GetLastError());
}
// GetLastError ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_GetLastError(JNIEnv *env, jobject this) {
return (jint) TlsGetValue(dsTlsIndexLastError);
}
// CreateFile ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_CreateFile(JNIEnv *env, jobject this,
jstring lpFileName,
jint dwDesiredAccess,
jint dwShareMode,
jint lpSecurityAttributes,
jint dwCreationDisposition,
jint dwFlagsAndAttributes,
jint hTemplateFile) {
const char* fileName = (*env)->GetStringUTFChars(env, lpFileName, NULL);
HANDLE handle = CreateFile(fileName,
dwDesiredAccess,
dwShareMode,
(LPSECURITY_ATTRIBUTES) lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
(HANDLE) hTemplateFile);
// Save the value of GetLastError for the relevant function
SaveLastError();
(*env)->ReleaseStringUTFChars(env, lpFileName, fileName);
return (jint) handle;
}
I am having a frustrating time resolving this issue and the JNI documentation is woefully sparse. Assistance would be greatly appreciated!
Am assigned a project on an Android networking app. The native side stores information about the users you are connected to and I need to send this user list to the Java side to update UI info. Essentially I need to compose a string array and a boolean array which contain the names and flags of connected users, but later more info may be sent. My difficulty is in releasing & cleaning up the Object array containing the String information. I am not clear on how to do this. Here is what I have:
void name_list_cb(struct user_info* user_list, size_t count, void *userdata)
{
jobject callbacks = (jobject)userdata;
JNIEnv *env;
(*g_vm)->GetEnv(g_vm, (void**)&env, JNI_VERSION_1_4);
jclass cls = (*env)->GetObjectClass(env, callbacks);
jmethodID method = (*env)->GetMethodID(env, cls, "user_list", "([Ljava/lang/String;[Z)V");
int i;
jobjectArray name_list;
jbooleanArray connected_list;
name_list = (jobjectArray)(*env)->NewObjectArray(env, count, (*env)->FindClass(env, "java/lang/String"), (*env)->NewStringUTF(env, ""));
connected_list = (jbooleanArray)(*env)->NewBooleanArray(env, count);
uint8_t boolean_arr[count];
for(i = 0; i < count; i++) {
(*env)->SetObjectArrayElement(env, name_list, i, (*env)->NewStringUTF(env, user_list[i].name));
boolean_arr[i] = user_list[i].connected;
}
(*env)->SetBooleanArrayRegion(env, connected_list, 0, count, (jboolean *)boolean_arr);
(*env)->CallVoidMethod(env, callbacks, method, name_list, connected_list);
(*env)->ReleaseBooleanArrayElements(env, connected_list, (jboolean *)boolean_arr, 0);
for(i = 0; i < count; i++) {
(*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, name_list, i), user_list[i].name);
//(*env)->ReleaseObjectArrayElements(env, name_list, count, 0);
}
(*env)->DeleteLocalRef(env, boolean_arr);
(*env)->DeleteLocalRef(env, name_list);
(*env)->DeleteLocalRef(env, connected_list);
(*env)->DeleteLocalRef(env, cls);
}
I get either a "referencetable overflow", or a "signal 11 (SIGSEGV), fault addr deadbaad". The overflow/memleak is the main prob. Basically I am not releasing the UTFChars and the Object elements. Although I have seen references to it online, my JNI version does not have ReleaseObjectArrayElement[s]. I have been researching how to do this exactly but no luck so far!
I think the problem is (*env)->DeleteLocalRef(env, boolean_arr); because boolean_arr is C function stack variable.
typedef jarray jobjectArray;
void (*DeleteLocalRef)(JNIEnv*, jobject);
So I think use DeleteLocalRef could release the jobjectArray.
Or you can just new the jobjectArray just one time in an initialize method.
I'd like a consistent and simple way to throw exceptions in JNI code; something that handles chained exceptions (implicitly from the env->ExceptionOccurred method, or explicitly by parameters, either way is good) and saves me looking up constructors every time I want to do this. All of the above is preferably in C, although I could translate it from C++ at need.
Does anyone on SO have something like this that they can share?
We just code utility methods for each of the types of exceptions we want to throw. Here are some examples:
jint throwNoClassDefError( JNIEnv *env, char *message )
{
jclass exClass;
char *className = "java/lang/NoClassDefFoundError";
exClass = (*env)->FindClass( env, className);
if (exClass == NULL) {
return throwNoClassDefError( env, className );
}
return (*env)->ThrowNew( env, exClass, message );
}
jint throwNoSuchMethodError(
JNIEnv *env, char *className, char *methodName, char *signature )
{
jclass exClass;
char *exClassName = "java/lang/NoSuchMethodError" ;
LPTSTR msgBuf;
jint retCode;
size_t nMallocSize;
exClass = (*env)->FindClass( env, exClassName );
if ( exClass == NULL ) {
return throwNoClassDefError( env, exClassName );
}
nMallocSize = strlen(className)
+ strlen(methodName)
+ strlen(signature) + 8;
msgBuf = malloc( nMallocSize );
if ( msgBuf == NULL ) {
return throwOutOfMemoryError
( env, "throwNoSuchMethodError: allocating msgBuf" );
}
memset( msgBuf, 0, nMallocSize );
strcpy( msgBuf, className );
strcat( msgBuf, "." );
strcat( msgBuf, methodName );
strcat( msgBuf, "." );
strcat( msgBuf, signature );
retCode = (*env)->ThrowNew( env, exClass, msgBuf );
free ( msgBuf );
return retCode;
}
jint throwNoSuchFieldError( JNIEnv *env, char *message )
{
jclass exClass;
char *className = "java/lang/NoSuchFieldError" ;
exClass = (*env)->FindClass( env, className );
if ( exClass == NULL ) {
return throwNoClassDefError( env, className );
}
return (*env)->ThrowNew( env, exClass, message );
}
jint throwOutOfMemoryError( JNIEnv *env, char *message )
{
jclass exClass;
char *className = "java/lang/OutOfMemoryError" ;
exClass = (*env)->FindClass( env, className );
if ( exClass == NULL ) {
return throwNoClassDefError( env, className );
}
return (*env)->ThrowNew( env, exClass, message );
}
That way, it's easy to find them, your code-completion editor will help you to type them in, and you can pass simple parameters.
I'm sure you could expand this to handle chained exceptions, or other more complicated approaches. This was enough to meet our needs.
I simply use 2 lines:
sprintf(exBuffer, "NE%4.4X: Caller can %s %s print", marker, "log", "or");
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/Exception"), exBuffer);
Produces:
Exception in thread "main" java.lang.Exception: NE0042: Caller can log or print.
My code starts in Java, invokes C++, which then invokes Java back again for things like finding, getting, and setting field values.
In case someone looking for a C++ approach finds this page, I'll plough on with this:
What I'm now doing is wrapping my JNI method bodies up with a C++ try/catch block,
JNIEXPORT void JNICALL Java_com_pany_jni_JNIClass_something(JNIEnv* env, jobject self)
{
try
{
... do JNI stuff
// return something; if not void.
}
catch (PendingException e) // (Should be &e perhaps?)
{
/* any necessary clean-up */
}
}
where PendingException is declared trivially:
class PendingException {};
and I'm invoking the following method after invoking any JNI from C++, so if the Java exception status indicates an error, I'll bail immediately and let the normal Java exception handling add the (Native method) line to the stack trace, while giving the C++ the opportunity to clean up while unwinding:
PendingException PENDING_JNI_EXCEPTION;
void throwIfPendingException(JNIEnv* env)
{
if (env->ExceptionCheck()) {
throw PENDING_JNI_EXCEPTION;
}
}
My Java stack trace looks like this for a failed env->GetFieldId() call:
java.lang.NoSuchFieldError: no field with name='opaque' signature='J' in class Lcom/pany/jni/JniClass;
at com.pany.jni.JniClass.construct(Native Method)
at com.pany.jni.JniClass.doThing(JniClass.java:169)
at com.pany.jni.JniClass.access$1(JniClass.java:151)
at com.pany.jni.JniClass$2.onClick(JniClass.java:129)
at android.view.View.performClick(View.java:4084)
and pretty similar if I call up to a Java method that throws:
java.lang.RuntimeException: YouSuck
at com.pany.jni.JniClass.fail(JniClass.java:35)
at com.pany.jni.JniClass.getVersion(Native Method)
at com.pany.jni.JniClass.doThing(JniClass.java:172)
I can't talk to wrapping the Java exception within another Java exception from within C++, which I think is part of your question - I've not found the need to do that - but if I did, I'd either do it with a Java-level wrapper around the native methods, or just extend my exception-throwing methods to take a jthrowable and replace the env->ThrowNew() call with something ugly: it's unfortunate Sun didn't provide a version of ThrowNew that took a jthrowable.
void impendNewJniException(JNIEnv* env, const char *classNameNotSignature, const char *message)
{
jclass jClass = env->FindClass(classNameNotSignature);
throwIfPendingException(env);
env->ThrowNew(jClass, message);
}
void throwNewJniException(JNIEnv* env, const char* classNameNotSignature, const char* message)
{
impendNewJniException(env, classNameNotSignature, message);
throwIfPendingException(env);
}
I wouldn't consider caching (exception) class constructor references because exceptions aren't supposed to be a usual control flow mechanism, so it shouldn't matter if they're slow. I imagine look-up isn't terribly slow anyway, since Java presumably does its own caching for that sort of thing.
I will put a more complete and general answer for who need a little bit more explanations like I need before.
First is nice to set your method with a Throw Exception so the IDE will ask for try/catch.
public native int func(Param1, Param2, Param3) throws IOException;
I decide for IOException over Exception because of this.
JNIEXPORT int JNICALL Java_YourClass_func
(int Param1, int Param2, int Param3) {
if (Param3 == 0) { //something wrong
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, "Can't divide by zero."); // Error Message
}
return (Param1+Param2)/Param3;
}