I am trying to centralize the creation of references to JNI methods
and fields defined for java objects. According to the Davlik "JNI
Tips" (http://android.git.kernel.org/?p=platform/
dalvik.git;a=blob_plain;f=docs/jni-tips.html;hb=HEAD):
"Because we are limiting ourselves to one VM per process, it's
reasonable to store this data in a static local structure."
In my native code I have several places that will try to access
various properties/methods of my java classes, so I want to have all
these lookups in one place, then reference them from the various
points in the native code. Hence I have created a JNILookup (native)
class, and declared the jFieldID and jMethodID members as static:
Eg:
// JNILookup.h
#ifndef JNILOOKUP_H_
#define JNILOOKUP_H_
...
static jclass worldCls;
static jfieldID bodiesField;
class JNILookup {
public:
....
}
The instance of this object is declared on the stack in my "main"
code:
static JNILookup lookup;
When I initialize my main code, I call a method on this JNILookup
class:
lookup.initialize(this->jvm, env); // this->jvm is a pointer to the
JVM, and the env is a pointer to current JVM environment
In this initialize method, I attach to JNIEnv and perform the lookups:
int status;
bool isAttached = false;
status = jvm->GetEnv((void **)&env, JNI_VERSION_1_4);
if(status < 0) {
__android_log_print(ANDROID_LOG_DEBUG, "JNILookup", "Attaching local
JNI environment reference");
// Attach this thread to the jvm
if (jvm->AttachCurrentThread(&env, NULL) < 0) {
__android_log_print(ANDROID_LOG_ERROR, "JNILookup", "Failed to
attach to JVM");
return;
}
isAttached = true;
}
...
worldCls = (jclass) env->NewGlobalRef(env->FindClass("com/blah/
World"));
...
bodiesField = env->GetFieldID( worldCls , "bodies", "[Lcom/blah/
Body;");
if(isAttached) jvm->DetachCurrentThread();
When I clean up, I delete the global ref to the jclass:
if(worldCls != NULL) env->DeleteGlobalRef(worldCls);
The problem I am having is when I try to access this field from
elsewhere in the native code (ie another class), I get the following
error:
JNI WARNING: inst fieldID 0x0 not valid for class Lcom/blah/World;
Obviously the reference to the field ID is being lost somewhere along
the way.
According to the JNI tips:
"Looking them [field IDs] up may require several string comparisons,
but once you have them the actual call to get the field or invoke the
method is very quick."
so clearly I don't want to have to look these up every time.
Does anyone have any clue as to how I can cache the lookup of a field
or method?
Thanks.
I was making the noob mistake of confusing a "definition" with a
"declaration" in C++.
The steps I used to achieve the result are:
1. Create a class (header file + cpp file) to contain all your class/
field/method lookups.
2. Define the member fields in the header file as static fields within
the "public" area
3. Declare the fields in the corresponding cpp file OUTSIDE the class
definition.
4. Define a static instance of the lookup class in your main
application code.
For example:
// JNILookup.h
class JNILookup {
public:
...
void initialize(JavaVM* jvm, JNIEnv* env);
// Define the class/field
static jclass someClass;
static jfieldID aField;
};
// JNILookup.cpp
// Declare the class/field
jclass JNILookup::someClass= NULL;
jfieldID JNILookup::aField= NULL;
void JNILookup::initialize(JavaVM* jvm, JNIEnv* env) {
// Assign the class/field
someClass= (jclass) env->NewGlobalRef(env->FindClass("com/blah/
SomeClass"));
aField= env->GetFieldID( someClass, "javaFieldName", "Lcom/blah/
SomeFieldClass;");
}
// MainApp.cpp
// Declare outside all methods
static JNILookup lookup;
void MainApp:init(JavaVM* jvm, JNIEnv *env) {
// Initialise lookups
lookup.initialize(jvm, env);
}
void MainApp:someMethod(jobject anObject) {
jobject someInstance = env->GetObjectField(anObject,
JNILookup::aField); // An instance of SomeFieldClass contained in an
instance of SomeClass
}
Of course I am not 100% sure this is the BEST way to achieve the
result.. anyone else got some comments/better ideas?
Cheers,
Jason.