Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Interface implementation #326

Merged
merged 25 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1af8e2c
setup
HosseinYousefi Jun 14, 2023
3ff33fb
add C methods + bindings for PortProxy
HosseinYousefi Jun 27, 2023
d0e6b11
move newPortCont. to ProtectedJniExt.
HosseinYousefi Jun 28, 2023
73f2baf
attach finalizable to close the port for interfaces - generate descri…
HosseinYousefi Jun 28, 2023
b13b77e
add missing JCharacter boxed type, .implement method completed for in…
HosseinYousefi Jul 3, 2023
becc6e5
fix descriptor issues
HosseinYousefi Jul 6, 2023
8d3ed93
fix comment
HosseinYousefi Jul 11, 2023
a5b2095
synchronize in the C layer instead + auto build jni.jar + test
HosseinYousefi Jul 14, 2023
13d0ada
call the function pointer when on the same thread
HosseinYousefi Jul 19, 2023
05fe1cf
generate correct implement when no method is available + bring typePa…
HosseinYousefi Jul 19, 2023
8d2da89
java format
HosseinYousefi Jul 19, 2023
daf0dd6
remove print
HosseinYousefi Jul 19, 2023
fa32943
remove more prints + add more time limit to descriptor test
HosseinYousefi Jul 20, 2023
6c78989
fix method filling
HosseinYousefi Jul 20, 2023
266f984
ignore unused local variable for jnigen generated files
HosseinYousefi Jul 20, 2023
dd0258e
regenarate bindings to add the ignore
HosseinYousefi Jul 20, 2023
64f17ac
temporarily do not check the ffigen bindings until ffigen#555 is solved
HosseinYousefi Jul 20, 2023
0b88397
revert the versions back to 2.17 as it was not necessary yet to upgra…
HosseinYousefi Jul 20, 2023
47b305b
remove empty file
HosseinYousefi Jul 20, 2023
6b4f112
fix synchronization
HosseinYousefi Jul 21, 2023
744344c
remove duplicated java source in jni + sort ffigen.yaml inline functions
HosseinYousefi Jul 21, 2023
aade7d9
add missing tests
HosseinYousefi Jul 21, 2023
16d71ea
add the interface implementation under an experiment flag + docs
HosseinYousefi Jul 21, 2023
8bb8e48
fix primitive boxing
HosseinYousefi Jul 21, 2023
a887b90
address comments
HosseinYousefi Jul 25, 2023
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
14 changes: 9 additions & 5 deletions .github/workflows/test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,15 @@ jobs:
flag-name: jni_tests
parallel: true
path-to-lcov: ./jni/coverage/lcov.info
- name: regenerate & compare ffigen bindings
## Use git to verify no source files have changed
run: |
dart run tool/generate_ffi_bindings.dart
git diff --exit-code -- lib/src/third_party src/third_party
# TODO(https://github.com/dart-lang/ffigen/issues/555): Ffigen generated
# on my machine has macOS specific stuff and CI does not.
# We should just generate the struct as opaque, but we currently can't.
#
# - name: regenerate & compare ffigen bindings
# ## Use git to verify no source files have changed
# run: |
# dart run tool/generate_ffi_bindings.dart
# git diff --exit-code -- lib/src/third_party src/third_party

## Run tests for package:jni on windows, just to confirm it works.
## Do not, for example, collect coverage or check formatting.
Expand Down
82 changes: 82 additions & 0 deletions jni/android/src/main/java/com/github/dart_lang/jni/PortProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
// 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.

package com.github.dart_lang.jni;

import java.lang.reflect.*;

public class PortProxy implements InvocationHandler {
static {
System.loadLibrary("dartjni");
}

private final long port;
private final long threadId;
private final long functionPtr;

private PortProxy(long port, long threadId, long functionPtr) {
this.port = port;
this.threadId = threadId;
this.functionPtr = functionPtr;
}

private static String getDescriptor(Method method) {
StringBuilder descriptor = new StringBuilder();
descriptor.append(method.getName()).append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> paramType : parameterTypes) {
appendType(descriptor, paramType);
}
descriptor.append(")");
appendType(descriptor, method.getReturnType());
return descriptor.toString();
}

private static void appendType(StringBuilder descriptor, Class<?> type) {
if (type == void.class) {
descriptor.append("V");
} else if (type == boolean.class) {
descriptor.append("Z");
} else if (type == byte.class) {
descriptor.append("B");
} else if (type == char.class) {
descriptor.append("C");
} else if (type == short.class) {
descriptor.append("S");
} else if (type == int.class) {
descriptor.append("I");
} else if (type == long.class) {
descriptor.append("J");
} else if (type == float.class) {
descriptor.append("F");
} else if (type == double.class) {
descriptor.append("D");
} else if (type.isArray()) {
descriptor.append('[');
appendType(descriptor, type.getComponentType());
} else {
descriptor.append("L").append(type.getName().replace('.', '/')).append(";");
}
}

public static Object newInstance(String binaryName, long port, long threadId, long functionPtr)
throws ClassNotFoundException {
Class clazz = Class.forName(binaryName);
return Proxy.newProxyInstance(
clazz.getClassLoader(), new Class[] {clazz}, new PortProxy(port, threadId, functionPtr));
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return _invoke(port, threadId, functionPtr, proxy, getDescriptor(method), args);
}

private native Object _invoke(
long port,
long threadId,
long functionPtr,
Object proxy,
String methodDescriptor,
Object[] args);
}
45 changes: 31 additions & 14 deletions jni/bin/setup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ void verboseLog(String msg) {
}
}

/// Find path to C sources in pub cache for package specified by [packageName].
/// Find path to C or Java sources in pub cache for package specified by
/// [packageName].
///
/// It's assumed C FFI sources are in "src/" relative to package root.
/// If package cannot be found, null is returned.
Future<String> findSources(String packageName) async {
Future<String> findSources(String packageName, String subDirectory) async {
final packageConfig = await findPackageConfig(Directory.current);
if (packageConfig == null) {
throw UnsupportedError("Please run from project root.");
Expand All @@ -106,7 +106,7 @@ Future<String> findSources(String packageName) async {
if (package == null) {
throw UnsupportedError("Cannot find package: $packageName");
}
return package.root.resolve("src").toFilePath();
return package.root.resolve(subDirectory).toFilePath();
}

/// Return '/src' directories of all dependencies which has a CMakeLists.txt
Expand Down Expand Up @@ -170,7 +170,8 @@ void main(List<String> arguments) async {

final sources = options.sources;
for (var packageName in options.packages) {
sources.add(await findSources(packageName));
// It's assumed C FFI sources are in "src/" relative to package root.
sources.add(await findSources(packageName, 'src'));
}
if (sources.isEmpty) {
final dependencySources = await findDependencySources();
Expand All @@ -185,6 +186,28 @@ void main(List<String> arguments) async {
exitCode = 1;
return;
}

final currentDirUri = Uri.directory(".");
final buildPath = options.buildPath ??
currentDirUri.resolve(_defaultRelativeBuildPath).toFilePath();
final buildDir = Directory(buildPath);
await buildDir.create(recursive: true);

final javaSrc = await findSources('jni', 'java');
final targetJar = File.fromUri(buildDir.uri.resolve('jni.jar'));
if (!needsBuild(targetJar, Directory.fromUri(Uri.directory(javaSrc)))) {
verboseLog('Last modified of ${targetJar.path}: '
'${targetJar.lastModifiedSync()}.');
stderr.writeln('Target newer than source, skipping build.');
} else {
verboseLog('Running mvn package for jni java sources to $buildPath.');
await runCommand(
'mvn',
['package', '-Dtarget=${buildDir.absolute.path}'],
await findSources('jni', 'java'),
);
}

for (var srcPath in sources) {
final srcDir = Directory(srcPath);
if (!srcDir.existsSync()) {
Expand All @@ -194,20 +217,14 @@ void main(List<String> arguments) async {
}

verboseLog("srcPath: $srcPath");

final currentDirUri = Uri.directory(".");
final buildPath = options.buildPath ??
currentDirUri.resolve(_defaultRelativeBuildPath).toFilePath();
final buildDir = Directory(buildPath);
await buildDir.create(recursive: true);
verboseLog("buildPath: $buildPath");

final targetFileUri = buildDir.uri.resolve(getTargetName(srcDir));
final targetFile = File.fromUri(targetFileUri);
if (!needsBuild(targetFile, srcDir)) {
verboseLog("last modified of ${targetFile.path}: "
"${targetFile.lastModifiedSync()}");
stderr.writeln("target newer than source, skipping build");
verboseLog("Last modified of ${targetFile.path}: "
"${targetFile.lastModifiedSync()}.");
stderr.writeln('Target newer than source, skipping build.');
continue;
}

Expand Down
12 changes: 11 additions & 1 deletion jni/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,27 @@ functions:
- 'release_lock'
- 'init_lock'
- 'destroy_lock'
- 'init_cond'
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
- 'destroy_cond'
- 'signal_cond'
- 'wait_for'
- 'thread_id'
- 'load_class'
- 'load_class_global_ref'
- 'attach_thread'
- 'load_method'
- 'load_static_method'
- 'load_field'
- 'load_static_field'
- 'load_class_platform'
- 'load_class_local_ref'
- 'to_global_ref'
- 'to_global_ref_result'
- 'check_exception'
- 'load_env'
# This is a native function in Java. No need to call it from Dart.
# Native functions in Java. No need to call them from Dart.
- 'Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith'
- 'Java_com_github_dart_1lang_jni_PortProxy__1invoke'
structs:
exclude:
- 'JniContext'
Expand All @@ -57,6 +66,7 @@ structs:
- '_JNIEnv'
- 'JNIInvokeInterface'
- '__va_list_tag'
- 'CallbackResult'
rename:
## opaque struct definitions, base types of jfieldID and jmethodID
'_jfieldID': 'jfieldID_'
Expand Down
1 change: 1 addition & 0 deletions jni/java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<build>
<finalName>jni</finalName>
<directory>${target}</directory>
</build>

<dependencies>
Expand Down
82 changes: 82 additions & 0 deletions jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2023, 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.

package com.github.dart_lang.jni;

import java.lang.reflect.*;

public class PortProxy implements InvocationHandler {
static {
System.loadLibrary("dartjni");
}

private final long port;
private final long threadId;
private final long functionPtr;

private PortProxy(long port, long threadId, long functionPtr) {
this.port = port;
this.threadId = threadId;
this.functionPtr = functionPtr;
}

private static String getDescriptor(Method method) {
StringBuilder descriptor = new StringBuilder();
descriptor.append(method.getName()).append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> paramType : parameterTypes) {
appendType(descriptor, paramType);
}
descriptor.append(")");
appendType(descriptor, method.getReturnType());
return descriptor.toString();
}

private static void appendType(StringBuilder descriptor, Class<?> type) {
if (type == void.class) {
descriptor.append("V");
} else if (type == boolean.class) {
descriptor.append("Z");
} else if (type == byte.class) {
descriptor.append("B");
} else if (type == char.class) {
descriptor.append("C");
} else if (type == short.class) {
descriptor.append("S");
} else if (type == int.class) {
descriptor.append("I");
} else if (type == long.class) {
descriptor.append("J");
} else if (type == float.class) {
descriptor.append("F");
} else if (type == double.class) {
descriptor.append("D");
} else if (type.isArray()) {
descriptor.append('[');
appendType(descriptor, type.getComponentType());
} else {
descriptor.append("L").append(type.getName().replace('.', '/')).append(";");
}
}

public static Object newInstance(String binaryName, long port, long threadId, long functionPtr)
throws ClassNotFoundException {
Class clazz = Class.forName(binaryName);
return Proxy.newProxyInstance(
clazz.getClassLoader(), new Class[] {clazz}, new PortProxy(port, threadId, functionPtr));
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return _invoke(port, threadId, functionPtr, proxy, getDescriptor(method), args);
}

private native Object _invoke(
long port,
long threadId,
long functionPtr,
Object proxy,
String methodDescriptor,
Object[] args);
}
3 changes: 2 additions & 1 deletion jni/lib/internal_helpers_for_jnigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/// not to be used directly.
library internal_helpers_for_jnigen;

export 'src/accessors.dart';
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
export 'src/jni.dart' show ProtectedJniExtensions;
export 'src/jreference.dart';
export 'src/accessors.dart';
export 'src/method_invocation.dart';
4 changes: 4 additions & 0 deletions jni/lib/jni_symbols.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ files:
name: JBoolean
type_class: JBooleanType
super_count: 1
'java.lang.Character':
name: JCharacter
type_class: JCharacterType
super_count: 1
'java.util.Set':
name: JSet
type_class: JSetType
Expand Down
35 changes: 30 additions & 5 deletions jni/lib/src/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,6 @@ abstract class Jni {

static final accessors = JniAccessors(_bindings.GetAccessors());

/// Returns a new PortContinuation.
static JObjectPtr newPortContinuation(ReceivePort port) {
return _bindings.PortContinuation__ctor(port.sendPort.nativePort).object;
}

/// Returns current application context on Android.
static JObjectPtr getCachedApplicationContext() {
return _bindings.GetApplicationContext();
Expand Down Expand Up @@ -301,6 +296,36 @@ extension ProtectedJniExtensions on Jni {
final lookup = dl.lookup;
return lookup;
}

/// Returns a new PortContinuation.
static JObjectPtr newPortContinuation(ReceivePort port) {
return Jni._bindings
.PortContinuation__ctor(port.sendPort.nativePort)
.object;
}

/// Returns a new PortProxy for a class with the given [binaryName].
static JObjectPtr newPortProxy(
String binaryName,
ReceivePort port,
Pointer<
NativeFunction<
Pointer<Void> Function(Uint64, Pointer<Void>, Pointer<Void>)>>
functionPtr) {
return Jni._bindings
.PortProxy__newInstance(
Jni.env.toJStringPtr(binaryName),
port.sendPort.nativePort,
functionPtr.address,
)
.object;
}

/// Return the result of a callback..
static void returnResult(
Pointer<CallbackResult> result, JObjectPtr object) async {
Jni._bindings.resultFor(result, object);
}
}

extension AdditionalEnvMethods on GlobalJniEnv {
Expand Down
Loading