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.
Related
I'm writing a native Java agent using JVMTI that goes over all the methods of all the loaded classes. Unfortunately many classes seem not yet prepared and therefore GetClassMethods returns JVMTI_ERROR_CLASS_NOT_PREPARED. I am registering a ClassPrepare event callback but that seem to be called only for very few classes. Simplified (minus all the error handling and deallocation) my code looks like this
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* jvm, char *options, void *reserved) {
jvmtiEnv *jvmti;
jint class_count;
jclass* classes;
jint method_count;
jmethodID* methods;
(*jvm)->GetEnv(jvm, (void**) &jvmti, JVMTI_VERSION_11);
(*jvmti)->GetLoadedClasses(jvmti, &class_count, &classes);
for (int i = 0; i < class_count; i++) {
jclass klass = classes[i];
// here a lot of time JVMTI_ERROR_CLASS_NOT_PREPARED is returned
jvmtiError err = (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods);
}
The agent is attached dynamically to a running JVM using JCMD and the JVMTI.agent_load command. I did try to register a class prepare callback using:
jvmtiEventCallbacks callbacks;
(void)memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassPrepare = &callbackClassPrepare;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint) sizeof(callbacks));
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, (jthread) NULL);
But this ended only being called for very few classes.
How can I get the JVM to prepare the loaded classes so that GetClassMethods returns JVMTI_ERROR_NONE?
So far I have only tested with JDK 17.0.1 with Shenandoah GC.
This is a normal situation when some classes are loaded but not linked. You don't need to do anything to prepare classes manually - JVM does this automatically when needed. JVM Specification guarantees the classes is completely prepared before it is initialized. As soon as it happens, JVM TI ClassPrepare event is fired.
So in order to get all available jmethodIDs you'll need:
Iterate over all loaded classes, ignoring possible JVMTI_ERROR_CLASS_NOT_PREPARED.
Set ClassPrepare event callback and call GetClassMethods in it.
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);
I have a C routine that is calling a Java module through the JNI invocation interface. I've been having an issue where the call to the Java method has been returning a NULL string when using the C module and the JNI, but when I use Java at the command line, the Java module returns an error and a default value.
Here is the data returned when I bypass the C code and call the Method through Java at the command line
$ java ClassName "This" "is" "my" "test" "string"
Exception on SomethingElse.Method: [Function: DifferentMethod]ExceptionClassException: ExceptionClassExceptionException: [Function: CFBDynamicMessage::getCodePage]codePageBytes is NULL, for UTF-8
Returned String -- data I'm trying to get
0.0| |0.0| |0.0|| |0.0|0| |0.0| | ||0.0|0|0|0|0|0|0|0|0|0|0||
I need to get the returned string, even in the event of an error on the java side. To try to see what is going on in the C and JNI, I turned up the debugging level for the JNI:
options[1].optionString = "-Xdebug"; /* print JNI-related messages */
options[2].optionString = "-Xlog:all";
// this next line allows us to continue processing after the Java
// error. Unfortunately the code then SegFaults further on.
options[3].optionString = "-Xcheck:jni:nonfatal";
options[4].optionString = "-verbose:jni";
The JNI starts the JVM, finds the class and the method and builds the Java string I need to pass to the method, but when I try to execute the method, I get the following errors:
JVMJNCK048E JNI error in CallObjectMethod/CallObjectMethodV: Ineligible receiver
JVMJNCK080E Error detected in the outermost frame of an attached thread
JVMJNCK023E JNI error detected. Continuing...
Segmentation fault (core dumped)
Here is the C Code (I am cutting out some error checking and whitespace for brevity):
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
cls = (*env)->FindClass(env, "GenericClassName");
mid = (*env)->GetMethodID(env, cls, "execute", "(Ljava/lang/Object;)Ljava/lang/Object;");
jstr = (*env)->NewStringUTF( env, "This|is|my|test|string|");
// This next line is the one that throws the ineligible receiver error
jstring resultStr = (*env)->CallObjectMethod(env, cls, mid, jstr);
// ExceptionOccurred(env) is false; no errors are displayed
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
const char *nativeString = (*env)->GetStringUTFChars(env, resultStr, JNI_FALSE);
// This next line calls the SegFault
fprintf(stdout, "%s\n", *nativeString);
I've used gdb to look at the core dump created by the SegFault and here are the what I think are the pertinent results:
print nativeString
$1 = 0x8dfaa80 "[Ljava.lang.Class;#34ca34ca"
print *nativeString
$2 = 91 '['
here is the definition of the java method I'm calling:
public Object execute (Object args) throws Exception { ... }
Any help or insight you can provide with this issue will be greatly appreciated. When I try to Google for the ineligible receiver error, I get a bunch of links about football, but nothing in the JNI or Java in general. I've spent the last few days scouring the Stack Overflow website, and although there were some promising questions asked, they were all related to Android and didn't provide any additional assistance in resolving my issue.
As two final notes, even if you can't help, thanks for reading this far and my environment is a 64bit RHEL server, but the C code and the JVM are both running as 32bit applications.
TL;DR - C is calling a Java Method through the JNI and I am getting an error code (JNI error in CallObjectMethod/CallObjectMethodV: Ineligible receiver) that results in Google telling me all about all the sports I can't watch because I'm working on this issue.
You're supplying a class instead of an object. So you're trying to call Class<GenericClassName>.execute(Object arg). You need an instance of the class.
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.
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)