calling java method based on jobject fails (invalid reference?) - java

I have the following problem:
in my java program I'm calling a native function that creates an object of the "MyEventReceiver" class in C++, later in that java program I'm calling an native function that calls the "test" method of that object. In this "test"-method I want to call a method of the java object that I used to call the second native function, but as soon as make this call, the jvm crashes.
This is the native code that creates the MyEventReceiver object:
JNIEXPORT jlong JNICALL Java_irr4jEventReceiver_native_1createEventReceiver
(JNIEnv *env, jobject obj){
MyEventReceiver *rec = new MyEventReceiver(env, obj);
return (long)rec;
}
this is the native code that i use later in the program to call the "test" method in that object:
JNIEXPORT void JNICALL Java_irr4jEventReceiver_native_1testmethode
(JNIEnv *env, jobject obj, jlong ptrrec){
MyEventReceiver *rec = (MyEventReceiver*)ptrrec;
rec->test();
}
and this is the MyEventReceiver class:
class MyEventReceiver : public IEventReceiver
{
public:
JNIEnv *myenv;
jobject receiverobj;
jclass SEventclass;
jobject eventobj;
jmethodID cid;
jclass cevrec;
jmethodID meth2;
public:
void test(){
eventobj = myenv->AllocObject(SEventclass);
eventobj = myenv->NewObject(SEventclass, cid);
myenv->CallVoidMethod(receiverobj,meth2,eventobj); //this is the line that causes the crash
}
MyEventReceiver(JNIEnv *env, jobject obj)
{
this->myenv=env;
receiverobj = env->NewGlobalRef(obj);
SEventclass = myenv->FindClass("SEvent");
cid = myenv->GetMethodID(SEventclass,"<init>", "()V");
cevrec = myenv->FindClass("MyEventReceiver");
meth2 =myenv->GetMethodID(cevrec, "OnEvent", "(LSEvent;)V");
//test();
}
};
if I call the method "test" at the end of the constructor, it works... only if I make the call later out of the java program... I think it has something to do with the jobject "receiverobj"... seems to get invalid after some time but I don't know why
...I stripped the code a bit...deleted some debug code. The "eventobj" I'm creating is fine... I can call other methods of that object, the methodIDs are also fine, just the line:
myenv->CallVoidMethod(receiverobj,meth2,eventobj);
gives me problems and i dont know why :)
crash message is:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x67d594f4, pid=5292, tid=2400
#
# JRE version: 7.0_21-b11
# Java VM: Java HotSpot(TM) Client VM (23.21-b01 mixed mode, sharing windows-x86 )
# Problematic frame:
# V [jvm.dll+0xa94f4]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# G:\irr4jjava\irr4j\hs_err_pid5292.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.sun.com/bugreport/crash.jsp
#
EDIT:
Thanks for the help so far. I tried to use global refs and attached thread but I can't get it to work. I'm not sure if I understood the thing with the attached thread...
I modified the first native method to create a global reference to the jobject and pass it to the constructor:
JavaVM *jvm;
JNIEXPORT jlong JNICALL Java_irr4jEventReceiver_native_1createEventReceiver
(JNIEnv *env, jobject obj){
jobject recobj = env->NewGlobalRef(obj);
env->GetJavaVM(&jvm);
MyEventReceiver *rec = new MyEventReceiver(recobj);
return (long)rec;
}
in the class "MyReceiverObject" I try attach the thread but it still crashes:
class MyEventReceiver : public IEventReceiver
{
public:
JNIEnv *myenv;
jobject receiverobj;
jclass SEventclass;
jobject eventobj;
jmethodID cid;
jclass cevrec;
jmethodID meth2;
public:
void test(){
jvm->AttachCurrentThread((void**)&myenv,NULL);
eventobj = myenv->AllocObject(SEventclass); //crash
eventobj = myenv->NewObject(SEventclass, cid);
myenv->CallVoidMethod(receiverobj,meth2,eventobj);
}
MyEventReceiver(jobject obj)
{
jvm->AttachCurrentThread((void**)&myenv,NULL);
receiverobj=obj;
SEventclass = myenv->FindClass("SEvent");
cid = myenv->GetMethodID(SEventclass,"<init>", "()V");
cevrec = myenv->FindClass("MyEventReceiver");
meth2 =myenv->GetMethodID(cevrec, "OnEvent", "(LSEvent;)V");
//test();
}
};

You need to read the chapter in the JNI Specification about global references. The jobject you were passed in your JNI method is only valid for the life of that method. You need to convert it to a GlobalRef, maybe a WeakGlobalRef, before you store it anywhere that is going to outlive the current invocation.
A similar thing applies to the JNIEnv pointer. It's only valid for the duration of the method it is passed to. If you want to use one in a different thread or at a later time you must get a new one via AttachThread().

Related

Full Native Implementation

I am designing an application through JNI in order to prevent third party edits. I have moved forth towards registering native methods in order to remove jni bridge linkages, but as you can see, methods with override attribute still need to exist within the java code as native linked. Is there a way to fully port the remaining java code for this specific file?
Java:
#Override
protected native void onCreate(Bundle savedInstanceState);
CPP:
void onCreate(JNIEnv *env, jobject classObject, jobject bundle) {
/**super.onCreate**/
gObjects[0] = env->NewGlobalRef(classObject);
gClasses[0] = env->GetObjectClass(gObjects[0]);
jclass s_Class = env->GetSuperclass(gClasses[0]);
jmethodID oc_ID = env->GetMethodID(s_Class, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(gObjects[0], gClasses[0], oc_ID, bundle);
}
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *curVM_, void *reserved) {
curVM = curVM_;
curVM->GetEnv((void **) &environments[0], JNI_VERSION_1_6);
/**Start of Native Method Registers**/
JNINativeMethod natives[1];
uint64_t pCount = sizeof(natives) / sizeof(natives[0]);
jclass klass = environments[0]->FindClass("com/company/app/Activity");
natives[0] = {"onCreate", "(Landroid/os/Bundle;)V", (void *)onCreate};
environments[0]->RegisterNatives(klass , natives, pCount);\
for (uint64_t i = 0; i < pCount; i++) natives[i] = {"", ""};\
return JNI_VERSION_1_6;
}
It does seem quite silly to think I can simply remove the native linkage between the activity and lib. Instead, I will be utilizing BaseDexClassLoader to sideload my dex file during runtime. I will not be giving up my code, but further information about this can be founded at: https://developer.android.com/reference/dalvik/system/BaseDexClassLoader

How to call getStackTrace method from java native interface (jni)

I am trying to understand the jni with some examples. I am trying to get the java stack trace using jni and so this is what I was doing
HelloJNI.java
package test.com.jni;
public class HelloJNI {
static {
System.loadLibrary("hello"); // Load native library at runtime
}
private native StackTraceElement[] getStackTraceNative(Throwable throwable);
private native void printStackTraceNative(Throwable throwable);
public static void main(String[] args) {
test();
}
public static void test() {
new HelloJNI().printStackTraceNative(new Throwable()); //Invoke native method
new HelloJNI().getStackTraceNative(new Throwable());
}
}
Native code (keeping the error handling out for simplicity)
test_com_jni_HelloJNI.c
JNIEXPORT jobjectArray JNICALL Java_test_com_jni_HelloJNI_getStackTraceNative (JNIEnv * env, jobject object, jthrowable exception) {
jclass exceptionClazz = (*env)->GetObjectClass(env, exception);
jmethodID getStackTraceMethod = (*env)->GetMethodID(env, exceptionClazz, "getStackTrace", "()[Ljava.lang.StackTraceElement");
jobjectArray stacktraces = (*env)->CallObjectMethod(env, exception, getStackTraceMethod);
return stacktraces;
}
JNIEXPORT void JNICALL Java_test_com_jni_HelloJNI_printStackTraceNative (JNIEnv * env, jobject object, jthrowable exception) {
jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
jmethodID printStackTraceMethod = (*env)->GetMethodID(env, exceptionClazz, "printStackTrace", "()V");
(*env)->CallVoidMethod(env, exception, printStackTraceMethod);
}
Now in this code native printStackTraceNative method works and it prints the stack trace however, getStackTraceNative doesn't. When I check the core dump file it says that java has thrown an exception java/lang/NoSuchMethodError for getStackTrace. I am confused because the parameter I am passing to getStackTraceNative method is of the type throwable and throwable should have the method getStackTrace.
What concept I possibly be missing here, any help on this is appreciated. Thanks
You got the method signature wrong. In method signatures the periods in the qualified class name are replaced by forward slashes. The signature for getStackTrace is therefore ()[Ljava/lang/StackTraceElement; (also note the semicolon)
You can get the method signatures from class files with javap with -s option:
javap -classpath '/path/to/jre/lib/rt.jar' -s java.lang.Throwable

Java JNI UnsatisfiedLinkError

I'm trying to expose C++ functionality to Java using JNI. I'm trying to get a simple message box to show up first, just to make sure everything works. However, I'm getting an UnsatisfiedLinkError exception being thrown (the exception is being thrown when I call the function, not)
Java class (project cpplib):
package src;
public class MessageBox {
static {
System.loadLibrary("cpplib");
}
private static native void libf_show(String message, String caption);
public static void show(String message, String caption) {
libf_show(message, caption);
}
}
Note: the folder of cpplib, the DLL, has been added into the native libraries path
C++ header messagebox.hpp:
#pragma once
#include "jni.h"
extern "C"
{
JNIEXPORT void JNICALL Java_cpplib_src_MessageBox_show(JNIEnv *env, jstring jstr_message, jstring jstr_caption);
}
C++ source messagebox.cpp:
#include "messagebox.hpp"
#include <windows.h>
JNIEXPORT void JNICALL Java_cpplib_src_MessageBox_show(JNIEnv *env, jstring jstr_message, jstring jstr_caption)
{
const char *message = env->GetStringUTFChars(jstr_message, 0);
const char *caption = env->GetStringUTFChars(jstr_caption, 0);
MessageBox(NULL, message, caption, MB_OK);
env->ReleaseStringUTFChars(jstr_message, message);
env->ReleaseStringUTFChars(jstr_caption, caption);
}
Full error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: src.MessageBox.libf_show(Ljava/lang/String;Ljava/lang/String;)V
at src.MessageBox.libf_show(Native Method)
at src.MessageBox.show(MessageBox.java:11)
at src.CPPLIB_Test.main(CPPLIB_Test.java:6)
I believe I'm using the 64-bit version of the JDK (as a 32-bit DLL didn't work), so I'm using the appropriate 64-bit JDK headers (if they are different at all).
Why doesn't Java like my DLL?
I tried to reproduce your problem but I get different header. If a recreate your MessageBox.java inside a src directory and compile with javac -d build src/MessageBox.java and finally obtain C/C++ headers with javah -d include -classpath build src.MessageBox
Then, I got this method signature
JNIEXPORT void JNICALL Java_src_MessageBox_libf_1show (JNIEnv *, jclass, jstring, jstring);
instead
JNIEXPORT void JNICALL Java_cpplib_src_MessageBox_show(JNIEnv *env, jstring jstr_message, jstring jstr_caption);
How are you creating your C++ header? maybe here it is the problem.

How to use .so library in android

I'm trying to use a python interpreter in my Android app to run SymPy. I already compiled Python for arm using this guide. http://mdqinc.com/blog/2011/09/cross-compiling-python-for-android/
This got me a libpython2.7.so file which I put into jniLibs/armeabi/.
In my app I load it as follows:
public class PythonTest {
static {
System.loadLibrary("python2.7");
}
public static native void Py_Initialize();
public static native void Py_Finalize();
public static native int PyRun_SimpleString(String s);
}
I am trying to use the methods from the headers located in the include directory which can also be found here: https://docs.python.org/2/c-api/
When I run the app on my device I get the following error:
No implementation found for void com.example.dorian.testapplication.PythonTest.Py_Initialize() (tried Java_com_example_dorian_testapplication_PythonTest_Py_1Initialize and Java_com_example_dorian_testapplication_PythonTest_Py_1Initialize__)
So to me this seems like the library loaded but it seems to look for the JNIEXPORT functions. But shouldn't I be able to use this library without writing specific C++ files? And if not, how would I accomplish this. Might there be a tool to generate wrapper files or something similar?
You need a JNI wrapper lib that will serve as a bridge between your Java code and libpython2.7.so. It may be enough for straters to have the three functions wrapped according to the JNI convention, e.g.
JNIEXPORT jint JNICALL com_example_dorian_testapplication_PythonTest_PyRun_1SimpleString
(JNIEnv *env, jclass jc, jstring js)
{
char* cs = env->GetStringUTFChars(js, 0);
std::string s = new std::string(cs);
env->ReleaseStringUTFChars(env, js, cs);
return PyRun_SimpleString(s.c_str());
}
If something is not clear, please read the tutorial at http://joaoventura.net/blog/2014/python-android-2.
Note that you can use any package name for the PythonTest class, not necessarily related to your Android app package name, e.g.
package com.python27;
class Python {
static {
System.loadLibrary("python2.7");
}
public static native void Initialize();
public static native void Finalize();
public static native int Run(String s);
}
will expect the JNI wrappers
JNIEXPORT void JNICALL com_python27_Python_Initialize(JNIEnv *env, jclass jc);
JNIEXPORT void JNICALL com_python27_Python_Finalize(JNIEnv *env, jclass jc);
JNIEXPORT jint JNICALL com_python27_Python_Run(JNIEnv *env, jclass jc, jstring js);

JNI: Callback from JVM to C++ fails to run

I start JVM from C++ program.
C++ code:
JNIEXPORT jobject JNICALL com_javelin_JavelinMarketData_callBackIntoNative(JNIEnv* env, jobject obj, jlong ptr)
{
std::cout << "com_javelin_JavelinMarketData_callBackIntoNative called" << std::endl;
}
int main()
{
JavaVM* jvm;
JNIEnv* env;
...
JNI_CreateJavaVM(&jvm, (void **)&env, &args);
jmethodID mainMethod = env->GetStaticMethodID(helloWorldClass, "main", "([Ljava/lang/String;)V");
env->CallStaticVoidMethod(helloWorldClass, mainMethod, ...);
}
then I need my C++ function called back from java:
Java code:
native void callBackIntoNative(long ptr);
public void onResponse(long param)
{
System.out.println("Calling callBackIntoNative()");
callBackIntoNative(param);
}
When I run my C++ program, JVM starts correctly, but after it prints "Calling callBackIntoNative()" the following acception appears:
Exception during callBackIntoNative(): 'com.javelin.JavelinMarketData.callBackIntoNative(J)V'
java.lang.UnsatisfiedLinkError: com.javelin.JavelinMarketData.callBackIntoNative(J)V
Can you please help?
Thanks, but I found a solution: I should have registered my C++ function as a native method using the code:
const JNINativeMethod methods[] = { { "callBackIntoNative", "(J)V", (void*)&com_javelin_JavelinMarketData_callBackIntoNative } };
const int methods_size = sizeof(methods) / sizeof(methods[0]);
jclass jClass = env->FindClass("com/javelin/JavelinMarketData");
env->RegisterNatives(jClass, methods, methods_size);
Now it works fine.
I would compile your callback apart into a dynamic library (.dll, .so, whatever your OS) and put it accesible to your java program. Then you just load your library by using JNI and call from your java classes whatever functionality you have in the library.

Categories