Implement JNI listener - java

I have the following code in a c++ "listener class" (more or less), which calls some function of a Java object. I suspect there's a memory leak:
JNIEnv *env = NULL;
vm_->AttachCurrentThread(&env, NULL);
const jclass cls = env->FindClass(...);
const jmethodID meth = env->GetMethodID(...);
const jobject obj = env->NewObject(cls, meth, ...);
[ more code ]
env->DeleteLocalRef(obj);
My question is: should I also release the local reference of cls and meth? JNI Documentation isn't very clear about it.

No, there is no need to do so. There is no heap allocated for those two variables, they are only local to the current method and don't have to be free'd or something.
As a rule of thumb, you have to delete JNI objects that were created with a method that has New in it's name, e.g.
env->NewStringUTF(...)
env->NewObjectArray(...)
env->NewObject(...)
because those methods all translate to some kind of memory allocation on the heap (new, malloc)

Related

Convert java code that returns object with template into c++ using jni

I need to call some java code from C++ using JNI.
I can't figure out how to get a value from a returned java generic with JNI. Java code that I need to call from C++ is:
encoderCapabilities.getQualityRange().getLower()
The problem is java returns the generic type Range<Integer>:
public Range<Integer> getQualityRange ()
I tried to use following C++ code, but it crash:
GetMethodID and CallObjectMethodV with function name getQualityRange and arguments ()Landroid/util/Range;. It seems did not crash, but next call crash:
getLower, ()I
Could you please suggest what code can work?
The object Range is fine as its method toString returns valid string "[1,100]", but "getLower" failed on getting methods.
Upd: The Answer from Botje works!
After type erasure, Range#getLower will have declared type Comparable, regardless of what the type in the Java source was.
Try this instead:
jobject range = ...;
jclass cls_Range = env->GetObjectClass(range);
jmethodID mid_Range_getLower = env->GetMethodID(cls_Range, "getLower", "()Ljava/lang/Comparable;");
jobject lower = env->CallObjectMethod(range, mid_Range_getLower);
jclass cls_Integer = env->GetObjectClass(lower);
jmethodID mid_Integer_intVale = env->GetMethodID(cls_Integer, "intValue", "()I");
jint lowerInt = env->CallIntMethod(lower, mid_Integer_intValue);

Exception 0xC0000005 in jvm.dll when creating an instance via JNI NewObject

I am writing a plug-in for an existing application. Implementation language is C. However, the actual functionality is implemented in Java. For this reason, I am using Java Native Interface (JNI) to create a JVM instance from within C. I can find the appropriate Java class and create an instance. This is what the code looks like:
login(uintptr_t connection, const char* username, …) {
…
jmethodID constructor = (*env)->GetMethodID(env, ps->class, "<init>", "(JLjava/lang/String;)V");
jstring jusername = (*env)->NewStringUTF(env, username);
jobject instance = (*env)->NewObject(env, ps->class, constructor, connection, jusername);
Everything works just fine.
On Linux.
On Windows, it is a complete mess. As soon as I try to create an instance of the Java class, it throws a
EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0813751f, pid=8, tid=0x00000009
. More details are written to a log file, but the stack trace is not helpful other than pointing to somewhere in the jvm.dll. Stepping through with a debugger has not been insightful. Note this is not the same as this question.
After days, I figured it out.
The constructor I am invoking expects a parameter. The type is long (Java) aka J (JNI Type Signature) aka jlong (corresponing C type). A C uintptr_t is compatible with a jlong.
On Linux, my uintptr_t is 8 bytes long, since I am in a amd64 environment with 64 bit applications. For Windows, the application was build in 32 bit. As a result uintptr_t is only 4 bytes long, but the JVM still expect a 8 byte jlong. However, NewObject is a variadic function, automatic promotion does not happen and type safety is not guaranteed.
login(uintptr_t connection, const char* username, …) {
…
jmethodID constructor = (*env)->GetMethodID(env, ps->class, "<init>", "(JLjava/lang/String;)V");
jstring jusername = (*env)->NewStringUTF(env, username);
jlong jconnection = connection;
jobject instance = (*env)->NewObject(env, ps->class, constructor, jconnection, jusername);
A simple cast to the correct type was the solution. I expect this pitfall to exist with CallVoidMethod or any of the Call*Method mentioned in the documentation, too.

On Android, can Java call JNI and then the JNI calls back into Java?

I am just learning android and JNI. Within Java, from a button click a JNI is being called. Within the called JNI I call back into Java.
Leaving out the error processing and trying to call a static void method with no parms:
void Java_com_example_ndk2_AndroidNDK1SampleActivity_callme(JNIEnv * env, jobject this, int i)
{
jclass handlerClass = (*env)->FindClass(env, "com/example/ndk2/AndroidNDK1SampleActivity");
jmethodID mid = (*env)->GetStaticMethodID(env, handlerClass, "JavaCallback","()V");
(*env)->CallStaticVoidMethod(env, mid, NULL);
}
The FindClass and GetStaticMethodID are NOT returning errors. On the call back to Java, Logcat is indicating from dalvikvm: Invalid indirect reference 0xxxx in decodeIndirectRef. Any suggestions would be welcome.
Remove the parameter (NULL) you are passing when invoking a method that takes none ("()V").

Send C++ string to Java via JNI

I am working on the C++ side of a project that is building an Android application. There is some information (via strings and string arrays) that I need to pass to the Java application (via JNI). I have never done this before, and the people working in the reverse direction have no experience with C++ and admit that they cannot really help.
I did find the following code (from here)
#include <jni.h>
#include "ArrayHandler.h"
JNIEXPORT jobjectArray JNICALL Java_ArrayHandler_returnArray (JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
char *message[5]= {"first","second","third","fourth","fifth"};
ret= (jobjectArray)env->NewObjectArray(5,env->FindClass("java/lang/String"),env->NewStringUTF(""));
for(i=0;i<5;i++) {
env->SetObjectArrayElement(ret,i,env->NewStringUTF(message[i]));
}
return(ret);
}
But this makes no sense to me. Mostly, I am not sure how I am supposed to incorporate this into the C++ side of the program and I am failing to understand exactly how this works. Is the code sending out the message upon execution of the return(ret); line? Or during the execution of the line within for loop?
Ideally, I would like the string/string array to be sent out "live" in line and not at the end of a function so that I do not have to incorporate a new function.
Will the code I found work for what I want (with some adaptation)? Is what I am looking for even possible? If so, how can I do it?
EDIT/UPDATE:
Having spent the day looking into JNI and the terminology, I think I have failed to correctly communicate what I am looking to achieve both here and as a comment to #jogabonito's answer/reply.
That being said. The code I am working on is for an IM client that will need to push message and presence updates to the Android java application (via JNI) so that the Android application does not poll for updates. I have managed to learn how to setup the functions for the java code to call to requrest information. However, I do not have any idea how to push new message or presence information (jabber stanza strings) to the java code when it comes in. All the code that I have seen on how to do this (see below for example) seems to require getting information from the java code (env, class, methodid, etc).
It does not make sense to me how this is supposed to be possible when it is not the java code calling the function, but my c++ code. Any explanation/help would be very much appreciated.
#include <string.h>
#include <stdio.h>
#include <jni.h>
jstring Java_the_package_MainActivity_getJniString( JNIEnv* env, jobject obj){
jstring jstr = (*env)->NewStringUTF(env, "This comes from jni.");
jclass clazz = (*env)->FindClass(env, "com/inceptix/android/t3d/MainActivity");
jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(Ljava/lang/String;)Ljava/lang/String;");
jobject result = (*env)->CallObjectMethod(env, obj, messageMe, jstr);
const char* str = (*env)->GetStringUTFChars(env,(jstring) result, NULL); // should be released but what a heck, it's a tutorial :)
printf("%s\n", str);
return (*env)->NewStringUTF(env, str);
}
At the request of #Sam, here is a method that avoids using modified UTF-8 because we don't know that it is safe to do so.
NewStringUTF creates a string from its modified UTF-8 encoding.
It is not correct to use it with user data--it's unlikely to be encoded with modified UTF-8. We could just hope that the characters in the data are restricted to keep it compatible. Instead, we can convert it properly.
JNI uses modified UTF-8 strings throughout its API. We can use strings we know are compatible, particularly literals for Java identifiers (except not all currency symbols).
Below are two native method implementations. The second is better in most ways.
For this native method:
private static native String getJniString();
Here is an implementation:
JNIEXPORT jstring JNICALL
Java_the_Package_MainActivity_getJniString(JNIEnv *env, jclass)
{
std::string message = "Would you prefer €20 once "
"or ₹10 every day for a year?";
int byteCount = message.length();
jbyte* pNativeMessage = reinterpret_cast<const jbyte*>(message.c_str());
jbyteArray bytes = env->NewByteArray(byteCount);
env->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);
// find the Charset.forName method:
// javap -s java.nio.charset.Charset | egrep -A2 "forName"
jclass charsetClass = env->FindClass("java/nio/charset/Charset");
jmethodID forName = env->GetStaticMethodID(
charsetClass, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;");
jstring utf8 = env->NewStringUTF("UTF-8");
jobject charset = env->CallStaticObjectMethod(charsetClass, forName, utf8);
// find a String constructor that takes a Charset:
// javap -s java.lang.String | egrep -A2 "String\(.*charset"
jclass stringClass = env->FindClass("java/lang/String");
jmethodID ctor = env->GetMethodID(
stringClass, "<init>", "([BLjava/nio/charset/Charset;)V");
jstring jMessage = reinterpret_cast<jstring>(
env->NewObject(stringClass, ctor, bytes, charset));
return jMessage;
}
JNI is awkward. so, if we can move the knowledge that the native string is "UTF-8" to the Java side, we can do this:
private static String getJniString2()
{
return new String(getJniStringBytes(), Charset.forName("UTF-8"));
}
private static native byte[] getJniStringBytes();
And the much simpler implementation:
JNIEXPORT jbyteArray JNICALL Java_the_Package_MainActivity_getJniStringBytes(JNIEnv *env, jclass)
{
std::string message = "Would you prefer €20 once "
"or ₹10 every day for a year?";
int byteCount = message.length();
jbyte* pNativeMessage = reinterpret_cast<const jbyte*>(message.c_str());
jbyteArray bytes = env->NewByteArray(byteCount);
env->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);
return bytes;
}
In the function you shared, in your c++ code you are creating an object array with NewObjectArray. Then in your for-loop you are creating a string with NewStringUTF and storing it at an index in your array using SetObjectArrayElement. Until now, your object array is only known to your c++ code and not to your java code. Only when you return it will your java app get access to it.
I can think of a couple of ways to send the string to java from c++, though it may not be exactly what you intended.
Pass a String array to your native function. In you native code you can access each element using GetObjectArrayElement and update it using SetObjectArrayElement. This will probably be pointless since you end up having to call a function which I persume you do not want.
If you already have a string defined as a field in your java code, from your native get access to it using GetFieldID and GetObjectField, and you can update it using SetObjectField. I dont know how you will signal your java code that the field has been updated though ( if you need it)
EDIT
The updated function which you have written is meant to be called from the java layer. The clue for this is name of the function Java_the_package_MainActivity_getJniString. To call java code from a native context, you will need references to the env and obj from java. Have a look at How do I load my own Java class in C on Android? for an approach to get this. You will also probably have to look up how to use global references in JNI
You can convert a c-string into a jstring and return it. An example would look something along the lines of:
JNIEXPORT jstring JNICALL Java_Class_Method(jstring data)
{
// jstring to char *
const char *cStr = (*env)->GetStringUTFChars(env, data, NULL);
// convert char * to jstring and return it
return ((*env)->NewStringUTF(env, cStr));
}
Typically with JNI the calls go from the JVM into the C code. The normal paradigm would be:
Java programmers make a Java class with several methods declared as native (no implementation)
Java programmers compile the class with javac
Java programmers run javah against the compiled .class file, this produces a .h header file
C programmer #include the new header file and implement the interface
The only examples I have seen of doing this in the reverse direction (C code initiating contact with Java) involves having the C code actually create a JVM.
To answer your question about the code sample, the Java Strings being created are returned with the return statement at the end of code execution, logically, this is when program flow for that thread of execution is returned back to the JVM.

calling java method from c++ using JNI

i'm trying to wrap a c++ library for a specific usb device in JAVA.
the library supports Callback functions to inform the application about the attachment and detachment of usb device to PC.
the call back function must have a specific format like this:
DWORD callbackFunction(void *params);
so i implemented a function like this in JNI dll and want to call a function in Java wapper whenever this function is called.
the question is what JNIENV i should use for calling GetObjectClass, GetMethodID and CallVoidMethod from?
This is how I initialize my DLL. the "Set(AttachDetach)Callback" methods accept a callback function(first parameter) and a void* parameter(secondparameter) that will be passed to the function when module attach/detach is detected.
JNIEXPORT void JNICALL Java_MyPackage_MyClass_InitializeDLL
(JNIEnv *env, jobject obj, jobject callback)
{
// Storing callback object in global variable.
callBackObj = callback;
env->GetJavaVM(&jvm);
MyInstance = new MyClass();
MyInstance ->SetAttachCallback(AttachCallBack, &callBackObj);
MyInstance ->SetDetachCallback(DetachCallBack, &callBackObj);
// Testing!
jclass callBackCls = env->FindClass("MyPackage/MyClassCallBacks");
jmethodID mid = env->GetMethodID(callBackCls, "attach", "(B)V");
if (!mid)
return ; /* method not found */
//This call here works well
env->CallVoidMethod(callBackObj, mid, 5);
}
then i set a callback function in DLL for the USB device and it is successfully called when i attach the device.
The code i put in attach callback of the USB device is this:
DWORD CALLBACK AttachCallBack(CallbackParams* params)
{
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
jclass callBackCls = env->FindClass("MyPackage/MyClassCallBacks");
jmethodID mid = env->GetMethodID(callBackCls, "attach", "(B)V");
if (!mid)
return -1; /* method not found */
// This call fails with an access violation Exception
env->CallVoidMethod(*((jobject *)(params->param)), mid, params->moduleIndex);
// This fails the same way too
env->CallVoidMethod(callBackObj, mid, 5);
jvm->DetachCurrentThread();
return 0;
}
Before i use AttachCurrentThread i wasn't able to use JNIENV pointer at all. but now any other use of this pointer is successful instead of the call to CallVoidMethod.
Do you see what is wrong here?
Let me add that MyPackage.MyClassCallBacks Is an interface that it's method is implemented in another calss namely "callBackClass"
You need to have a reference to the current JVM:
JavaVM *jvm;
You can add an initilizing method to the C++ backend, which obtains this reference when the program starts:
JNIEXPORT void JNICALL init(JNIEnv *env, jclass){
env->GetJavaVM(&jvm);
}
And when observing for USB attachemnt / detachment, you can obtain JNIEnv from this JavaVM like this:
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
//your code here
jvm->DetachCurrentThread();
This is implemented so that every USB device change creates a new thread. If you use only one thread for the chceking, you need to attach only once (in the initilizer, perhaps?) and you will then have JNIEnv valid as long as your native thread stays attached to the JVM.
What you may have to do is create a queue in C and wait on it or poll it using a Java thread. This would always have a current JNIEnv available to it.
It seems you can't ...
.... save the JNIENV from the last JNI call which set this up and reuse it.
Your callback appears to return params which you may have passed it when you setup the call back. You can make one of them the JNIENV.
I had the same problem too. It was as though the reference to the object, created in the initialization method, was of no use in the other methods. And it is indeed like that.The solution is in the initialization of the reference to the object, which must be initialized not simply with
callBackObj = callback
but with
callbackObj = env->NewGlobalRef(callback)
Same issue here: Objective C calling Java methods using JNI
Create a JNI init(JNIEnv * env, jclass c (or jobject o) ) and
save param #1 JNIEnv
save param #2 jclass (if static)
or
save param #2 jobject (in non-static)
lookup and save the jmethodID(s) for the Java method(s) you will be invoking.
Good idea to also have a JNI shutdown(JNIEnv * env, jclass (or jobject) ) for native shutdown/cleanup

Categories