-
Notifications
You must be signed in to change notification settings - Fork 74
Description
Problem
When using JNI, references to Java objects need to be deleted. Manual management leads to the following type of code.
testWidgets("Long.intValue() using JniObject", (tester) async {
final longClass = jni.findJniClass("java/lang/Long");
final longCtor = longClass.getConstructorID("(J)V");
final long = longClass.newObject(longCtor, [176]);
final intValue = long.callIntMethodByName("intValue", "()I", []);
expect(intValue, equals(176));
long.delete();
longClass.delete();
});
This type of code smells like it should use the Arena
from package:ffi
.
https://github.com/dart-lang/ffi/blob/master/lib/src/arena.dart
The question is what API we should use.
API 1: Extension method on Arena
testWidgets("Long.intValue() using JniObject", (tester) async {
using((Arena arena) {
final longClass = arena.manage(jni.findJniClass("java/lang/Long"));
final longCtor = longClass.getConstructorID("(J)V");
final long = arena.manage(longClass.newObject(longCtor, [176]));
final intValue = long.callIntMethodByName("intValue", "()I", []);
expect(intValue, equals(176));
});
});
Pro:
- Conceptually makes it very clear that the object returned from
manage
is managed by the arena so that the API user doesn't have to worry about managing it.
Con:
- It requires the API user to write
arena.manage
before the thing they are thinking about.
API 2: Method on objects with delete
testWidgets("Long.intValue() using JniObject", (tester) async {
using((Arena arena) {
final longClass = jni.findJniClass("java/lang/Long")..deletedBy(arena);
final longCtor = longClass.getConstructorID("(J)V");
final long = longClass.newObject(longCtor, [176])..deletedBy(arena);
final intValue = long.callIntMethodByName("intValue", "()I", []);
expect(intValue, equals(176));
});
});
Pro:
- The methods
delete
anddeletedBy
are clearly related.
Con:
..
feels like it is easily forgotten.deletedBy
doesn't feel like a natural method name. Should it bedeleter
?setDeleter
?
Side note: should it be delete
? It is DeleteGlobalRef
in JNI, but if we want to talk about the abstraction of native resource management, we've used release
as verb. Not free
or delete
.
API 3: Optional arena
parameter
testWidgets("Long.intValue() using JniObject", (tester) async {
using((Arena arena) {
final longClass = jni.findJniClass("java/lang/Long", arena: arena);
final longCtor = longClass.getConstructorID("(J)V");
final long = longClass.newObject(longCtor, [176], arena: arena);
final intValue = long.callIntMethodByName("intValue", "()I", []);
expect(intValue, equals(176));
});
});
Pro:
- Most concise
Con:
- Generated for a huge API surface
This approach would be similar to the optional allocator
parameter in toNativeUtf8
.
https://github.com/dart-lang/ffi/blob/master/lib/src/utf8.dart#L81
API 4: NativeFinalizer
s
We could attach a NativeFinalizer
to all objects and have them be cleaned up automatically.
testWidgets("Long.intValue() using JniObject", (tester) async {
final longClass = jni.findJniClass("java/lang/Long");
final longCtor = longClass.getConstructorID("(J)V");
final long = longClass.newObject(longCtor, [176]);
final intValue = long.callIntMethodByName("intValue", "()I", []);
expect(intValue, equals(176));
});
// Magic, at some point the GC does cleanup...
However, even in that case we could keep delete
and Arena
support. Those would cancel the native finalizer.
@mahesh-hegde and I are slightly leaning towards API 2.
@liamappelbe @lrhn @mkustermann WDYT?