-
Notifications
You must be signed in to change notification settings - Fork 87
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
manageis 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.managebefore 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
deleteanddeletedByare clearly related.
Con:
..feels like it is easily forgotten.deletedBydoesn'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: NativeFinalizers
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?