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()).
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.
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.
I´m trying to call java methods from simulink Matlab using JNI. I developed a small code in C++ that calls a main method which only prints helloworld on screen as a first test but at the line that it calls to find the class, matlab crashes.
The C++ code is this :
#include <stdio.h>
#include <jni.h>
#include "mex.h"
class MatlabAmbassador {
public:
MatlabAmbassador ();
//Destructor
~MatlabAmbassador ();
void run();
JNIEnv* create_vm() ;
void invoke_class(JNIEnv* env);
private:
}; // end class
MatlabAmbassador::MatlabAmbassador() {
}
MatlabAmbassador::~MatlabAmbassador() {
}
// -------------------------------------------------------------------------
JNIEnv* MatlabAmbassador::create_vm() {
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs args;
JavaVMOption options[1];
long status;
/* There is a new JNI_VERSION_1_4, but it doesn't add anything for the purposes of our example. */
args.version = JNI_VERSION_1_6;
args.nOptions = 1;
options[0].optionString = "-Djava.class.path=C:\\Apps\\Projetos em java\\Portico\\Portico_Agent\\bin";
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
status=JNI_CreateJavaVM(&jvm, (void **)&env, &args);
return env;
}
// -------------------------------------------------------------------------
void MatlabAmbassador::invoke_class(JNIEnv* env) {
jclass helloWorldClass;
jmethodID mainMethod;
jobjectArray applicationArgs;
jstring applicationArg0;
mexPrintf("First\n");
helloWorldClass = env->FindClass("Teste"); <--- MATLAB CRASHES HERE
mexPrintf("second\n");
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear() ;
}
if ( helloWorldClass == NULL ) {
mexPrintf( "%s%s\n", "Unable to obtain class reference for ",
helloWorldClass );
return;
} else {
mexPrintf( "%s%s\n", "Sucessfully created class reference for ",
helloWorldClass );
}
mainMethod = env->GetStaticMethodID( helloWorldClass, "main", "([Ljava/lang/String;)V");
applicationArgs = env->NewObjectArray(1, env->FindClass("java/lang/String"), NULL);
applicationArg0 = env->NewStringUTF( "From-C-program");
env->SetObjectArrayElement( applicationArgs, 0, applicationArg0);
env->CallStaticVoidMethod( helloWorldClass, mainMethod, applicationArgs);
}
// -------------------------------------------------------------------------
void MatlabAmbassador::run() {
char str [80];
mexPrintf(" INITIALIZING....\n" );
JNIEnv* env = create_vm();
invoke_class( env );
}
// -------------------------------------------------------------------------
Simulink Matlab has some methods to be used. One of this method is used to call the method run() described above. Here follows a piece of code:
static void mdlStart(SimStruct *S)
{
char *buf;
size_t buflen;
int status;
buflen = mxGetN((ssGetSFcnParam(S, 2)))*sizeof(mxChar)+1 ; // le o 3o parametro passado pela funcao (nome do arq)
buf = (char *)mxMalloc(buflen); // aloca memoria
status = mxGetString((ssGetSFcnParam(S, 2)), buf,(mwSize)buflen);
ssGetPWork(S)[0] = (void *) new MatlabAmbassador; // store new C++ object in the
MatlabAmbassador *c = (MatlabAmbassador *) ssGetPWork(S)[0];
c->run();
and the simple java code is:
public class Teste
{
public static void main(String[] args)
{
System.out.println(" INITALIZING...");
}
}
So, can anyone explain what am I missing or explain if there is a real problem calling JNI inside Matlab. Matlab is version 2011b and java version installed is JDK 1.0.6_45.
I´d appreciate any help as soon as possible.
Best regards
André Nudel
andre.nudel#gmail.com
Are you sure JNI_CreateJavaVM succeeds? Your code is not checking the returned status code or resulting env value (which is not getting initialized, so it may contain garbage), and the crash is apparently occurring at the first attempt to use env.
If you're running this inside Matlab, the JVM creation may be failing because there's already a JVM running inside the process as part of Matlab's normal environment. The JNI documents say that "creation of multiple VMs in a single process is not supported" (http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html#wp636). If this is what's happening, JNI_CreateJavaVM will return JNI_EEXIST (-5). Even if that's not what's going on here, it's good practice to check the status codes returned from functions that can fail. The examples in the JNI documentation omit error checking "for clarity", but you should include it in code you're actually going to run.
Check the status returned by JNI_CreateJavaVM and print it out to make sure it's succeeding. And maybe initialize env to 0 so it's clear whether you got a pointer from JNI or just random data.
I am not very familiar with Simulink, so I'm showing an example of a regular MEX-function instead.
As suggested by #AndrewJanke, I retrieve the existing JVM instance running inside the MATLAB process instead of creating a new one.
jni.cpp
#include "mex.h"
#include "jni.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
// get MATLAB Java virtual machine instance
JavaVM *vm = NULL;
int n; jint res;
res = JNI_GetCreatedJavaVMs(&vm, 1, (jsize*) &n);
if (res != JNI_OK || n != 1) {
mexErrMsgIdAndTxt("mex:jni", "Couldn't find existing Java VM");
}
// get JNI interface instance
JNIEnv *env = NULL;
res = vm->GetEnv((void**) &env, JNI_VERSION_1_6);
if (res != JNI_OK || env == NULL) {
mexErrMsgIdAndTxt("mex:jni", "Couldn't get Java JNI environment");
}
mexPrintf("using Java %d.%d\n",
env->GetVersion() >> 16, env->GetVersion() & 0xFFFF);
}
I compiled the file using:
mex -I"C:\Program Files\Java\jdk1.6.0_45\include"
-I"C:\Program Files\Java\jdk1.6.0_45\include\win32" jni.cpp
"C:\Program Files\Java\jdk1.6.0_45\lib\jvm.lib"
Run in MATLAB, I get:
>> jni
using Java 1.6
You should be able to extend the example above to call an external Java class. Note that I have not been able to pass data between MATLAB MEX-API and Java JNI API (by that I mean passing Java objects as they are to a MEX-function, or the other way around, passing some kind of jobject back directly as is to MATLAB, without conversion that is), as in:
>> x = java.lang.Double(1)
>> my_mex_jni_fcn(x)
The problem is that mxArray is an opaque type, so I do not know how to interpret the pointer returned by mxGetData
I have the following class in Java
package com.artifex.mupdf.data;
public class FzTextSpan {
FzRect bbox;
int len, cap;
FzTextChar[] mFzTextChars;
public FzTextSpan(FzRect bbox, int len, int cap, FzTextChar[] mFzTextChars) {
super();
this.bbox = bbox;
this.len = len;
this.cap = cap;
this.mFzTextChars = mFzTextChars;
}
}
I am trying to invoke the constructor from JNI using the foll code
jclass jFzSpanClass;
jmethodID jFzSpanCtor;
jFzSpanClass = (*env)->FindClass(env, "com/artifex/mupdf/data/FzTextSpan");
if (jFzSpanClass==NULL) return NULL;
jFzSpanCtor = (*env)->GetMethodID(env, jFzSpanClass, "<init>",
"(Lcom/artifex/mupdf/data/FzRect;II;[Lcom/artifex/mupdf/data/FzTextChar;)V");
I am getting
Bogus Method Descriptor: "(Lcom/artifex/mupdf/data/FzRect;II;[Lcom/artifex/mupdf/data/FzTextChar;)V");
You have the method signature string wrong. Don't try to guess these: javap -s will tell you with 100% accuracy.
You have one semicolon more in the descriptor:
"(Lcom/artifex/mupdf/data/FzRect;II;[Lcom/artifex/mupdf/data/FzTextChar;)V"
the correct string is:
"(Lcom/artifex/mupdf/data/FzRect;II[Lcom/artifex/mupdf/data/FzTextChar;)V"
I'm trying to access the message in a jthrowable while handing an exception generated when I fail to find a class. However, I am unable to access the message ID of getMessage() on the jthrowable object, and I don't know why. I've tried changing the signature of getMessage to "()Ljava/lang/String" (without the semicolon at the end, but that's necessary, right?) with no joy. I'm confused as hell about this. I even tried replacing getMessage with toString, and that didn't work. Obviously I'm doing something trivially wrong here.
Here's the code I'm using:
jthrowable java_exception;
jclass java_class;
jmethodID method;
java_exception = (*jEnv)->ExceptionOccurred(jEnv);
assert (java_exception != NULL);
java_class = (*jEnv)->GetObjectClass (jEnv, java_exception);
assert (java_class != NULL);
method = (*jEnv)->GetMethodID (jEnv, java_class, "getMessage", "()Ljava/lang/String;");
if (method == NULL) {
printf ("Seriously, how do I get here?!\n");
(*jEnv)->ExceptionDescribe (jEnv);
return;
}
The output of this code (amongst other things) looks like this:
Seriously, how do I get here?!
Exception in thread "main" java.lang.NoClassDefFoundError: com/planet/core360/docgen/Processor
javap -p -s java.lang.Throwable gives me this:
Compiled from "Throwable.java"
public class java.lang.Throwable extends java.lang.Object implements java.io.Serializable{
...
public java.lang.String getMessage();
Signature: ()Ljava/lang/String;
...
Okay, so it looks like my problem was that GetObjectClass doesn't act the way you'd expect it to on a jthrowable, or at least the results of it are not useful for the purposes of getting methods. Replacing that portion of the code with this works:
java_class = (*jEnv)->FindClass (jEnv, "java/lang/Throwable");
method = (*jEnv)->GetMethodID (jEnv, java_class, "getMessage", "()Ljava/lang/String;");
Damnably odd, that. I hope this helps someone else in the future, though.
I tried your approach, and it worked for me. A few things though: I'm using the C++ interface (though that shouldn't make a difference), and I'm using Java 6 update 10, x64 edition, on Ubuntu 8.04. Perhaps the Java version and/or platform used will make a difference.
#include <cstdio>
#include <jni.h>
int
main(int argc, char** argv)
{
if (argc != 3) {
std::fprintf(stderr, "usage: %s class message\n", argv[0]);
return 1;
}
JavaVM* jvm;
void* penv;
JavaVMInitArgs args = {JNI_VERSION_1_6};
if (jint res = JNI_CreateJavaVM(&jvm, &penv, &args)) {
std::fprintf(stderr, "Can's create JVM: %d\n", res);
return -res;
}
JNIEnv* env(static_cast<JNIEnv*>(penv));
jint vers(env->GetVersion());
std::printf("JNI version %d.%d\n", vers >> 16, vers & 0xffff);
env->ThrowNew(env->FindClass(argv[1]), argv[2]);
jthrowable exc(env->ExceptionOccurred());
std::printf("Exception: %p\n", exc);
if (exc) {
jclass exccls(env->GetObjectClass(exc));
jclass clscls(env->FindClass("java/lang/Class"));
jmethodID getName(env->GetMethodID(clscls, "getName", "()Ljava/lang/String;"));
jstring name(static_cast<jstring>(env->CallObjectMethod(exccls, getName)));
char const* utfName(env->GetStringUTFChars(name, 0));
jmethodID getMessage(env->GetMethodID(exccls, "getMessage", "()Ljava/lang/String;"));
jstring message(static_cast<jstring>(env->CallObjectMethod(exc, getMessage)));
char const* utfMessage(env->GetStringUTFChars(message, 0));
std::printf("Exception: %s: %s\n", utfName, utfMessage);
env->ReleaseStringUTFChars(message, utfMessage);
env->ReleaseStringUTFChars(name, utfName);
}
return -jvm->DestroyJavaVM();
}
I've used jnitest java/lang/InternalError 'Hello, world!' for my testing; feel free to try with different exception types!