Skip to content

Commit

Permalink
[jnigen] Implement multiple interfaces (#1584)
Browse files Browse the repository at this point in the history
  • Loading branch information
HosseinYousefi authored Sep 20, 2024
1 parent 7922b20 commit 12bc10a
Show file tree
Hide file tree
Showing 28 changed files with 724 additions and 318 deletions.
10 changes: 9 additions & 1 deletion pkgs/jni/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:jni/jni.dart';

extension on String {
/// Returns a Utf-8 encoded `Pointer<Char>` with contents same as this string.
Pointer<Char> toNativeChars(Allocator allocator) {
return toNativeUtf8(allocator: allocator).cast<Char>();
}
}

// An example of calling JNI methods using low level primitives.
// GlobalJniEnv is a thin abstraction over JNIEnv in JNI C API.
//
Expand All @@ -18,7 +26,7 @@ import 'package:jni/jni.dart';
String toJavaStringUsingEnv(int n) => using((arena) {
final env = Jni.env;
final cls = env.FindClass("java/lang/String".toNativeChars(arena));
final mId = env.GetStaticMethodID(cls, "valueOf".toNativeChars(),
final mId = env.GetStaticMethodID(cls, "valueOf".toNativeChars(arena),
"(I)Ljava/lang/String;".toNativeChars(arena));
final i = arena<JValue>();
i.ref.i = n;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,32 @@
package com.github.dart_lang.jni;

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

public class PortProxy implements InvocationHandler {
public class PortProxyBuilder implements InvocationHandler {
private static final PortCleaner cleaner = new PortCleaner();

static {
System.loadLibrary("dartjni");
}

private final long port;
private static final class DartImplementation {
final long port;
final long pointer;

DartImplementation(long port, long pointer) {
this.port = port;
this.pointer = pointer;
}
}

private boolean built = false;
private final long isolateId;
private final long functionPtr;
private final HashMap<String, DartImplementation> implementations = new HashMap<>();

private PortProxy(long port, long isolateId, long functionPtr) {
this.port = port;
public PortProxyBuilder(long isolateId) {
this.isolateId = isolateId;
this.functionPtr = functionPtr;
}

private static String getDescriptor(Method method) {
Expand Down Expand Up @@ -62,15 +72,28 @@ private static void appendType(StringBuilder descriptor, Class<?> type) {
}
}

public static Object newInstance(String binaryName, long port, long isolateId, long functionPtr)
throws ClassNotFoundException {
Class<?> clazz = Class.forName(binaryName);
public void addImplementation(String binaryName, long port, long functionPointer) {
implementations.put(binaryName, new DartImplementation(port, functionPointer));
}

public Object build() throws ClassNotFoundException {
if (implementations.isEmpty()) {
throw new IllegalStateException("No interface implementation added");
}
if (built) {
throw new IllegalStateException("This proxy has already been built");
}
built = true;
ArrayList<Class<?>> classes = new ArrayList<>();
for (String binaryName : implementations.keySet()) {
classes.add(Class.forName(binaryName));
}
Object obj =
Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[] {clazz},
new PortProxy(port, isolateId, functionPtr));
cleaner.register(obj, port);
classes.get(0).getClassLoader(), classes.toArray(new Class<?>[0]), this);
for (DartImplementation implementation : implementations.values()) {
cleaner.register(obj, implementation.port);
}
return obj;
}

Expand All @@ -89,7 +112,15 @@ private static native Object[] _invoke(

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object[] result = _invoke(port, isolateId, functionPtr, proxy, getDescriptor(method), args);
DartImplementation implementation = implementations.get(method.getDeclaringClass().getName());
Object[] result =
_invoke(
implementation.port,
isolateId,
implementation.pointer,
proxy,
getDescriptor(method),
args);
_cleanUp((Long) result[0]);
if (result[1] instanceof DartException) {
Throwable cause = ((DartException) result[1]).cause;
Expand Down
3 changes: 2 additions & 1 deletion pkgs/jni/lib/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export 'dart:ffi' show nullptr;
export 'package:ffi/ffi.dart' show Arena, using;

export 'src/errors.dart';
export 'src/jni.dart' hide ProtectedJniExtensions;
export 'src/jimplementer.dart';
export 'src/jni.dart' hide ProtectedJniExtensions, StringMethodsForJni;
export 'src/jobject.dart';
export 'src/jreference.dart' hide ProtectedJReference;
export 'src/jvalues.dart';
Expand Down
3 changes: 2 additions & 1 deletion pkgs/jni/lib/src/accessors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ extension JniResultMethods on JniResult {
}

JReference get reference {
return JGlobalReference(objectPointer);
final pointer = objectPointer;
return pointer == nullptr ? jNullReference : JGlobalReference(pointer);
}

T object<T extends JObject>(JObjType<T> type) {
Expand Down
118 changes: 118 additions & 0 deletions pkgs/jni/lib/src/jimplementer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi';
import 'dart:isolate';

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

import '../_internal.dart';
import 'jobject.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.
///
/// Example:
/// ```dart
/// final implementer = JImplemeneter();
/// Foo.implementIn(implementer, fooImpl);
/// Bar.implementIn(implementer, barImpl);
/// final foobar = implementer.build(Foo.type); // Or `Bar.type`.
/// ```
class JImplementer extends JObject {
JImplementer.fromReference(super.reference) : super.fromReference();

static final _class =
JClass.forName(r'com/github/dart_lang/jni/PortProxyBuilder');

static final _newId = _class.constructorId(r'(J)V');

static final _new = ProtectedJniExtensions.lookup<
NativeFunction<
JniResult Function(Pointer<Void>, JMethodIDPtr,
VarArgs<(Int64,)>)>>('globalEnv_NewObject')
.asFunction<JniResult Function(Pointer<Void>, JMethodIDPtr, int)>();

factory JImplementer() {
ProtectedJniExtensions.ensureInitialized();
return JImplementer.fromReference(_new(
_class.reference.pointer,
_newId as JMethodIDPtr,
ProtectedJniExtensions.getCurrentIsolateId())
.reference);
}

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

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

/// Should not be used directly.
///
/// Use `implementIn` from the generated interface instead.
@internal
void add(
String binaryName,
RawReceivePort port,
Pointer<NativeFunction<JObjectPtr Function(Int64, JObjectPtr, JObjectPtr)>>
pointer,
) {
using((arena) {
_addImplementation(
reference.pointer,
_addImplementationId as JMethodIDPtr,
(binaryName.toJString()..releasedBy(arena)).reference.pointer,
port.sendPort.nativePort,
pointer.address)
.check();
});
}

static final _buildId = _class.instanceMethodId(
r'build',
r'()Ljava/lang/Object;',
);

static final _build = ProtectedJniExtensions.lookup<
NativeFunction<
JniResult Function(
Pointer<Void>,
JMethodIDPtr,
)>>('globalEnv_CallObjectMethod')
.asFunction<
JniResult Function(
Pointer<Void>,
JMethodIDPtr,
)>();

/// Builds an proxy object with the specified [type] that implements all the
/// added interfaces with the given implementations.
///
/// Releases this implementer.
T implement<T extends JObject>(JObjType<T> type) {
return type.fromReference(implementReference());
}

/// Used in the JNIgen generated code.
///
/// It is unnecessary to construct the type object when the code is generated.
@internal
JReference implementReference() {
final ref = _build(reference.pointer, _buildId as JMethodIDPtr).reference;
release();
return ref;
}
}
Loading

0 comments on commit 12bc10a

Please sign in to comment.