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 all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ A `*` denotes required configuration.
| `source_path` | List of directory paths | Directories to search for source files. Note: source_path for dependencies downloaded using `maven_downloads` configuration is added automatically without the need to specify here. |
| `class_path` | List of directory / JAR paths | Classpath for API summary generation. This should include any JAR dependencies of the source files in `source_path`. |
| `classes` * | List of qualified class / package names | List of qualified class / package names. `source_path` will be scanned assuming the sources follow standard java-ish hierarchy. That is a.b.c either maps to a directory `a/b/c` or a class file `a/b/c.java`. |
| `enable_experiment` | List of experiment names:<br><ul><li>`interface_implementation`</li></ul> | List of enabled experiments. These features are still in development and their API might break. |
| `output:` | (Subsection) | This subsection will contain configuration related to output files. |
| `output:` >> `bindings_type` | `c_based` (default) or `dart_only` | Binding generation strategy. [Trade-offs](#pure-dart-bindings) are explained at the end of this document. |
| `output:` >> `c:` | (Subsection) | This subsection specified C output configuration. Required if `bindings_type` is `c_based`. |
Expand Down
4 changes: 4 additions & 0 deletions jni/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.0-wip-2
* Added `PortProxy` and related methods used for interface implementation.
* Added the missing binding for `java.lang.Character`.

## 0.5.0
* **Breaking Change** ([#137](https://github.com/dart-lang/jnigen/issues/137)): Java primitive types are now all lowercase like `jint`, `jshort`, ...
* The bindings for `java.util.Set`, `java.util.Map`, `java.util.List` and the numeric types like `java.lang.Integer`, `java.lang.Boolean`, ... are now included in `package:jni`.
Expand Down
20 changes: 20 additions & 0 deletions jni/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,31 @@ rootProject.allprojects {
apply plugin: 'com.android.library'

android {
// Keeping the classes from being removed by proguard.
defaultConfig {
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
}
}

// Condition for namespace compatibility in AGP 8
if (project.android.hasProperty("namespace")) {
namespace 'com.github.dart_lang.jni'
}

// Adding [PortContinuation] and [PortProxy] classes shared between Flutter and
// Dart-standalone versions of package:jni.
sourceSets {
main {
java {
srcDirs '../java/src/main/java'
}
}
}

// Bumping the plugin compileSdkVersion requires all clients of this plugin
// to bump the version in their app.
compileSdkVersion 31
Expand Down
1 change: 1 addition & 0 deletions jni/android/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-keep class com.github.dart_lang.jni.** { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@

import android.app.Activity;
import android.content.Context;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.PluginRegistry.Registrar;

@Keep
public class JniPlugin implements FlutterPlugin, ActivityAware {

@Override
Expand Down

This file was deleted.

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
2 changes: 1 addition & 1 deletion jni/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.6.0-dev.1"
version: "0.6.0-dev.2"
js:
dependency: transitive
description:
Expand Down
28 changes: 20 additions & 8 deletions jni/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,33 @@ functions:
- 'setJniGetters'
- 'jni_log'
# Inline functions
# keep-sorted start
- 'acquire_lock'
- 'release_lock'
- 'init_lock'
- 'attach_thread'
- 'check_exception'
- 'destroy_cond'
- 'destroy_lock'
- 'init_cond'
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
- 'init_lock'
- 'load_class'
- 'load_class_global_ref'
- 'attach_thread'
- 'load_method'
- 'load_static_method'
- 'load_class_local_ref'
- 'load_class_platform'
- 'load_env'
- 'load_field'
- 'load_method'
- 'load_static_field'
- 'load_static_method'
- 'release_lock'
- 'signal_cond'
- 'thread_id'
- 'to_global_ref'
- 'check_exception'
- 'load_env'
# This is a native function in Java. No need to call it from Dart.
- 'to_global_ref_result'
- 'wait_for'
# keep-sorted end
# 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 +68,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
Loading
Loading