JNI / Android NDK - Maintaining a global object reference - c++

I know I can't keep a reference to the internals of an array so I was wondering if it is OK to keep a global pointer to a java array object or indeed any java object. And whether it makes any difference that I create it from the C++.
It works, but I was worried the garbage collector could potentially relocate the memory (which I understand it the reason for Get... and Release... methods on JNIEnv).
//global jfloatArray
jfloatArray jarray;
//called once
JNIEXPORT void Java_com_example_test1_Main_Init
(JNIEnv *env, jclass thiz){
//create once
jarray = env->NewFloatArray(10); //if valid, would it be as valid to pass it in?
}
//called repeatedly
JNIEXPORT void JNICALL
Java_com_example_test1_Main_loop(JNIEnv* env, jobject thiz) {
//use jarray in here
}
Edit:
Here is the correct code.
//global jfloatArray
jfloatArray jarray;
//called once
JNIEXPORT void Java_com_example_test1_Main_Init
(JNIEnv *env, jclass thiz){
//create once
//create it - this gives a local reference
jfloatArray local_jarray = env->NewFloatArray(10);
//get a global reference, cast it and set to the global "jarray"
jarray = (jfloatArray) env->NewGlobalRef(local_jarray);
//delete the local reference
env->DeleteLocalRef(local_jarray);
}
//called repeatedly
JNIEXPORT void JNICALL
Java_com_example_test1_Main_loop(JNIEnv* env, jobject thiz) {
//use jarray in here
}

Your reference is merely that -- a reference. It will not prevent the object it refers to from being relocated. It will prevent the object from being recollected; local references are automatically destroyed after returning, but since you're using a global variable, you should use a global reference, which necessitates manual management. See NewGlobalRef and DeleteGlobalRef.

Related

ReleaseStringUTFChars with non-original jstring argument?

In order to minimize JNI marshalling, I want to store some strings on the C++ side as static variables via a setup method, use them in a different JNI method call rather than passing them each time, and then release the strings later with yet another JNI method call. For example,
C++ code:
static const char *stringValue1;
extern "C" JNIEXPORT void JNICALL
Java_mypackage_myclass_setValue1(JNIEnv* env, jclass jobj, jstring javaString) {
jboolean isCopy;
if(!env->IsSameObject(javaString, NULL)) {
const char *stringValue1= env->GetStringUTFChars(javaString, &isCopy);
}
}
extern "C" JNIEXPORT void JNICALL
Java_mypackage_myclass_execute(JNIEnv* env, jclass jobj, jint javaInt) {
// ... use the stringValue1 static variable - otherwise, this method would need another jstring argument.
}
extern "C" JNIEXPORT void JNICALL
Java_mypackage_myclass_releaseValue1(JNIEnv* env, jclass jobj, jstring javaString) {
if(stringValue1 != 0) {
env->ReleaseStringUTFChars(javaString, stringValue1);
}
}
Java code:
myclass.setValue1("hello")
for(int i = 0; i < 10; i++) {
myclass.execute(i); // execute needs the "hello" string, but we try to store it in the C++ side rather than pass it in each loop iteration.
}
myclass.releaseValue1("hello");
Is this a valid JNI pattern? In all of the JNI examples I have seen, both calls to GetStringUTFChars and ReleaseStringUTFChars occur in the same method/scope. In this example, they do not occur in the same scope, and the jstring argument could possibly not be the same Java String either.
Yes, the documentation states
This array is valid until it is released by ReleaseStringUTFChars().
This is corroborated by the implementation in Hotspot, which just allocates off-heap memory for a copy.

Can Java layer object be released by JNI?

I create a Java object by calling native code which return a jobject value.
Java code:
Object myObj = nativeCreateObject();
Native code:
jobject* hold_ref;
JNIEXPORT jobject JNICALL
nativeCreateObject(JNIEnv *env ...) {
.....
result = env->NewGlobalRef(jobj);
hold_ref = &result;
return result;
}
my question is: whether I can use hold_ref later to release the myObj by reference in native layer?
e.g.
native code:
*hold_ref = NULL;
Then the myObj in Java layer is null?
if not, how can I release this object by native code?
I am not sure, what you want to achieve, but here is how it basicly works:
whether I can use hold_ref later to release the myObj by reference in native layer?
Yes, you can and should use env->DeleteGlobalRef(myObj) to free the global reference you have created so the garbage collector can clean up and finaly destroy the object.
Then the myObj in Java layer is null? if not, how can I release this object by native code?
There is no way your Java-variable will turn null magicaly when you delete the reference from jni native code. Java itself holds a reference to prevent the object is removed by the garbage collection.
You may want to use it this way:
C++
jobject* hold_ref;
JNIEXPORT jobject JNICALL nativeCreateObject(JNIEnv *env ...) {
.....
result = env->NewGlobalRef(jobj);
hold_ref = &result;
return result;
}
JNIEXPORT void JNICALL nativeDestroyObject(JNIEnv *env ...) {
.....
env->DeleteGlobalRef(jobj);
hold_ref = nullptr;
}
JAVA
// Creates two references (native and local variable)
Object myObj = nativeCreateObject();
// Deletes the native reference, but not the local one
nativeDeleteObject(myObj);
// myObj != null
myObj = null;
// now there is no reference to your created object
// the garbage collector may destroy it any time
If you want to invalidate your object in someway I suggest to manage the state and throw an exception, if the object was invalidated like this:
class MyInvalidateableObject {
private boolean invalidated = false;
public void invalidate() {
this.invalidated = true;
}
public void foo() {
if (invalidated)
throw new IllegalStateException("Object has been invalidated");
... // do the normal stuff
}
}
Simply call invalidate() on your object from native code to prevent it from being used anymore.

Setting a temporary environment variable for Batch from C++

I am implementing a small pack of programs for batch users to use.
Almost all things in this pack is made in C++ and is called from Java.
How would I set a environment variable for the Batch file to use?
I have tried using this:
JNIEXPORT void JNICALL METHOD_NAME(JNIEnv *env, jclass theclass, jstring key, jstring value) {
const char* thekey = env->GetStringUTFChars(key, false);
const char* thevalue = env->GetStringUTFChars(value, false);
std::string envvar;
envvar.append(thekey);
envvar.append("=");
envvar.append(thevalue);
_putenv(envvar.c_str());
env->ReleaseStringUTFChars(key, thekey);
env->ReleaseStringUTFChars(value, thevalue);
}
However the Batch file did not see any new variable.
Should I use system("set thing=value");?
After some research I came to the conclusion that a child process cannot modify the parent process' environment.

What is obj in MonitorEnter?

I am not sure what obj is in MonitorEnter JNI function. Is it obj passed as parameter in native function or shared variable which I want to synchronize?
I have a variable called buffer which is shared by two threads.
This is my code.
JNIEXPORT void JNICALL Java_company_com_HelloActivity_setBuffer(JNIEnv *env, jobject obj, jstring jstr)
{
char buf[256];
int len = (*env)->GetStringLength(env, jstr);
(*env)->GetStringUTFRegion(env, jstr, 0, len, buf);
(*env)->MonitorEnter(env, obj); // I don't think this is correct.
strcat(buffer, buf); // buffer is declared as global char buffer[256];
(*env)->MonitorExit(env, obj);
}
EDIT:
How about this? syncobj is defined in Activity as static Object and shared with another thread.
JNIEXPORT void JNICALL Java_company_com_HelloActivity_setBuffer(JNIEnv *env, jobject obj, jstring jstr, jobject syncobj)
{
char buf[256];
int len = (*env)->GetStringLength(env, jstr);
(*env)->GetStringUTFRegion(env, jstr, 0, len, buf);
(*env)->MonitorEnter(env, syncobj);
strcat(buffer, buf);
(*env)->MonitorExit(env, syncobj);
}
It is exactly the same as in this Java code:
synchronized (syncobj) // = MonitorEnter(env, syncobj)
{
// ...
} // = MonitorExit(env, syncobj)
Your first snippet is an equivalent of synchronized(this), the second snippet is synchronized(syncobj). But both snippets make sense only if the other thread accessing your buffer can see either this or syncobj. I am afraid that the other thread is a native one. Why else you would create the buffer in native code then? If i am correct, then MonitorEnter/Exit is unnecessarily arcane - you could use it but the other native thread would need to hold a global reference to this or syncobj. Native locking seems like much cleaner solution. You are locking native resource, not JVM resource.

How to access arrays within an object with JNI?

JNI tutorials, for instance this one, cover quite well how to access primitive fields within an object, as well as how to access arrays that are provided as explicit function arguments (i.e. as subclasses of jarray). But how to access Java (primitive) arrays that are fields within an jobject? For instance, I'd like to operate on the byte array of the following Java object:
class JavaClass {
...
int i;
byte[] a;
}
The main program could be something like this:
class Test {
public static void main(String[] args) {
JavaClass jc = new JavaClass();
jc.a = new byte[100];
...
process(jc);
}
public static native void process(JavaClass jc);
}
The corresponding C++ side would then be:
JNIEXPORT void JNICALL Java_Test_process(JNIEnv * env, jclass c, jobject jc) {
jclass jcClass = env->GetObjectClass(jc);
jfieldID iId = env->GetFieldID(jcClass, "i", "I");
// This way we can get and set the "i" field. Let's double it:
jint i = env->GetIntField(jc, iId);
env->SetIntField(jc, iId, i * 2);
// The jfieldID of the "a" field (byte array) can be got like this:
jfieldID aId = env->GetFieldID(jcClass, "a", "[B");
// But how do we operate on the array???
}
I was thinking to use GetByteArrayElements, but it wants an ArrayType as its argument. Obviously I'm missing something. Is there a way to to this?
I hope that will help you a little (check out the JNI Struct reference, too):
// Get the class
jclass mvclass = env->GetObjectClass( *cls );
// Get method ID for method getSomeDoubleArray that returns a double array
jmethodID mid = env->GetMethodID( mvclass, "getSomeDoubleArray", "()[D");
// Call the method, returns JObject (because Array is instance of Object)
jobject mvdata = env->CallObjectMethod( *base, mid);
// Cast it to a jdoublearray
jdoubleArray * arr = reinterpret_cast<jdoubleArray*>(&mvdata)
// Get the elements (you probably have to fetch the length of the array as well
double * data = env->GetDoubleArrayElements(*arr, NULL);
// Don't forget to release it
env->ReleaseDoubleArrayElements(*arr, data, 0);
Ok here I operate with a method instead of a field (I considered calling a Java getter cleaner) but you probably can rewrite it for the fields as well. Don't forget to release and as in the comment you'll probably still need to get the length.
Edit: Rewrite of your example to get it for a field. Basically replace CallObjectMethod by GetObjectField.
JNIEXPORT void JNICALL Java_Test_process(JNIEnv * env, jclass c, jobject jc) {
jclass jcClass = env->GetObjectClass(jc);
jfieldID iId = env->GetFieldID(jcClass, "i", "I");
// This way we can get and set the "i" field. Let's double it:
jint i = env->GetIntField(jc, iId);
env->SetIntField(jc, iId, i * 2);
// The jfieldID of the "a" field (byte array) can be got like this:
jfieldID aId = env->GetFieldID(jcClass, "a", "[B");
// Get the object field, returns JObject (because Array is instance of Object)
jobject mvdata = env->GetObjectField (jc, aID);
// Cast it to a jdoublearray
jdoubleArray * arr = reinterpret_cast<jdoubleArray*>(&mvdata)
// Get the elements (you probably have to fetch the length of the array as well
double * data = env->GetDoubleArrayElements(*arr, NULL);
// Don't forget to release it
env->ReleaseDoubleArrayElements(*arr, data, 0);
}
In gcc 6.3 I get a warning saying "dereferencing type-punned pointer will break strict-aliasing rules" from a line like this:
jdoubleArray arr = *reinterpret_cast<jdoubleArray*>(&mvdata);
But since jdoubleArray is itself a pointer to class _jdoubleArray, there's no need to get the address before casting, and this static_cast works without warnings:
jdoubleArray arr = static_cast<jdoubleArray>(mvdata);