Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jnigen] Implement multiple interfaces #1584

Merged
merged 3 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but have you considered using Dart's null instead of jNullReference? That's what the ObjC bindings do. It means you get Dart's null safety for free.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every Java object can be null, so it creates a lot of question mark noise. Could be an option though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait you mean only for the reference type? Yeah I could do that!

}

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
Loading