Android JNI ReleaseByteArrayElements does not work - android

i have got a problem at using Android JNI. I call a java method from native C. Everything works fine, BUT after a few seconds the APP crashes, because the maximum of 512 entrys of JNI refs is full (or the memory is full).
Here is my code:
int jniBluetoothSend( TJNIAdapter *pAdapter, byte *data, int dwLength )
{
JNIEnv *pEnv;
JavaVM *pVm = NULL;
jclass cls;
jmethodID methodId;
int nRet;
jbyteArray aData;
if ( pAdapter )
{
pVm = pAdapter->pVm;
( *pVm )->AttachCurrentThread( pVm, &pEnv, NULL );
if ( pAdapter->pClasses != NULL && pAdapter->pClasses->hgs_bluetooth != NULL )
{
// get function
methodId = ( *pEnv )->GetMethodID( pEnv, pAdapter->pClasses->hgs_bluetooth, "write", "([BI)I" );
if ( methodId )
{
aData = ( *pEnv )->NewByteArray( pEnv, dwLength);
( *pEnv )->SetByteArrayRegion( pEnv, aData, 0, dwLength, data);
// write Data to device
nRet = ( *pEnv )->CallIntMethod( pEnv, g_hgsBthObject, methodId, aData, dwLength );
// and delete Reference -> so GC can cleanup
( *pEnv )->DeleteLocalRef( pEnv, aData );
}
}
//( *pVm )->DetachCurrentThread( pVm ); -> // crashes as soon as getting called
}
return nRet;
}
Everytime "ReleaseByteArrayElements()" gets called a warning shows up in Android Log from dalvik:
JNI: unpinPrimitiveArray(0x428e84a8) failed to find entry (valid=1)
So, i think, the problem is, that the created array is not getting freed. But I don't know, how to do this in the right way.
Can anyone help me with this?
Thanks!
EDIT
I have done a several tests.
If i add the DetachCurrentThread function to the bottom of the second if(), the APP crashes, if DetachCurrentThread is getting called.
But i have added DeleteLocalRef in an other function, and the APP does not crash anymore.
So my question is: Do i have to call DetachCurrentThread in every function i call AttachCurrentThread or is it enough if i call that once to the end of the APP?
Also updated the Code

Everytime ReleaseByteArrayElements() gets called a warning shows up in Android Log from dalvik:
JNI: unpinPrimitiveArray(0x428e84a8) failed to find entry (valid=1)
ReleaseByteArrayElements is meant to be paired with a previous call to GetByteArrayElements. From Oracle's documentation:
Release<PrimitiveType>ArrayElements Routines
void Release<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, NativeType *elems, jint mode);
A family of functions that informs the VM that the native code no longer needs access to elems. The elems argument is a pointer derived from array using the corresponding Get<PrimitiveType>ArrayElements() function.
Since you didn't call GetByteArrayElements you also shouldn't call ReleaseByteArrayElements.
Note that SetByteArrayRegion() function is not one of the functions that pin an array, therefore it does not require a release.

Related

JNI how to manage the life cycle of a c++ buffer

I have an image buffer allocated on the heap in my c++ code that I would like to share with some other c++ objects as well as Java objects through JNI. On the native side im using shared_ptr and I was wondering what is the best way to do so ? my thought is to allocate the buffer on the heap once and share a reference everywhere. I'm taking advantage of smart pointers so that the buffer will be deallocated as soon as all the references go out of scope, but I'm facing an issue when sharing a reference to the java side.
how can I ensure that my java object has a valid reference to the buffer all the time ? how can c++ determine that the reference counter reaches 0 when java object is done using its reference. My concern is to avoid memory leak and also ensure that buffer doesn't get destroyed too soon before getting processed by the java class.
thanks for the help
The general answer is "make the Java object's lifetime influence the lifetime of the C++ object".
Start with the following Java class:
class Refholder implements AutoCloseable {
private long ptr; // the actual pointer
private long shared_ptr; // a pointer to a shared_ptr keeping `ptr` alive
public Refholder(long ptr, long shared_ptr) {
this.ptr = ptr;
this.shared_ptr = shared_ptr;
}
public native void close();
public void finalize() { close(); }
// Other methods to access the contents of `ptr` go here.
};
This will contain both the actual pointer and a pointer to a shared_ptr.
When you want to hand a reference to Java, use the following:
jobject passToJava(JNIEnv *env, std::shared_ptr<Foo> instance) {
jclass cls_Refholder = env->FindClass("Refholder");
jmethodID ctr_Refholder = env->GetMethodID(cls_Refholder, "<init>", "(JJ)V");
// This line increases the reference count and remembers where we put the copy
std::shared_ptr<Foo> *copy = new std::shared_ptr<Foo>(std::move(instance));
jobject ret = env->NewObject(cls_Refholder, ctr_Refholder, copy->get(), copy);
return ret;
}
Finally, the close method is responsible for extracting the shared_ptr and deallocating it:
JNIEXPORT void Java_Refholder_close(JNIEnv *env, jobject obj) {
jclass cls_Refholder = env->GetObjectClass(obj);
jfieldID fld_Refholder_ptr = env->GetFieldID(cls_Refholder, "ptr", "J");
jfieldID fld_Refholder_shared_ptr = env->GetFieldID(cls_Refholder, "shared_ptr", "J");
std::shared_ptr<Foo> *copy = (std::shared_ptr<Foo>*)env->GetLongField(obj, fld_Refholder_shared_ptr);
if (!copy)
return;
env->SetLongField(obj, fld_Refholder_ptr, 0);
env->SetLongField(obj, fld_Refholder_shared_ptr, 0);
delete copy;
}
I have decided to implement both AutoCloseable and finalize because I do not know how your Java code plans to use the reference. If you need deterministic destruction of the C++ object you need to use try-with-resources or explicit calls to the close method. If you do not, at last the finalize will close it.

.. CLOSED .. JNI error 'java_class == null' Android Studio

This is closed. New problem will be addressed in a new question.
See edit for latest problem. I am trying to pass a Vector3 value from my cpp library to my java activity. I am able to do it vice versa, but cannot seem to find a way to go cpp to java. Anyone mine helping me out with this? I am receving this error: undefined reference to 'jni_createjavavm'
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("MenuActivity");
jmethodID mid = env->GetStaticMethodID(cls, "Test", "(I)V");
env->CallStaticVoidMethod(cls, mid);
/* We are done. */
jvm->DestroyJavaVM();
Nov 11 2018 #2031 UTC+9 | EDIT: New Problem.. Crashes with java_class == NULL.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
//Some Other Code Not Regarding JVM
JNIEnv *env;
vm->AttachCurrentThread(&env, NULL);
jclass cls = env->FindClass("MenuActivity");
jmethodID mid = env->GetStaticMethodID(cls, "Test", "(I)V");
env->CallStaticVoidMethod(cls, mid);
return JNI_VERSION_1_6;
}
On Android, there is no JNI_CreateJavaVM(). The apps run in JVM which is essential to access system APIs and services.
The callbacks from native code to the Java part of the app use the JNIEnv * that must belong to the current thread.
If this runs on a Java thread, the JNIEnv is received as the first parameter by the native method. You can call back to Java from a native thread, too. But then, you must attach the thread to JVM. AttachCurrentThread() accepts JavaVM * which can be stored as a global in your native code. You can obtain it in JNI_OnLoad() or derive it from JNIEnv with GetJavaVM().
Each native thread that is attached, must be detached on termination. The best practice is to use pthread_key_create() to define a destructor function that will be called before the thread exits.
You can read more explanations in the Android JNI tips article.

What is the lifespan of a const char* in JNI?

I'm working on a Android NDK-based code and I don't get how "long" is the lifespan of my string literals.
My program is a C++ game which interacts with the Android Java-side from time to time – like uploading data on servers, connecting to Facebook, etc.
In a downcall from Java to (native) C++, I use different ways to convert jstring to const char*, and I realized that some work but another doesn't. And I don't understand why.
E.g.: in a JNI downcall, where toCall(const char*) is a regular C++ function which is defined elsewhere.
JNIEXPORT void JNICALL Java_com_alpha_beta_Gamma_onDelta(JNIEnv *env, jclass, jstring jstr) {
// #1
const char* cVar = jnu::ToString(jstr).c_str();
toCall(cVar);
// #2
std::string strVar = jnu::ToString(jstr);
toCall(strVar.c_str())
// #3
toCall(jnu::ToString(jstr).c_str());
// #4
std::string strVara;
jnu::SetString(jstr, strVara);
toCall(strVara.c_str());
}
And the jnu functions are:
std::string jnu::ToString(JNIEnv *env, jstring jstr) {
if (jstr) {
const char *cstr = env->GetStringUTFChars(jstr, NULL);
std::string str = cstr;
env->ReleaseStringUTFChars(jstr, cstr);
return str;
}
return std::string();
}
std::string& jnu::SetString(JNIEnv* env, jstring jstr, std::string& output) {
if (jstr) {
const char *cstr = env->GetStringUTFChars(jstr, NULL);
output = cstr;
env->ReleaseStringUTFChars(jstr, cstr);
}
return output;
}
Cases #2, #3 and #4 work just fine, whereas #1 just send plain garbage to the function to call.
Maybe it's C++ 101 but I don't get why my #1 case is buggy. Someone please give me any clue?
Thanks!
The char array pointed to by the pointer returned by std::string::c_str is owned by the std::string instance. That means that when the string that you called c_str on goes out of scope the data pointed to is deleted.
This is what happens in case #1. The return value of jnu::ToString (or any other temporary) goes out of scope at the end of the expression, so as soon as you initialize cVar the data it points to gets deleted and you have a dangling pointer. Any attempt to dereference a dangling pointer will result in undefined behavior.
In case #2 the string returned by jnu::ToString gets copied to strVar (or maybe moved, or maybe Return Value Optimization kicks in and no temporary ever actually gets created; it doesn't really matter). The data pointed to by the pointer returned by strVar.c_str() will continue to exist until strVar goes out of scope or needs to reallocate its storage.
In case #3 the temporary string returned by jnu::ToString will continue to exist through the full expression, so it survives through the call to toCall.
Case #4 is similar to #2 except for how jnu::SetString fills strVara.

How does JNIEnv global referencing differ from jobject in C?

I have my JNI environment and jobject objects saved locally as of now. I found that for my JNI to run of ICS and up devices, I need to fix my JNI code. This is the error I get:
02-20 10:20:59.523: E/dalvikvm(21629): JNI ERROR (app bug): attempt to use stale local reference 0x38100019
02-20 10:20:59.523: E/dalvikvm(21629): VM aborting
02-20 10:20:59.523: A/libc(21629): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 21629
I am confused about how to create/destroy these globals, and if I am even doing it right.
My application currently runs fine on all pre-ICS devices using this code:
BYTE Java_my_eti_commander_RelayAPIModel_00024NativeCalls_InitRelayJava( JNIEnv *env, jobject obj ) {
myEnv = (env);
myObject = obj;
changeID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "changeItJavaWrapper", "(S)V" );
getID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "getItJavaWrapper" , "(S)S" );
putID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "putItJavaWrapper" , "(B)V" );
flushID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "flushItJavaWrapper" , "()V" );
delayID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "delayItJavaWrapper" , "(S)V" );
RelayAPI_SetBaud= WrapSetBaud;
RelayAPI_get = WrapGetIt;
RelayAPI_put = WrapPutIt;
RelayAPI_flush = WrapFlushIt;
RelayAPI_delay = WrapDelayIt;
...
}
Under the GetStaticMethodID calls, the RelayAPI_ variables are all function pointers that lead here:
void WrapSetBaud( WORD w ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, changeID, w );
}
short WrapGetIt( WORD time ) {
return (*myEnv)->CallStaticShortMethod( myEnv, myObject, getID, time );
}
void WrapPutIt( BYTE buff ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, putID, buff );
}
void WrapFlushIt( void ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, flushID );
}
void WrapDelayIt( WORD wait ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, delayID, wait );
}
Finally, it returns to my Java code here:
public static void changeItJavaWrapper( short l ) throws IOException {
mModelService.changeitJava( l );
}
public static void flushItJavaWrapper() {
mModelService.flushitJava();
}
public static void putItJavaWrapper( byte p ) {
mModelService.putitJava( p );
}
public static void delayItJavaWrapper( short wait ) {
mModelService.delayitJava( wait );
}
public static short getItJavaWrapper( short s ) throws IOException {
return mModelService.getitJava( s );
}
I have changed my initializations to:
myEnv = (*env)->NewGlobalRef(env,obj);
myObject = (*env)->NewGlobalRef(env,obj);
But i'm extremely confused with this, as they have the same parameters, and it just doesn't make sense. I can't find documentation for this method anywhere as stupid as that sounds, this tutorial, this page, and the oracle docs don't have any information on the NewGlobalRef method itself.
EDIT
jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass bluetoothClass;
JNIEnv *myEnv;
First of all: myEnv = (*env)->NewGlobalRef(env,obj); is just wrong. You mustn't cache this value.
What you're allowed to is to cache method IDs, field IDs, class references, ... (but make sure you clean up this stuff afterwards). But caching these values requires special measures.
Why? The problem is that the JVM is allowed to load and unload classes according to the needs of the program. Therefore it could happen that a class is unloaded as soon as the last instance of the class has been destroyed by the garbage collector. As soon as this happens your cached IDs are not valid anymore. It's possible that the IDs will be the same after the JVM loads the class again but this is not guaranteed.
Solution: If you want to cache these IDs you have to tell the JVM that it's not allowed to unload a class. This is exactly what NewGlobalRef does. You just increment the reference for the reference passed to NewGlobalRef so the reference count never drops to zero and grabage collection is not allowed to clean up the referenced element.
Attention: Creating a NewGlobalRef has a serious drawback: Other than in Java you have to make sure that you call DeleteGlobalRef if you don't need this reference anymore in order to reenable the garbage collection of the reference. (As the garbage collecter is not aware of wether you still need this reference or not) Or in other words: You have to make sure that you cleanup your garbage yourself otherwise you'll leave a memory leak.
I'd also say it's not a good idea to create a global ref for an object (unless you really want to keep the object alive) as this means the object won't ever get into garbage and therefore never will be freed.
Better Variant: If you want to cache these IDs in order to speedup access to a certain object, keep a global reference for the class (using FindClass) and grab the IDs from the class object FindClass returns.
Here's a (incomplete) example of what I mean. I usually create a structure holding all the IDs I need to access a class just to keep my namespaces clean. You can imagine this as follows:
/*! \brief Holds cached field IDs for MyClass.java */
typedef struct MyClass {
int loaded; /*!< Nonzero if the information are valid */
jclass clazz; /*!< Holds a global ref for the class */
jfieldID aField; /*!< Holds the field ID of aField */
}tMyClass;
static tMyClass me = { 0 };
The easiest way is to provide a "connect" function for your object which does the initialization of the structure defined above.
/*! \brief This function fetches field IDs for a specific class in order to have
faster access elsewhere in the code
\param env a valid JNI environment
\return
- 0 if OK
- <0 if an error occured */
int MyClass_connect(JNIEnv *env)
{
jobject theClass = env->FindClass("MyClass");
if (theClass == NULL) goto no_class;
me.clazz = (jclass) env->NewGlobalRef(theClass); // make it global to avoid class unloading and therefore
// invalidating the references obtained.
if (me.clazz == NULL) goto no_memory;
me.aField = env->GetFieldID(me.clazz, "aField", "I") ;
if (me.aField == NULL) goto no_aField;
me.loaded = 1;
return 0;
no_aField:
env->DeleteGlobalRef(me.clazz);
no_memory:
no_class:
return -1;
}
After calling MyClass_connect successfully you can use me.aField to shorten the code to access a the field in your code. Of course you have to provide a disconnect function which is called when MyClass is not required anymore:
void MyClass_disconnect(JNIEnv *env)
{
if (me.loaded == 0) return;
env->DeleteGlobalRef(me.clazz);
memset(me, 0, sizeof(tMyClass));
}
Sorry for this a bit lengthy posting but I hope this helps to solve your confusion a bit and gives you a bit an insight of the inner workings of JNI together with a little receipe how to deal with this efficiently.
Edit: You can find documentation about JNI calls on oracle's website

FindClass from any thread in Android JNI

Android's JNI tips page mentions this FAQ: Why didn't FindClass find my class?
They mention multiple solutions and the last option there is this one:
Cache a reference to the ClassLoader object somewhere handy, and issue
loadClass calls directly. This requires some effort.
So, I tried to get it working and it seems that no matter what, this method simply does not work for me. Eventually, I figured how to use ClassLoader but it won't work if from a native thread I try to loadClass that hasn't been touched/loaded yet. Essentially, it's the identical to env->FindClass in behavior when called from a native thread, with the exception that it won't return 0 for classes that were already use in the app. Any idea if I didn't get it right, or it's impossible to access classes from a native thread that weren't used/loaded yet.
EDIT: I'll give more info to explain what exactly I mean. There is regular JNI env->FindClass(className), and another one that I wrote myFindClass(env, className) that uses cached ClassLoader->loadClass.
The class that I'm trying to access from native c/c++ is "com/noname/TestClient". Inside myFindClass I also use env->FindClass and log value that it returns:
jclass myFindClass(JNIEnv * env, const char* name)
{
...
jclass c0 = env->FindClass(name);
jclass c1 = (jclass)env->CallObjectMethod(ClassLoader,
MID_loadClass, envNewStringUTF(name));
dlog("myFindClass(\"%s\") => c0:%p, c1:%p, c0 and c1 are same: %d",
name, c0, c1, env->IsSameObject(c0, c1));
...
}
Then, I have these 3 combinations to explain the issue.
1)
//inside JNI_OnLoad thread
myFindClass(env, "com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0x41b64558, c1:0x41b64558,
c0 and c1 are same: 1 ...myFindClass("com/noname/TestClent") => c0:0,
c1:0x41b64558, c0 and c1 are same: 0
2)
//inside JNI_OnLoad thread
env->FindClass("com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass("com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0, c1:0x41b64558, c0 and c1 are same: 0
3)
//inside JNI_OnLoad thread
//"com/noname/TestClient" isn't touched from JNI_OnLoad.
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0, c1:0, c0 and c1 are same: 1
Basically, my issue is that ClassLoader doesn't find my class in the 3rd case. Is it a bug? What can be done to fix the problem?
EDIT2:
On top of that, it seems that ClassLoader::loadClass is plainly buggy. If I ask myFindClass("noname/TestClent") then it returns some garbage, and when I use that returned jclass in any way the app crashes.
After much trying and crashing of my app, a colleague and I managed to cache and succesfully use the class loader in another, native, thread. The code we used is shown below (C++11, but easily converted to C++2003), posted here since we couldn't find any examples of the aforementioned "Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.". Calling findClass worked perfectly when called from a thread different from the one of JNI_OnLoad. I hope this helps.
JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
gJvm = pjvm; // cache the JavaVM pointer
auto env = getEnv();
//replace with one of your classes in the line below
auto randomClass = env->FindClass("com/example/RandomClass");
jclass classClass = env->GetObjectClass(randomClass);
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
"()Ljava/lang/ClassLoader;");
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
return JNI_VERSION_1_6;
}
jclass findClass(const char* name) {
return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
}
JNIEnv* getEnv() {
JNIEnv *env;
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(status < 0) {
status = gJvm->AttachCurrentThread(&env, NULL);
if(status < 0) {
return nullptr;
}
}
return env;
}
Try attaching your native thread to the JVM first.
The pointer to jvm you can obtain first thing in JNI_OnLoad
env->GetJavaVM(&jvm);
Then from your native thread
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
Then use that env for FindClass

Categories

Resources