Historically, JNI code in Fennec has mostly used raw JNI types like jobject
and jstring
. However, the need to manage object lifetimes and the lack of strong typing make this practice error-prone. We do have some helper classes like AutoLocalJNIFrame
, RefCountedJavaObject
, WrappedJavaObject
, and AutoGlobalWrappedJavaObject
, but I've found them to be inconvenient to use.
Bug 1116868 is introducing several new “smart” classes to improve dealing with JNI references. As a start, instead of using raw jobject
types, there are now different smart types for different usages:
Type | When to use |
---|---|
Object::LocalRef | To replace local jobject references; e.g. local variables |
Object::GlobalRef | To replace global jobject references; e.g. instance members |
Object::Param | To replace jobject function parameters |
Note that these new classes are under the mozilla::jni
namespace, so your code should include it first,
using namespace mozilla::jni; // then use Object::LocalRef namespace jni = mozilla::jni; // then use jni::Object::LocalRef
These classes make managing lifetimes very easy. Previously, it was easy to make mistakes like this,
jobject obj = GetObject(); // use obj obj = GetAnotherObject(); // oops! first obj was leaked
This mistake could happen to both local and global references. Now with the smart classes, these errors are eliminated,
Object::LocalRef obj = GetObject(); // use obj obj = GetAnotherObject(); // first obj was automatically deleted
The new classes also make it easy to convert between local and global references,
// Object::GlobalRef mObj; Object::LocalRef obj = mObj; // automatic conversion from global to local ref obj = GetNewObject(); mObj = obj; // automatic conversion from local to global ref
For function parameters, you can pass either a LocalRef
or a GlobalRef
to a Param
parameter,
void SetObject(Object::Param obj); Object::LocalRef obj; SetObject(obj); // pass in LocalRef // Object::GlobalRef mObj; SetObject(mObj); // pass in GlobalRef
For function return values, you should return a LocalRef
per JNI convention,
Object::LocalRef GetObject() { Object::LocalRef ret = MakeObject(); return ret; }
Note that in the above example, only one local reference is ever created because of return value optimization performed by the compiler.
Both LocalRef
and GlobalRef
support move semantics, so the following example still creates only one local reference overall,
Object::LocalRef GetObject(); Object::LocalRef foo; foo = GetObject();
It also means you can use LocalRef
and GlobalRef
with container classes like mozilla::Vector
without worrying about performance impact.
LocalRef
and GlobalRef
can be used like pointers/Java references,
Object::GlobalRef ref = nullptr; ref = GetRef(); if (ref) { Object::LocalRef ref2 = ref; // compare underlying objects, so a LocalRef // and a GlobalRef of the same object are equal MOZ_ASSERT(ref == ref2); }
jobject
is not the only wrapped type; other JNI types correspond to different smart types,
JNI type | Use these smart types | ||
---|---|---|---|
jstring | String::LocalRef | String::GlobalRef | String::Param |
jclass | ClassObject::LocalRef | ClassObject::GlobalRef | ClassObject::Param |
jthrowable | Throwable::LocalRef | Throwable::GlobalRef | Throwable::Param |
jbooleanArray | BooleanArray::LocalRef | BooleanArray::GlobalRef | BooleanArray::Param |
jbyteArray | ByteArray::LocalRef | ByteArray::GlobalRef | ByteArray::Param |
… | … | ||
jobjectArray | ObjectArray::LocalRef | ObjectArray::GlobalRef | ObjectArray::Param |
And if you use auto-generated classes from widget/android/GeneratedJNIWrappers.h, each class is being updated in bug 1116589 to have its own reference types. For example, mozilla::widget::ViewTransform::LocalRef
is a local reference to a Java ViewTransform
instance,
ViewTransform::LocalRef vt = ViewTransform::New(); float x = vt->OffsetX(); // get offsetX field vt->OffsetX(1.0f); // set offsetX field
Using separate classes ensures better type-safety, because type-checking is done by the compiler,
Foo::LocalRef foo; Bar::LocalRef bar = foo; // error: invalid conversion
However, the auto-generated classes often only accept Object::Param
parameters or return Object::LocalRef
values. Therefore, as a special case, any LocalRef
or GlobalRef
can automatically convert to Object::Param
, and Object::LocalRef
can automatically convert to any other LocalRef
.
Object::LocalRef Foo(Object::Param foo); Bar::LocalRef bar; bar = Foo(bar); // bar is converted to Object::Param // then return value is converted to Bar::LocalRef
The String
types have some custom behavior in addition to the standard behavior. A String::LocalRef
or String::GlobalRef
can automatically convert to a nsString
or a nsCString
,
String::LocalRef GetString(); nsString str = nsString(GetString()); nsCString cstr = nsCString(GetString());
Conversely, a nsAString
or a nsACString
can automatically convert to a String::Param
,
void SetString(String::Param param); nsString str; SetString(str); SetString(NS_LITERAL_CSTRING("text")); String::LocalRef ref; SetString(ref); // okay too
A String::LocalRef
, String::GlobalRef
, or String::Param
also has a Length
method,
size_t GetStringLength(String::Param param) { return param.Length(); } MOZ_ASSERT(GetStringLength(NS_LITERAL_STRING("text")) == 4);
The goal of these new classes is to make using raw JNI types obsolete. However, until all the refactoring is done, there are still cases where raw JNI values are needed, for example to call JNIEnv functions.
To get a raw JNI reference from any LocalRef
or GlobalRef
, call its Get
method,
void Foo(jobject param); Object::LocalRef obj; Foo(obj.Get());
To turn a raw JNI reference into any Param
, call the Foo::Ref::From
method,
void Foo(Object::Param param); jobject obj; Foo(Object::Ref::From(obj));
To return a raw JNI reference from any LocalRef
or GlobalRef
, call its Forget
method,
jobject GetRawRef() { Object::LocalRef ref = GetRef(); return ref.Forget(); }
Use LocalRef::Adopt
to manage a returned raw local reference,
jobject GetRawRef(); Object::LocalRef ref = Object::LocalRef::Adopt(GetRawRef());
LocalRef
also has an Env
method that returns a cached JNIEnv
pointer,
Object::LocalRef ref = GetRef(); auto cls = ClassObject::LocalRef::Adopt( ref.Env()->GetObjectClass(ref.Get()));