Skip to content

Commit

Permalink
[jnigen] Implement void interface methods as listeners (#1596)
Browse files Browse the repository at this point in the history
  • Loading branch information
HosseinYousefi authored Sep 27, 2024
1 parent 8b260e6 commit 16918ae
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
package com.github.dart_lang.jni;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.*;

public class PortProxyBuilder implements InvocationHandler {
private static final PortCleaner cleaner = new PortCleaner();
Expand All @@ -28,6 +27,7 @@ private static final class DartImplementation {
private boolean built = false;
private final long isolateId;
private final HashMap<String, DartImplementation> implementations = new HashMap<>();
private final HashSet<String> asyncMethods = new HashSet<>();

public PortProxyBuilder(long isolateId) {
this.isolateId = isolateId;
Expand Down Expand Up @@ -72,8 +72,10 @@ private static void appendType(StringBuilder descriptor, Class<?> type) {
}
}

public void addImplementation(String binaryName, long port, long functionPointer) {
public void addImplementation(
String binaryName, long port, long functionPointer, List<String> asyncMethods) {
implementations.put(binaryName, new DartImplementation(port, functionPointer));
this.asyncMethods.addAll(asyncMethods);
}

public Object build() throws ClassNotFoundException {
Expand Down Expand Up @@ -106,21 +108,28 @@ private static native Object[] _invoke(
long functionPtr,
Object proxy,
String methodDescriptor,
Object[] args);
Object[] args,
boolean isBlocking);

private static native void _cleanUp(long resultPtr);

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DartImplementation implementation = implementations.get(method.getDeclaringClass().getName());
String descriptor = getDescriptor(method);
boolean isBlocking = !asyncMethods.contains(descriptor);
Object[] result =
_invoke(
implementation.port,
isolateId,
implementation.pointer,
proxy,
getDescriptor(method),
args);
descriptor,
args,
isBlocking);
if (!isBlocking) {
return null;
}
_cleanUp((Long) result[0]);
if (result[1] instanceof DartException) {
Throwable cause = ((DartException) result[1]).cause;
Expand Down
33 changes: 18 additions & 15 deletions pkgs/jni/lib/src/jimplementer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
import 'dart:ffi';
import 'dart:isolate';

import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart' show internal;

import '../jni.dart';
import 'accessors.dart';
import 'jni.dart';
import 'jobject.dart';
import 'jreference.dart';
import 'lang/jstring.dart';
import 'third_party/generated_bindings.dart';
import 'types.dart';

/// A builder that builds proxy objects that implement one or more interfaces.
Expand Down Expand Up @@ -50,17 +46,17 @@ class JImplementer extends JObject {

static final _addImplementationId = _class.instanceMethodId(
r'addImplementation',
r'(Ljava/lang/String;JJ)V',
r'(Ljava/lang/String;JJLjava/util/List;)V',
);

static final _addImplementation = ProtectedJniExtensions.lookup<
NativeFunction<
JThrowablePtr Function(Pointer<Void>, JMethodIDPtr,
VarArgs<(Pointer<Void>, Int64, Int64)>)>>(
VarArgs<(Pointer<Void>, Int64, Int64, Pointer<Void>)>)>>(
'globalEnv_CallVoidMethod')
.asFunction<
JThrowablePtr Function(
Pointer<Void>, JMethodIDPtr, Pointer<Void>, int, int)>();
JThrowablePtr Function(Pointer<Void>, JMethodIDPtr, Pointer<Void>,
int, int, Pointer<Void>)>();

/// Should not be used directly.
///
Expand All @@ -71,15 +67,22 @@ class JImplementer extends JObject {
RawReceivePort port,
Pointer<NativeFunction<JObjectPtr Function(Int64, JObjectPtr, JObjectPtr)>>
pointer,
List<String> asyncMethods,
) {
using((arena) {
_addImplementation(
reference.pointer,
_addImplementationId as JMethodIDPtr,
(binaryName.toJString()..releasedBy(arena)).reference.pointer,
port.sendPort.nativePort,
pointer.address)
.check();
reference.pointer,
_addImplementationId as JMethodIDPtr,
(binaryName.toJString()..releasedBy(arena)).reference.pointer,
port.sendPort.nativePort,
pointer.address,
(asyncMethods
.map((m) => m.toJString()..releasedBy(arena))
.toJList(JString.type)
..releasedBy(arena))
.reference
.pointer,
).check();
});
}

Expand Down
5 changes: 4 additions & 1 deletion pkgs/jni/lib/src/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ extension ProtectedJniExtensions on Jni {
/// Returns the result of a callback.
static void returnResult(
Pointer<CallbackResult> result, JObjectPtr object) async {
Jni._bindings.resultFor(result, object);
// The result is `nullptr` when the callback is a listener.
if (result != nullptr) {
Jni._bindings.resultFor(result, object);
}
}

static Dart_FinalizableHandle newJObjectFinalizableHandle(
Expand Down
40 changes: 26 additions & 14 deletions pkgs/jni/src/dartjni.c
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,20 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke(
jlong functionPtr,
jobject proxy,
jstring methodDescriptor,
jobjectArray args) {
CallbackResult* result = (CallbackResult*)malloc(sizeof(CallbackResult));
if (isolateId != (jlong)Dart_CurrentIsolate_DL()) {
init_lock(&result->lock);
init_cond(&result->cond);
acquire_lock(&result->lock);
result->ready = 0;
result->object = NULL;
jobjectArray args,
jboolean isBlocking) {
CallbackResult* result = NULL;
if (isBlocking) {
result = (CallbackResult*)malloc(sizeof(CallbackResult));
}
if (isolateId != (jlong)Dart_CurrentIsolate_DL() || !isBlocking) {
if (isBlocking) {
init_lock(&result->lock);
init_cond(&result->cond);
acquire_lock(&result->lock);
result->ready = 0;
result->object = NULL;
}

Dart_CObject c_result;
c_result.type = Dart_CObject_kInt64;
Expand All @@ -448,18 +454,24 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke(

Dart_PostCObject_DL(port, &c_post);

while (!result->ready) {
wait_for(&result->cond, &result->lock);
}
if (isBlocking) {
while (!result->ready) {
wait_for(&result->cond, &result->lock);
}

release_lock(&result->lock);
destroy_lock(&result->lock);
destroy_cond(&result->cond);
release_lock(&result->lock);
destroy_lock(&result->lock);
destroy_cond(&result->cond);
}
} else {
result->object = ((jobject(*)(uint64_t, jobject, jobject))functionPtr)(
port, (*env)->NewGlobalRef(env, methodDescriptor),
(*env)->NewGlobalRef(env, args));
}
if (!isBlocking) {
// No result is created in this case, there is nothing to clean up either.
return NULL;
}
// Returning an array of length 2.
// [0]: The result pointer, used for cleaning up the global reference, and
// freeing the memory since we passed the ownership to Java.
Expand Down
1 change: 1 addition & 0 deletions pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Added `JImplementer` which enables building an object that implements multiple
Java interfaces. Each interface now has a static `implementIn` method that
takes a `JImplementer` and the implementation object.
- Added the ability to implement void-returning interface methods as listeners.
- Generating identifiers that start with an underscore (`_`) and making them
public by prepending a dollar sign.
- Fixed an issue where inheriting a generic class could generate incorrect code.
Expand Down
48 changes: 44 additions & 4 deletions pkgs/jnigen/docs/interface_implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,24 @@ class Runnable extends JObject {
) { /* ... */ }
}
abstract interface class $Runnable {
abstract mixin class $Runnable {
factory $Runnable({
required void Function() run,
bool run$async,
}) = _$Runnable;
bool get run$async => false;
void run();
}
class _$Runnable implements $Runnable {
_$Runnable({
required void Function() run,
this.run$async = false;
}) : _run = run;
final void Function() _run;
final bool run$async;
void run() {
return _run();
Expand Down Expand Up @@ -83,7 +87,7 @@ implementing the interface in Java instead of using the lambdas:
```java
// Java
public class Printer implements Runnable {
public class Printer with Runnable {
private final String text;
public Printer(String text) {
Expand All @@ -104,7 +108,7 @@ You can do the same in Dart by creating a subclass that implements `$Runnable`:
```dart
// Dart
class Printer implements $Runnable {
class Printer with $Runnable {
final String text;
Printer(this.text);
Expand All @@ -120,6 +124,42 @@ And similarly write `Runnable.implement(Printer('hello'))` and
`Runnable.implement(Printer('world'))`, to create multiple Runnables and share
common logic.
### Implement as a listener
By default, when any of methods of the implemented interface gets called, the
caller will block until the callee returns a result.
Void-returning functions don't have to return a result, so we can choose to not
block the caller when the method is just a listener. To signal this, pass `true`
to `<method name>$async` argument when implementing the interface inline:
```dart
// Dart
final runnable = Runnable.implement($Runnable(
run: () => print('hello'),
run$async: true, // This makes the run method non-blocking.
));
```
Similarly, when subclassing
```dart
// Dart
class Printer with $Runnable {
final String text;
Printer(this.text);
@override
void run() {
print(text);
}
@override
bool get run$async => true; // This makes the run method non-blocking.
```
### Implement multiple interfaces
To implement more than one interface, use a `JImplementer` from `package:jni`.
Expand All @@ -141,5 +181,5 @@ possible to make it a `Closable` by passing in `Closable.type` to
```dart
// Dart
final closable = object.castTo(Closable.type);
final closable = object.as(Closable.type);
```
Loading

0 comments on commit 16918ae

Please sign in to comment.