Reputation:
I have some native code which returns a jbyteArray (so byte[] on the Java side) and I want to return null. However, I run into problems if I simply return 0 in place of the jbyteArray.
Some more information: The main logic is in Java, the native method is used to encode some data into a byte stream. don;t ask.. it has to be done like this. Recently, the native code had to be changed a bit and now it runs horribly horrible slow. After some experimentation, which included commenting out all code in the native method before the return, it turns out that returning 0 causes the slowdown. When returning an actual jbyteArray, everything is fine.
Method signatures for my code:
On the C++ side:
extern "C" JNIEXPORT jbyteArray JNICALL Java_com_xxx_recode (JNIEnv* env, jclass java_this, jbyteArray origBytes, jobject message)
On the Java side:
private static native byte[] recode(byte[] origBytes, Message message);
The native code looks something like this:
jbyteArray javaArray;
if (error != ERROR) {
// convert to jbyteArray
javaArray = env->NewByteArray((jsize) message.size);
env->SetByteArrayRegion(java_array, 0, message.size, reinterpret_cast<jbyte*>(message.buffer()));
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
error = ERROR;
}
}
if (error == ERROR) {
return 0; // Does NOT work - doesn't crash, just slows everything down horrible.
}
else {
return javaArray; // Works perfectly.
}
Does anyone know of any reasons that this could happen? Is it valid to return NULL from a native method in place of a jbyteArray, or is there another procedure to return null back to Java. Unfortunately, I had no luck on Google.
Thanks!
EDIT: Added additional information.
Upvotes: 17
Views: 19127
Reputation: 11027
This is an old question but I had it too a minute ago...
You say in your question:
return 0; // Does NOT work - doesn't crash, just slows everything down horrible.
I just gave a try actually, with a jintArray
as this is what my code has to allocate and return, unless an error happens (defined by some criteria not related to this topic) in which case it has to return a null result.
It happens that returning NULL
(defined as ((void*)0)
) works perfectly and is interpreted as null
when back to the Java side. I didn't notice any degradation of the performances. And unless I missed anything returning 0
with no void *
cast would not change anything to this.
So I don't think this was the cause of the slowdown you encountered. NULL
looks just fine to return null
.
EDIT:
I do confirm, the return value has nothing to do with performances. I just tested a same code returning a null value on a side, and its counterpart returning an object (a jintArray
) on the other. Performances are similar for NULL
, a jintArray
of size 0, and a random jintArray
of a few KBs allocated statically.
I also tried changing the value of a caller class's field, and returing void, with roughly the same performances. A very very little bit slower, probably due to the reflection code needed to catch that field and set it.
All these tests were made under Android, not under Java standalones - maybe this is why? (see comments):
Upvotes: 8
Reputation: 12910
Have you tried returning a NULL reference?
This is untested (don't have a JNI development environment at hand at the moment) but you should be able to create a new global reference to NULL and return it like this:
return (*env)->NewGlobalRef(env, NULL);
EDIT That being said, you check if an exception occurs, but do not clear it. That, as far as I can understand, means that it is still "thrown" in the Java layer, so you should be able to use just that as an error indicator; then it does not matter what the function returns. In fact, calling a JNI function other than ExceptionClear()/ExceptionDescribe() when an exception is thrown is not "safe" according to the documentation. That the functions is "slow" might be caused by the ExceptionDescribe() function writing debugging information.
So, if I understand this correctly, this should be a well-behaved function throwing an exception the first time an error occurs, and returning NULL on each subsequent call (until 'error' is cleared):
if (error != ERROR) {
jbyteArray javaArray = env->NewByteArray((jsize) message.size);
env->SetByteArrayRegion(javaArray, 0, message.size, reinterpret_cast<jbyte*>(message.buffer()));
if (env->ExceptionOccurred()) {
error = ERROR;
return 0;
}
return javaArray;
} else {
return env->NewGlobalRef(NULL);
}
Again, this is untested since I dont have a JNI environment available right now.
Upvotes: -2
Reputation: 41519
There's some asymmetry in your code that struck my eye: you never decide upon the type of object to return, except when returning 'nothing'. Apparently the env
object decides how to allocate a javaSrray
, so why not ask it to return some kind of empty array? It may be possible that the 0 that you return needs to be handled in a special way while marshaling between jni and java.
Upvotes: 0