[jnigen] Interface implementation (https://github.com/dart-lang/jnigen/issues/326)
* attach finalizable to close the port for interfaces
* add missing JCharacter boxed type
* add .implement method completed for interfaces
* fix descriptor issues
* temporarily do not check the ffigen bindings until ffigenhttps://github.com/dart-lang/jnigen/issues/555 is solved
* remove duplicated java source in jni + sort ffigen.yaml inline functions
* add the interface implementation under an experiment flag + docs
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 6bcd562..4e5da23 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -188,11 +188,15 @@
flag-name: jni_tests
parallel: true
path-to-lcov: ./pkgs/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.
diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md
index 08d66d8..35a09db 100644
--- a/pkgs/jni/CHANGELOG.md
+++ b/pkgs/jni/CHANGELOG.md
@@ -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`.
diff --git a/pkgs/jni/android/build.gradle b/pkgs/jni/android/build.gradle
index 4f5ca1b..3f650e8 100644
--- a/pkgs/jni/android/build.gradle
+++ b/pkgs/jni/android/build.gradle
@@ -27,11 +27,31 @@
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
diff --git a/pkgs/jni/android/consumer-rules.pro b/pkgs/jni/android/consumer-rules.pro
new file mode 100644
index 0000000..269e421
--- /dev/null
+++ b/pkgs/jni/android/consumer-rules.pro
@@ -0,0 +1 @@
+-keep class com.github.dart_lang.jni.** { *; }
diff --git a/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java b/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java
index 2a22dd8..d3a1ebe 100644
--- a/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java
+++ b/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java
@@ -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
diff --git a/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/PortContinuation.java b/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/PortContinuation.java
deleted file mode 100644
index eb377e4..0000000
--- a/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/PortContinuation.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import kotlin.coroutines.Continuation;
-import kotlin.coroutines.CoroutineContext;
-import kotlinx.coroutines.Dispatchers;
-
-/// An implementation of kotlin.coroutines.Continuation which sends the address
-/// of the object to Dart through a native port.
-///
-/// This allows converting Kotlin coroutines to Dart async methods.
-/// The implementation of native void _resumeWith is located in `dartjni.c`.
-@Keep
-public class PortContinuation implements Continuation {
- static {
- System.loadLibrary("dartjni");
- }
-
- private long port;
-
- public PortContinuation(long port) {
- this.port = port;
- }
-
- @NonNull
- @Override
- public CoroutineContext getContext() {
- return (CoroutineContext) Dispatchers.getIO();
- }
-
- @Override
- public void resumeWith(Object o) {
- _resumeWith(port, o);
- }
-
- private native void _resumeWith(long port, Object result);
-}
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart
index 12ee6f9..7727cc1 100644
--- a/pkgs/jni/bin/setup.dart
+++ b/pkgs/jni/bin/setup.dart
@@ -93,11 +93,11 @@
}
}
-/// 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.");
@@ -106,7 +106,7 @@
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
@@ -170,7 +170,8 @@
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();
@@ -185,6 +186,28 @@
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()) {
@@ -194,20 +217,14 @@
}
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;
}
diff --git a/pkgs/jni/example/pubspec.lock b/pkgs/jni/example/pubspec.lock
index 34dff04..c730dba 100644
--- a/pkgs/jni/example/pubspec.lock
+++ b/pkgs/jni/example/pubspec.lock
@@ -200,7 +200,7 @@
path: ".."
relative: true
source: path
- version: "0.6.0-dev.1"
+ version: "0.6.0-dev.2"
js:
dependency: transitive
description:
diff --git a/pkgs/jni/ffigen.yaml b/pkgs/jni/ffigen.yaml
index 1704f43..62b7429 100644
--- a/pkgs/jni/ffigen.yaml
+++ b/pkgs/jni/ffigen.yaml
@@ -33,22 +33,33 @@
- 'setJniGetters'
- 'jni_log'
# Inline functions
+ # keep-sorted start
- 'acquire_lock'
- - 'release_lock'
- - 'init_lock'
+ - 'attach_thread'
+ - 'check_exception'
+ - 'destroy_cond'
- 'destroy_lock'
+ - 'init_cond'
+ - 'init_lock'
- 'load_class'
- 'load_class_global_ref'
- - 'attach_thread'
- - 'load_method'
- - 'load_static_method'
- - 'load_field'
- - 'load_static_field'
- - 'to_global_ref'
- - 'check_exception'
+ - 'load_class_local_ref'
+ - 'load_class_platform'
- 'load_env'
- # This is a native function in Java. No need to call it from Dart.
+ - 'load_field'
+ - 'load_method'
+ - 'load_static_field'
+ - 'load_static_method'
+ - 'release_lock'
+ - 'signal_cond'
+ - 'thread_id'
+ - 'to_global_ref'
+ - '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'
@@ -57,6 +68,7 @@
- '_JNIEnv'
- 'JNIInvokeInterface'
- '__va_list_tag'
+ - 'CallbackResult'
rename:
## opaque struct definitions, base types of jfieldID and jmethodID
'_jfieldID': 'jfieldID_'
diff --git a/pkgs/jni/java/pom.xml b/pkgs/jni/java/pom.xml
index 62f121e..2bd6f88 100644
--- a/pkgs/jni/java/pom.xml
+++ b/pkgs/jni/java/pom.xml
@@ -16,6 +16,7 @@
<build>
<finalName>jni</finalName>
+ <directory>${target}</directory>
</build>
<dependencies>
diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
new file mode 100644
index 0000000..a75a914
--- /dev/null
+++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
@@ -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);
+}
diff --git a/pkgs/jni/lib/internal_helpers_for_jnigen.dart b/pkgs/jni/lib/internal_helpers_for_jnigen.dart
index 7e1fc8e..257b0c2 100644
--- a/pkgs/jni/lib/internal_helpers_for_jnigen.dart
+++ b/pkgs/jni/lib/internal_helpers_for_jnigen.dart
@@ -6,6 +6,7 @@
/// not to be used directly.
library internal_helpers_for_jnigen;
+export 'src/accessors.dart';
export 'src/jni.dart' show ProtectedJniExtensions;
export 'src/jreference.dart';
-export 'src/accessors.dart';
+export 'src/method_invocation.dart';
diff --git a/pkgs/jni/lib/jni_symbols.yaml b/pkgs/jni/lib/jni_symbols.yaml
index 8b1f9ec..5159dc9 100644
--- a/pkgs/jni/lib/jni_symbols.yaml
+++ b/pkgs/jni/lib/jni_symbols.yaml
@@ -41,6 +41,10 @@
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
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart
index 0698b84..8145642 100644
--- a/pkgs/jni/lib/src/jni.dart
+++ b/pkgs/jni/lib/src/jni.dart
@@ -193,11 +193,6 @@
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();
@@ -301,6 +296,36 @@
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 {
diff --git a/pkgs/jni/lib/src/lang/jcharacter.dart b/pkgs/jni/lib/src/lang/jcharacter.dart
new file mode 100644
index 0000000..6ab76cf
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jcharacter.dart
@@ -0,0 +1,64 @@
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+class JCharacterType extends JObjType<JCharacter> {
+ const JCharacterType();
+
+ @override
+ String get signature => r"Ljava/lang/Character;";
+
+ @override
+ JCharacter fromRef(JObjectPtr ref) => JCharacter.fromRef(ref);
+
+ @override
+ JObjType get superType => const JObjectType();
+
+ @override
+ final superCount = 1;
+
+ @override
+ int get hashCode => (JCharacterType).hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ return other.runtimeType == (JCharacterType) && other is JCharacterType;
+ }
+}
+
+class JCharacter extends JObject {
+ @override
+ // ignore: overridden_fields
+ late final JObjType<JCharacter> $type = type;
+
+ JCharacter.fromRef(
+ JObjectPtr ref,
+ ) : super.fromRef(ref);
+
+ /// The type which includes information such as the signature of this class.
+ static const type = JCharacterType();
+
+ static final _class = Jni.findJClass(r"java/lang/Character");
+
+ static final _ctorId =
+ Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(C)V");
+
+ JCharacter(int c)
+ : super.fromRef(Jni.accessors.newObjectWithArgs(
+ _class.reference, _ctorId, [JValueChar(c)]).object);
+
+ static final _charValueId =
+ Jni.accessors.getMethodIDOf(_class.reference, r"charValue", r"()C");
+
+ int charValue({bool deleteOriginal = false}) {
+ final ret = Jni.accessors.callMethodWithArgs(
+ reference, _charValueId, JniCallType.charType, []).char;
+ if (deleteOriginal) {
+ delete();
+ }
+ return ret;
+ }
+}
diff --git a/pkgs/jni/lib/src/lang/jnumber.dart b/pkgs/jni/lib/src/lang/jnumber.dart
index 8461f10..cab3033 100644
--- a/pkgs/jni/lib/src/lang/jnumber.dart
+++ b/pkgs/jni/lib/src/lang/jnumber.dart
@@ -7,13 +7,14 @@
import '../jobject.dart';
import '../third_party/generated_bindings.dart';
import '../types.dart';
-import 'jinteger.dart';
-import 'jshort.dart';
-import 'jlong.dart';
+import 'jboolean.dart';
+import 'jbyte.dart';
+import 'jcharacter.dart';
import 'jdouble.dart';
import 'jfloat.dart';
-import 'jbyte.dart';
-import 'jboolean.dart';
+import 'jinteger.dart';
+import 'jlong.dart';
+import 'jshort.dart';
class JNumberType extends JObjType<JNumber> {
const JNumberType();
@@ -137,6 +138,7 @@
JShort toJShort() => JShort(this);
JInteger toJInteger() => JInteger(this);
JLong toJLong() => JLong(this);
+ JCharacter toJCharacter() => JCharacter(this);
}
extension DoubleToJava on double {
diff --git a/pkgs/jni/lib/src/lang/lang.dart b/pkgs/jni/lib/src/lang/lang.dart
index e4c1af7..786488c 100644
--- a/pkgs/jni/lib/src/lang/lang.dart
+++ b/pkgs/jni/lib/src/lang/lang.dart
@@ -4,6 +4,7 @@
export 'jboolean.dart';
export 'jbyte.dart';
+export 'jcharacter.dart';
export 'jdouble.dart';
export 'jfloat.dart';
export 'jinteger.dart';
diff --git a/pkgs/jni/lib/src/method_invocation.dart b/pkgs/jni/lib/src/method_invocation.dart
new file mode 100644
index 0000000..69cdc4a
--- /dev/null
+++ b/pkgs/jni/lib/src/method_invocation.dart
@@ -0,0 +1,38 @@
+// 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.
+
+import 'dart:ffi';
+
+import 'third_party/generated_bindings.dart';
+
+import 'lang/jstring.dart';
+import 'jarray.dart';
+import 'jobject.dart';
+
+class $MethodInvocation {
+ final Pointer<CallbackResult> result;
+ final JString methodDescriptor;
+ final JArray<JObject> args;
+
+ $MethodInvocation._(this.result, this.methodDescriptor, this.args);
+
+ factory $MethodInvocation.fromAddresses(
+ int resultAddress,
+ int descriptorAddress,
+ int argsAddress,
+ ) {
+ return $MethodInvocation._(
+ Pointer<CallbackResult>.fromAddress(resultAddress),
+ JString.fromRef(Pointer<Void>.fromAddress(descriptorAddress)),
+ JArray.fromRef(
+ const JObjectType(),
+ Pointer<Void>.fromAddress(argsAddress),
+ ),
+ );
+ }
+
+ factory $MethodInvocation.fromMessage(List<dynamic> message) {
+ return $MethodInvocation.fromAddresses(message[0], message[1], message[2]);
+ }
+}
diff --git a/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
index 7cb2bdb..ef83bb5 100644
--- a/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
+++ b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
@@ -178,6 +178,23 @@
late final _InitDartApiDL =
_InitDartApiDLPtr.asFunction<int Function(ffi.Pointer<ffi.Void>)>();
+ void resultFor(
+ ffi.Pointer<CallbackResult> result,
+ JObjectPtr object,
+ ) {
+ return _resultFor(
+ result,
+ object,
+ );
+ }
+
+ late final _resultForPtr = _lookup<
+ ffi.NativeFunction<
+ ffi.Void Function(
+ ffi.Pointer<CallbackResult>, JObjectPtr)>>('resultFor');
+ late final _resultFor = _resultForPtr
+ .asFunction<void Function(ffi.Pointer<CallbackResult>, JObjectPtr)>();
+
JniResult PortContinuation__ctor(
int j,
) {
@@ -192,6 +209,25 @@
late final _PortContinuation__ctor =
_PortContinuation__ctorPtr.asFunction<JniResult Function(int)>();
+ JniResult PortProxy__newInstance(
+ JObjectPtr binaryName,
+ int port,
+ int functionPtr,
+ ) {
+ return _PortProxy__newInstance(
+ binaryName,
+ port,
+ functionPtr,
+ );
+ }
+
+ late final _PortProxy__newInstancePtr = _lookup<
+ ffi.NativeFunction<
+ JniResult Function(
+ JObjectPtr, ffi.Int64, ffi.Int64)>>('PortProxy__newInstance');
+ late final _PortProxy__newInstance = _PortProxy__newInstancePtr.asFunction<
+ JniResult Function(JObjectPtr, int, int)>();
+
ffi.Pointer<GlobalJniEnvStruct> GetGlobalEnv() {
return _GetGlobalEnv();
}
@@ -1936,6 +1972,41 @@
external ffi.Pointer<ffi.Void> extraInfo;
}
+class CallbackResult extends ffi.Struct {
+ external MutexLock lock;
+
+ external ConditionVariable cond;
+
+ @ffi.Int()
+ external int ready;
+
+ external JObjectPtr object;
+}
+
+typedef MutexLock = pthread_mutex_t;
+typedef pthread_mutex_t = __darwin_pthread_mutex_t;
+typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t;
+
+class _opaque_pthread_mutex_t extends ffi.Struct {
+ @ffi.Long()
+ external int __sig;
+
+ @ffi.Array.multi([56])
+ external ffi.Array<ffi.Char> __opaque;
+}
+
+typedef ConditionVariable = pthread_cond_t;
+typedef pthread_cond_t = __darwin_pthread_cond_t;
+typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t;
+
+class _opaque_pthread_cond_t extends ffi.Struct {
+ @ffi.Long()
+ external int __sig;
+
+ @ffi.Array.multi([40])
+ external ffi.Array<ffi.Char> __opaque;
+}
+
class GlobalJniEnvStruct extends ffi.Struct {
external ffi.Pointer<ffi.Void> reserved0;
diff --git a/pkgs/jni/pubspec.yaml b/pkgs/jni/pubspec.yaml
index 0c1068a..8344249 100644
--- a/pkgs/jni/pubspec.yaml
+++ b/pkgs/jni/pubspec.yaml
@@ -1,6 +1,10 @@
+# Copyright (c) 2022, 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.
+
name: jni
description: Library to access JNI from dart and flutter
-version: 0.6.0-dev.1
+version: 0.6.0-wip.2
repository: https://github.com/dart-lang/jnigen/tree/main/jni
environment:
diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c
index 19a32eb..6f0fa08 100644
--- a/pkgs/jni/src/dartjni.c
+++ b/pkgs/jni/src/dartjni.c
@@ -574,10 +574,11 @@
jobject thiz,
jlong port,
jobject result) {
- Dart_CObject dartPtr;
- dartPtr.type = Dart_CObject_kInt64;
- dartPtr.value.as_int64 = (jlong)((*env)->NewGlobalRef(env, result));
- Dart_PostCObject_DL(port, &dartPtr);
+ attach_thread();
+ Dart_CObject c_post;
+ c_post.type = Dart_CObject_kInt64;
+ c_post.value.as_int64 = (jlong)((*env)->NewGlobalRef(env, result));
+ Dart_PostCObject_DL(port, &c_post);
}
// com.github.dart_lang.jni.PortContinuation
@@ -586,6 +587,7 @@
jmethodID _m_PortContinuation__ctor = NULL;
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j) {
+ attach_thread();
load_class_global_ref(&_c_PortContinuation,
"com/github/dart_lang/jni/PortContinuation");
if (_c_PortContinuation == NULL)
@@ -602,3 +604,88 @@
}
return (JniResult){.value = {.l = _result}, .exception = check_exception()};
}
+
+// com.github.dart_lang.jni.PortProxy
+jclass _c_PortProxy = NULL;
+
+jmethodID _m_PortProxy__newInstance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr) {
+ attach_thread();
+ load_class_global_ref(&_c_PortProxy, "com/github/dart_lang/jni/PortProxy");
+ if (_c_PortProxy == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_static_method(_c_PortProxy, &_m_PortProxy__newInstance, "newInstance",
+ "(Ljava/lang/String;JJJ)Ljava/lang/Object;");
+ if (_m_PortProxy__newInstance == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ jobject _result = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, _c_PortProxy, _m_PortProxy__newInstance, binaryName, port,
+ thread_id(), functionPtr);
+ return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+void resultFor(CallbackResult* result, jobject object) {
+ acquire_lock(&result->lock);
+ result->ready = 1;
+ result->object = object;
+ signal_cond(&result->cond);
+ release_lock(&result->lock);
+}
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args) {
+ attach_thread();
+ if (threadId != thread_id()) {
+ CallbackResult* result = (CallbackResult*)malloc(sizeof(CallbackResult));
+ 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;
+ c_result.value.as_int64 = (jlong)result;
+
+ Dart_CObject c_method;
+ c_method.type = Dart_CObject_kInt64;
+ c_method.value.as_int64 =
+ (jlong)((*env)->NewGlobalRef(env, methodDescriptor));
+
+ Dart_CObject c_args;
+ c_args.type = Dart_CObject_kInt64;
+ c_args.value.as_int64 = (jlong)((*env)->NewGlobalRef(env, args));
+
+ Dart_CObject* c_post_arr[] = {&c_result, &c_method, &c_args};
+ Dart_CObject c_post;
+ c_post.type = Dart_CObject_kArray;
+ c_post.value.as_array.values = c_post_arr;
+ c_post.value.as_array.length = sizeof(c_post_arr) / sizeof(c_post_arr[0]);
+
+ Dart_PostCObject_DL(port, &c_post);
+ while (!result->ready) {
+ wait_for(&result->cond, &result->lock);
+ }
+ release_lock(&result->lock);
+ destroy_lock(&result->lock);
+ destroy_cond(&result->cond);
+ jobject object = result->object;
+ free(result);
+ return object;
+ } else {
+ return ((jobject(*)(uint64_t, jobject, jobject))functionPtr)(
+ port, (*env)->NewGlobalRef(env, methodDescriptor),
+ (*env)->NewGlobalRef(env, args));
+ }
+}
\ No newline at end of file
diff --git a/pkgs/jni/src/dartjni.h b/pkgs/jni/src/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jni/src/dartjni.h
+++ b/pkgs/jni/src/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jni/test/boxed_test.dart b/pkgs/jni/test/boxed_test.dart
index 43489e8..88d8686 100644
--- a/pkgs/jni/test/boxed_test.dart
+++ b/pkgs/jni/test/boxed_test.dart
@@ -26,6 +26,13 @@
expect((-val).toJByte().byteValue(deleteOriginal: true), -val);
});
});
+ testRunner('JCharacter', () {
+ const val = 1 << 5;
+ using((arena) {
+ expect(JCharacter(val).charValue(deleteOriginal: true), val);
+ expect(JCharacter(0).charValue(deleteOriginal: true), 0);
+ });
+ });
testRunner('JShort', () {
const val = 1 << 10;
using((arena) {
@@ -79,6 +86,14 @@
expect(a.$type.hashCode, b.$type.hashCode);
});
});
+ testRunner('JCharacter.\$type hashCode and ==', () {
+ using((arena) {
+ final a = JCharacter(1)..deletedIn(arena);
+ final b = JCharacter(2)..deletedIn(arena);
+ expect(a.$type, b.$type);
+ expect(a.$type.hashCode, b.$type.hashCode);
+ });
+ });
testRunner('JShort.\$type hashCode and ==', () {
using((arena) {
final a = JShort(1)..deletedIn(arena);
diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md
index 48b1b21..8d2086e 100644
--- a/pkgs/jnigen/CHANGELOG.md
+++ b/pkgs/jnigen/CHANGELOG.md
@@ -1,8 +1,15 @@
-## 0.6.0-dev.1
+## 0.6.0-wip.2
+* Fixed a bug where the nested classes would be generated incorrectly depending on the backend used for generation.
+* Fixed a bug where ASM backend would produce the incorrect parent for multi-level nested classes.
+* Fixed a bug where the backends would produce different descriptors for the same method.
+* Added `enable_experiment` option to config.
+* Created an experiment called `interface_implementation` which creates a `.implement` method for interfaces, so you can implement them using Dart.
+
+## 0.6.0-wip.1
* **Breaking Change** Specifying a class always pulls in nested classes by default. If a nested class is specified in config, it will be an error.
* Save all `jnigen` logs to a file in `.dart_tool/jnigen/logs/`. This is useful for debugging.
-## 0.6.0-dev.0
+## 0.6.0-wip.0
* **Breaking Change** Removed `suspend_fun_to_async` flag from the config. It's now happening by default since we read the Kotlin's metadata and reliably identify the `suspend fun`s.
## 0.5.0
diff --git a/pkgs/jnigen/README.md b/pkgs/jnigen/README.md
index 8d47afd..d524e0e 100644
--- a/pkgs/jnigen/README.md
+++ b/pkgs/jnigen/README.md
@@ -218,6 +218,7 @@
| `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`. |
diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
index 028fa42..b14813f 100644
--- a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
+++ b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
@@ -12,6 +12,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -2600,18 +2601,14 @@
static jni.JString get SECURITY_PATCH =>
const jni.JStringType().fromRef(_get_SECURITY_PATCH().object);
- static final _ctor = jniLookup<
- ffi.NativeFunction<
- jni.JniResult Function(
- ffi.Pointer<ffi.Void>)>>("Build_VERSION__ctor")
- .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+ static final _ctor = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+ "Build_VERSION__ctor")
+ .asFunction<jni.JniResult Function()>();
- /// from: public void <init>(android.os.Build $parent)
+ /// from: public void <init>()
/// The returned object must be deleted after use, by calling the `delete` method.
- factory Build_VERSION(
- Build $parent,
- ) {
- return Build_VERSION.fromRef(_ctor($parent.reference).object);
+ factory Build_VERSION() {
+ return Build_VERSION.fromRef(_ctor().object);
}
}
@@ -2754,18 +2751,14 @@
/// from: static public final int TIRAMISU
static const TIRAMISU = 33;
- static final _ctor = jniLookup<
- ffi.NativeFunction<
- jni.JniResult Function(
- ffi.Pointer<ffi.Void>)>>("Build_VERSION_CODES__ctor")
- .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+ static final _ctor = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+ "Build_VERSION_CODES__ctor")
+ .asFunction<jni.JniResult Function()>();
- /// from: public void <init>(android.os.Build $parent)
+ /// from: public void <init>()
/// The returned object must be deleted after use, by calling the `delete` method.
- factory Build_VERSION_CODES(
- Build $parent,
- ) {
- return Build_VERSION_CODES.fromRef(_ctor($parent.reference).object);
+ factory Build_VERSION_CODES() {
+ return Build_VERSION_CODES.fromRef(_ctor().object);
}
}
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c b/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c
index c41ff6e..a6e491c 100644
--- a/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c
@@ -1440,17 +1440,16 @@
jmethodID _m_Build_VERSION__ctor = NULL;
FFI_PLUGIN_EXPORT
-JniResult Build_VERSION__ctor(jobject _parent) {
+JniResult Build_VERSION__ctor() {
load_env();
load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
if (_c_Build_VERSION == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
- load_method(_c_Build_VERSION, &_m_Build_VERSION__ctor, "<init>",
- "(Landroid/os/Build;)V");
+ load_method(_c_Build_VERSION, &_m_Build_VERSION__ctor, "<init>", "()V");
if (_m_Build_VERSION__ctor == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
- jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION,
- _m_Build_VERSION__ctor, _parent);
+ jobject _result =
+ (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION, _m_Build_VERSION__ctor);
return to_global_ref_result(_result);
}
@@ -1615,18 +1614,18 @@
jmethodID _m_Build_VERSION_CODES__ctor = NULL;
FFI_PLUGIN_EXPORT
-JniResult Build_VERSION_CODES__ctor(jobject _parent) {
+JniResult Build_VERSION_CODES__ctor() {
load_env();
load_class_global_ref(&_c_Build_VERSION_CODES,
"android/os/Build$VERSION_CODES");
if (_c_Build_VERSION_CODES == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
load_method(_c_Build_VERSION_CODES, &_m_Build_VERSION_CODES__ctor, "<init>",
- "(Landroid/os/Build;)V");
+ "()V");
if (_m_Build_VERSION_CODES__ctor == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION_CODES,
- _m_Build_VERSION_CODES__ctor, _parent);
+ _m_Build_VERSION_CODES__ctor);
return to_global_ref_result(_result);
}
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h b/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
index 78a7ded..e1002dd 100644
--- a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
+++ b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
@@ -12,6 +12,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -57,7 +58,8 @@
/// The returned object must be deleted after use, by calling the `delete` method.
Future<jni.JString> thinkBeforeAnswering() async {
final $p = ReceivePort();
- final $c = jni.JObject.fromRef(jni.Jni.newPortContinuation($p));
+ final $c =
+ jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
_thinkBeforeAnswering(reference, $c.reference).object;
final $o = jni.JObjectPtr.fromAddress(await $p.first);
final $k = const jni.JStringType().getClass().reference;
diff --git a/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h b/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h
+++ b/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/example/notification_plugin/lib/notifications.dart b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
index c95f3e6..170c720 100644
--- a/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
+++ b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
@@ -16,6 +16,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/example/notification_plugin/src/dartjni.h b/pkgs/jnigen/example/notification_plugin/src/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/example/notification_plugin/src/dartjni.h
+++ b/pkgs/jnigen/example/notification_plugin/src/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
index 614a23f..3e55252 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
@@ -30,6 +30,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
index 657f68a..87862ba 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
@@ -30,6 +30,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
index 9d6fe3a..306258c 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
@@ -30,6 +30,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h
+++ b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
index 6a536d3..33fecbd 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
@@ -8,6 +8,7 @@
import com.github.dart_lang.jnigen.apisummarizer.util.SkipException;
import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
import java.util.*;
+import java.util.stream.Collectors;
import org.objectweb.asm.*;
import org.objectweb.asm.signature.SignatureReader;
@@ -27,6 +28,9 @@
List<ClassDecl> visited = new ArrayList<>();
Stack<ClassDecl> visiting = new Stack<>();
+ /// Actual access for the inner classes as originally defined.
+ Map<String, Integer> actualAccess = new HashMap<>();
+
public AsmClassVisitor() {
super(AsmConstants.API);
}
@@ -42,9 +46,8 @@
var current = new ClassDecl();
visiting.push(current);
var type = Type.getObjectType(name);
- current.binaryName = type.getClassName();
- current.modifiers = TypeUtils.access(access);
- current.parentName = TypeUtils.parentName(type);
+ current.binaryName = name.replace('/', '.');
+ current.modifiers = TypeUtils.access(actualAccess.getOrDefault(current.binaryName, access));
current.declKind = TypeUtils.declKind(access);
current.superclass = TypeUtils.typeUsage(Type.getObjectType(superName), null);
current.interfaces =
@@ -57,6 +60,21 @@
}
@Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ var binaryName = name.replace('/', '.');
+ actualAccess.put(binaryName, access);
+ var alreadyVisitedInnerClass =
+ visited.stream()
+ .filter(decl -> decl.binaryName.equals(binaryName))
+ .collect(Collectors.toList());
+ // If the order of visit is outer first inner second.
+ // We still want to correct the modifiers.
+ if (!alreadyVisitedInnerClass.isEmpty()) {
+ alreadyVisitedInnerClass.get(0).modifiers = TypeUtils.access(access);
+ }
+ }
+
+ @Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
if (name.contains("$")) {
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java
index 9067f9e..5410acf 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java
@@ -7,11 +7,13 @@
import com.github.dart_lang.jnigen.apisummarizer.elements.Method;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeParam;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
+import java.util.ArrayList;
+import java.util.List;
import org.objectweb.asm.signature.SignatureVisitor;
public class AsmMethodSignatureVisitor extends SignatureVisitor {
private final Method method;
- private int paramIndex = -1;
+ private final List<TypeUsage> paramTypes = new ArrayList<>();
public AsmMethodSignatureVisitor(Method method) {
super(AsmConstants.API);
@@ -43,13 +45,22 @@
@Override
public SignatureVisitor visitReturnType() {
+ // Visiting params finished.
+ //
+ // In case of non-static inner class constructor, there is an extra parent parameter
+ // at the beginning which is not accounted for by the signature. So we fill the types
+ // for the last [paramTypes.size()] params.
+ int startIndex = method.params.size() - paramTypes.size();
+ for (int i = 0; i < paramTypes.size(); ++i) {
+ method.params.get(startIndex + i).type = paramTypes.get(i);
+ }
return new AsmTypeUsageSignatureVisitor(method.returnType);
}
@Override
public SignatureVisitor visitParameterType() {
- paramIndex++;
- return new AsmTypeUsageSignatureVisitor(method.params.get(paramIndex).type);
+ paramTypes.add(new TypeUsage());
+ return new AsmTypeUsageSignatureVisitor(paramTypes.get(paramTypes.size() - 1));
}
@Override
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java
index 391b831..485c442 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java
@@ -8,22 +8,19 @@
import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider;
-import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.ClassReader;
public class AsmSummarizer {
public static List<ClassDecl> run(List<InputStreamProvider> inputProviders) {
- List<ClassDecl> parsed = new ArrayList<>();
+ var visitor = new AsmClassVisitor();
for (var provider : inputProviders) {
var inputStream = provider.getInputStream();
var classReader = wrapCheckedException(ClassReader::new, inputStream);
- var visitor = new AsmClassVisitor();
classReader.accept(visitor, 0);
- parsed.addAll(visitor.getVisited());
provider.close();
}
- return parsed;
+ return visitor.getVisited();
}
}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java
index 3c52c10..6ddda45 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java
@@ -93,7 +93,8 @@
@Override
public void visitInnerClassType(String name) {
- super.visitInnerClassType(name);
- // TODO(#139) support nested generic classes
+ typeUsage.shorthand += "." + name;
+ ((TypeUsage.DeclaredType) typeUsage.type).binaryName += "$" + name;
+ ((TypeUsage.DeclaredType) typeUsage.type).simpleName = name;
}
}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java
index 521c02d..8f18565 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java
@@ -19,14 +19,6 @@
class TypeUtils {
- public static String parentName(Type type) {
- var className = type.getClassName();
- if (!className.contains("$")) {
- return null;
- }
- return className.split("\\$")[0];
- }
-
public static String simpleName(Type type) {
var internalName = type.getInternalName();
if (type.getInternalName().length() == 1) {
@@ -46,7 +38,8 @@
case OBJECT:
usage.kind = TypeUsage.Kind.DECLARED;
usage.type =
- new TypeUsage.DeclaredType(type.getClassName(), TypeUtils.simpleName(type), null);
+ new TypeUsage.DeclaredType(
+ type.getInternalName().replace('/', '.'), TypeUtils.simpleName(type), null);
break;
case ARRAY:
usage.kind = TypeUsage.Kind.ARRAY;
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java
index d0f2301..7ca8bb6 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java
@@ -40,10 +40,6 @@
throw new RuntimeException(
"Unexpected element kind " + e.getKind() + " on " + c.binaryName);
}
- var parent = e.getEnclosingElement();
- if (parent instanceof TypeElement) {
- c.parentName = env.elements.getBinaryName((TypeElement) parent).toString();
- }
c.javadoc = docComment(env.trees.getDocCommentTree(e));
c.typeParams = StreamUtil.map(e.getTypeParameters(), this::typeParam);
var superclass = e.getSuperclass();
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
index 231f0f5..746b913 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
@@ -27,7 +27,6 @@
*/
public String binaryName;
- public String parentName;
public List<TypeParam> typeParams = new ArrayList<>();
public List<Method> methods = new ArrayList<>();
public List<Field> fields = new ArrayList<>();
diff --git a/pkgs/jnigen/lib/src/bindings/c_generator.dart b/pkgs/jnigen/lib/src/bindings/c_generator.dart
index dedef76..79b07c8 100644
--- a/pkgs/jnigen/lib/src/bindings/c_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/c_generator.dart
@@ -11,72 +11,6 @@
import '../util/string_util.dart';
import 'visitor.dart';
-/// JVM representation of type signatures.
-///
-/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
-class Descriptor extends TypeVisitor<String> {
- const Descriptor();
-
- @override
- String visitArrayType(ArrayType node) {
- final inner = node.type.accept(this);
- return '[$inner';
- }
-
- @override
- String visitDeclaredType(DeclaredType node) {
- final internalName = node.binaryName.replaceAll('.', '/');
- return 'L$internalName;';
- }
-
- @override
- String visitPrimitiveType(PrimitiveType node) {
- return node.signature;
- }
-
- @override
- String visitTypeVar(TypeVar node) {
- // It should be possible to compute the erasure of a type
- // in parser itself.
- // TODO(#23): Use erasure of the type variable here.
- // This is just a (wrong) placeholder
- return super.visitTypeVar(node);
- }
-
- @override
- String visitWildcard(Wildcard node) {
- final extendsBound = node.extendsBound?.accept(this);
- return extendsBound ?? 'Ljava/lang/Object;';
- }
-
- @override
- String visitNonPrimitiveType(ReferredType node) {
- return "Ljava/lang/Object;";
- }
-}
-
-/// Generates JNI Method signatures.
-///
-/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
-/// Also see: [Descriptor]
-class MethodSignature extends Visitor<Method, String> {
- const MethodSignature();
-
- @override
- String visit(Method node) {
- final s = StringBuffer();
- s.write('(');
- s.write(node.params
- .map((param) => param.type)
- .accept(const Descriptor())
- .join());
- s.write(')');
- final returnType = node.returnType.accept(const Descriptor());
- s.write(returnType);
- return s.toString();
- }
-}
-
class CFieldName extends Visitor<Field, String> {
const CFieldName();
@@ -255,7 +189,7 @@
if (!node.isCtor && !node.isStatic) 'jobject self_',
...node.params.accept(const _CParamGenerator(addReturnType: true)),
].join(',');
- final jniSignature = node.accept(const MethodSignature());
+ final jniSignature = node.descriptor;
final ifStaticMethodID = node.isStatic ? 'static_' : '';
var javaReturnType = node.returnType.type;
@@ -357,7 +291,7 @@
$_loadEnvCall
${node.classDecl.accept(_CLoadClassGenerator())}
load_${ifStaticField}field($classVar, &$fieldVar, "$fieldName",
- "${node.type.accept(const Descriptor())}");
+ "${node.type.descriptor}");
$accessorStatements
}
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
index 2854e6b..3db7c0b 100644
--- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -7,6 +7,7 @@
import 'package:meta/meta.dart';
import '../config/config.dart';
+import '../config/experiments.dart';
import '../elements/elements.dart';
import '../logging/logging.dart';
import '../util/string_util.dart';
@@ -33,6 +34,7 @@
const _typeParamPrefix = '\$';
// Misc.
+const _protectedExtension = 'ProtectedJniExtensions';
const _classRef = '_class.reference';
const _env = '$_jni.Jni.env';
const _accessors = '$_jni.Jni.accessors';
@@ -128,7 +130,7 @@
// Auto-generated initialization code.
final $_ffi.Pointer<T> Function<T extends $_ffi.NativeType>(String sym) $_lookup =
- ProtectedJniExtensions.initGeneratedLibrary("${config.outputConfig.cConfig!.libraryName}");
+ $_protectedExtension.initGeneratedLibrary("${config.outputConfig.cConfig!.libraryName}");
''';
@@ -155,6 +157,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
''';
@@ -399,6 +402,119 @@
method.accept(methodGenerator);
}
+ // Experimental: Interface implementation
+ if (node.declKind == DeclKind.interfaceKind &&
+ (config.experiments?.contains(Experiment.interfaceImplementation) ??
+ false)) {
+ s.write('''
+ /// Maps a specific port to the implemented methods.
+ static final Map<int, Map<String, Function>> _\$methods = {};
+
+ /// Maps a specific port to the type parameters.
+ static final Map<int, Map<String, $_jType>> _\$types = {};
+
+ ReceivePort? _\$p;
+
+ static final Finalizer<ReceivePort> _\$finalizer = Finalizer((\$p) {
+ _\$methods.remove(\$p.sendPort.nativePort);
+ _\$types.remove(\$p.sendPort.nativePort);
+ \$p.close();
+ });
+
+ @override
+ void delete() {
+ _\$methods.remove(_\$p?.sendPort.nativePort);
+ _\$types.remove(_\$p?.sendPort.nativePort);
+ _\$p?.close();
+ _\$finalizer.detach(this);
+ super.delete();
+ }
+
+ static jni.JObjectPtr _\$invoke(
+ int port,
+ jni.JObjectPtr descriptor,
+ jni.JObjectPtr args,
+ ) {
+ return _\$invokeMethod(
+ port,
+ \$MethodInvocation.fromAddresses(
+ 0,
+ descriptor.address,
+ args.address,
+ ),
+ );
+ }
+
+ static final ffi.Pointer<
+ ffi.NativeFunction<
+ jni.JObjectPtr Function(
+ ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+ _\$invokePointer = ffi.Pointer.fromFunction(_\$invoke);
+
+ static ffi.Pointer<ffi.Void> _\$invokeMethod(
+ int \$p,
+ \$MethodInvocation \$i,
+ ) {
+ final \$d = \$i.methodDescriptor.toDartString(deleteOriginal: true);
+ final \$a = \$i.args;
+ ''');
+ final proxyMethodIf = _InterfaceMethodIf(resolver, s);
+ for (final method in node.methods) {
+ method.accept(proxyMethodIf);
+ }
+ s.write('''
+ return jni.nullptr;
+ }
+
+ factory $name.implement(
+''');
+ final typeClassesCall =
+ typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 3));
+ s.write(_encloseIfNotEmpty(
+ '{',
+ [
+ ...typeParams
+ .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'),
+ ...node.methods.accept(_InterfaceImplementArg(resolver))
+ ].join(_newLine(depth: 2)),
+ '}',
+ ));
+
+ s.write('''
+ ) {
+ final \$p = ReceivePort();
+ final \$x = $name.fromRef(
+ $typeClassesCall
+ $_protectedExtension.newPortProxy(
+ r"${node.binaryName}",
+ \$p,
+ _\$invokePointer,
+ ),
+ ).._\$p = \$p;
+ final \$a = \$p.sendPort.nativePort;
+ _\$types[\$a] = {};
+ _\$methods[\$a] = {};
+''');
+ final typeFiller = _InterfaceTypesFiller(s);
+ for (final typeParam in node.allTypeParams) {
+ typeParam.accept(typeFiller);
+ }
+ final methodFiller = _InterfaceMethodsFiller(s);
+ for (final method in node.methods) {
+ method.accept(methodFiller);
+ }
+ s.write('''
+ _\$finalizer.attach(\$x, \$p, detach: \$x);
+ \$p.listen((\$m) {
+ final \$i = \$MethodInvocation.fromMessage(\$m);
+ final \$r = _\$invokeMethod(\$p.sendPort.nativePort, \$i);
+ ProtectedJniExtensions.returnResult(\$i.result, \$r);
+ });
+ return \$x;
+ }
+ ''');
+ }
+
// End of Class definition
s.writeln('}');
@@ -491,7 +607,7 @@
String visitArrayType(ArrayType node) {
final innerType = node.type;
if (innerType.kind == Kind.primitive) {
- return '$_jArray<$_jni.${(innerType.type as PrimitiveType).jniType}>';
+ return '$_jArray<$_jni.j${(innerType.type as PrimitiveType).name}>';
}
return '$_jArray<${innerType.accept(this)}>';
}
@@ -565,14 +681,32 @@
/// Generates the type class.
class _TypeClassGenerator extends TypeVisitor<_TypeClass> {
final bool isConst;
+
+ /// Whether or not to return the equivalent boxed type class for primitives.
+ /// Only for interface implemetation.
+ final bool boxPrimitives;
+
+ /// Whether or not to find the correct type variable from the static map.
+ /// Only for interface implemetation.
+ final bool typeVarFromMap;
+
final Resolver resolver;
- _TypeClassGenerator(this.resolver, {this.isConst = true});
+ _TypeClassGenerator(
+ this.resolver, {
+ this.isConst = true,
+ this.boxPrimitives = false,
+ this.typeVarFromMap = false,
+ });
@override
_TypeClass visitArrayType(ArrayType node) {
- final innerTypeClass =
- node.type.accept(_TypeClassGenerator(resolver, isConst: false));
+ final innerTypeClass = node.type.accept(_TypeClassGenerator(
+ resolver,
+ isConst: false,
+ boxPrimitives: boxPrimitives,
+ typeVarFromMap: typeVarFromMap,
+ ));
final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : '';
return _TypeClass(
'$ifConst${_jArray}Type(${innerTypeClass.name})',
@@ -587,8 +721,12 @@
.toList();
// The ones that are declared.
- final definedTypeClasses =
- node.params.accept(_TypeClassGenerator(resolver, isConst: false));
+ final definedTypeClasses = node.params.accept(_TypeClassGenerator(
+ resolver,
+ isConst: false,
+ boxPrimitives: boxPrimitives,
+ typeVarFromMap: typeVarFromMap,
+ ));
// Can be const if all the type parameters are defined and each of them are
// also const.
@@ -628,11 +766,15 @@
@override
_TypeClass visitPrimitiveType(PrimitiveType node) {
final ifConst = isConst ? 'const ' : '';
- return _TypeClass('$ifConst$_jni.${node.jniType}Type()', true);
+ final name = boxPrimitives ? 'J${node.boxedName}' : 'j${node.name}';
+ return _TypeClass('$ifConst$_jni.${name}Type()', true);
}
@override
_TypeClass visitTypeVar(TypeVar node) {
+ if (typeVarFromMap) {
+ return _TypeClass('_\$types[\$p]!["${node.name}"]!', false);
+ }
return _TypeClass(node.name, false);
}
@@ -823,7 +965,7 @@
void writeDartOnlyAccessor(Field node) {
final name = node.finalName;
final ifStatic = node.isStatic ? 'Static' : '';
- final descriptor = node.type.accept(const Descriptor());
+ final descriptor = node.type.descriptor;
s.write('''
static final _id_$name =
$_accessors.get${ifStatic}FieldIDOf(
@@ -950,10 +1092,10 @@
void writeDartOnlyAccessor(Method node) {
final name = node.finalName;
final ifStatic = node.isStatic ? 'Static' : '';
- final signature = node.accept(const MethodSignature());
+ final descriptor = node.descriptor;
s.write('''
static final _id_$name = $_accessors.get${ifStatic}MethodIDOf(
- $_classRef, r"${node.name}", r"$signature");
+ $_classRef, r"${node.name}", r"$descriptor");
''');
}
@@ -1110,7 +1252,7 @@
s.write('''async {
$typeInference
final \$p = ReceivePort();
- final \$c = $_jObject.fromRef($_jni.Jni.newPortContinuation(\$p));
+ final \$c = $_jObject.fromRef($_protectedExtension.newPortContinuation(\$p));
$callExpr;
final \$o = $_jPointer.fromAddress(await \$p.first);
final \$k = $returnTypeClass.getClass().reference;
@@ -1367,3 +1509,129 @@
return {};
}
}
+
+/// The argument for .implement method of interfaces.
+class _InterfaceImplementArg extends Visitor<Method, String> {
+ final Resolver resolver;
+
+ _InterfaceImplementArg(this.resolver);
+
+ @override
+ String visit(Method node) {
+ final returnType = node.returnType.accept(_TypeGenerator(resolver));
+ final name = node.finalName;
+ final args = node.params.accept(_ParamDef(resolver)).join(', ');
+ return 'required $returnType Function($args) $name,';
+ }
+}
+
+/// The if statement to check which method has been called from the proxy class.
+class _InterfaceMethodIf extends Visitor<Method, void> {
+ final Resolver resolver;
+ final StringSink s;
+
+ _InterfaceMethodIf(this.resolver, this.s);
+
+ @override
+ void visit(Method node) {
+ final signature = node.javaSig;
+ final saveResult = node.returnType.name == 'void' ? '' : 'final \$r = ';
+ s.write('''
+ if (\$d == r"$signature") {
+ ${saveResult}_\$methods[\$p]![\$d]!(
+''');
+ for (var i = 0; i < node.params.length; ++i) {
+ node.params[i].accept(_InterfaceParamCast(resolver, s, paramIndex: i));
+ }
+ const returnBox = _InterfaceReturnBox();
+ s.write('''
+ );
+ return ${node.returnType.accept(returnBox)};
+ }
+''');
+ }
+}
+
+/// Generates casting to the correct parameter type from the list of JObject
+/// arguments received from the call to the proxy class.
+class _InterfaceParamCast extends Visitor<Param, void> {
+ final Resolver resolver;
+ final StringSink s;
+ final int paramIndex;
+
+ _InterfaceParamCast(
+ this.resolver,
+ this.s, {
+ required this.paramIndex,
+ });
+
+ @override
+ void visit(Param node) {
+ final typeClass = node.type
+ .accept(_TypeClassGenerator(
+ resolver,
+ boxPrimitives: true,
+ typeVarFromMap: true,
+ ))
+ .name;
+ s.write('\$a[$paramIndex].castTo($typeClass, deleteOriginal: true)');
+ if (node.type.kind == Kind.primitive) {
+ // Convert to Dart type.
+ final name = (node.type.type as PrimitiveType).name;
+ s.write('.${name}Value(deleteOriginal: true)');
+ }
+ s.writeln(',');
+ }
+}
+
+/// Boxes the returned primitive value into the correct Boxed type.
+/// Only returns the reference for non primitive types.
+/// Returns null for void.
+///
+/// For example `$r.toJInteger().reference` when the return type is `integer`.
+class _InterfaceReturnBox extends TypeVisitor<String> {
+ const _InterfaceReturnBox();
+
+ @override
+ String visitNonPrimitiveType(ReferredType node) {
+ return '\$r.reference';
+ }
+
+ @override
+ String visitPrimitiveType(PrimitiveType node) {
+ if (node.name == 'void') {
+ return '$_jni.nullptr';
+ }
+ return '$_jni.J${node.boxedName}(\$r).reference';
+ }
+}
+
+/// Fills the static _$types map with the correct type classes for the given
+/// port.
+class _InterfaceTypesFiller extends Visitor<TypeParam, void> {
+ final StringSink s;
+
+ _InterfaceTypesFiller(this.s);
+
+ @override
+ void visit(TypeParam node) {
+ s.write('''
+ _\$types[\$a]!["${node.name}"] = ${node.name};
+''');
+ }
+}
+
+/// Fills the static _$method map with the correct callbacks for the given
+/// port.
+class _InterfaceMethodsFiller extends Visitor<Method, void> {
+ final StringSink s;
+
+ _InterfaceMethodsFiller(this.s);
+
+ @override
+ void visit(Method node) {
+ s.write('''
+ _\$methods[\$a]![r"${node.javaSig}"] = ${node.finalName};
+''');
+ }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/descriptor.dart b/pkgs/jnigen/lib/src/bindings/descriptor.dart
new file mode 100644
index 0000000..f7d774a
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/descriptor.dart
@@ -0,0 +1,115 @@
+import '../elements/elements.dart';
+import 'visitor.dart';
+
+/// Adds the type and method descriptor to all methods in all of the classes.
+///
+/// ASM already fills the descriptor field for methods but doclet does not.
+class Descriptor extends Visitor<Classes, void> {
+ const Descriptor();
+
+ @override
+ void visit(Classes node) {
+ for (final classDecl in node.decls.values) {
+ classDecl.accept(const _ClassDescriptor());
+ }
+ }
+}
+
+class _ClassDescriptor extends Visitor<ClassDecl, void> {
+ const _ClassDescriptor();
+
+ @override
+ void visit(ClassDecl node) {
+ final methodDescriptor = MethodDescriptor(node.allTypeParams);
+ for (final method in node.methods) {
+ method.descriptor ??= method.accept(methodDescriptor);
+ }
+ final typeDescriptor = TypeDescriptor(node.allTypeParams);
+ for (final field in node.fields) {
+ field.type.descriptor = field.type.accept(typeDescriptor);
+ }
+ for (final interface in node.interfaces) {
+ interface.descriptor = interface.accept(typeDescriptor);
+ }
+ }
+}
+
+/// Generates JNI Method descriptor.
+///
+/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
+/// Also see: [TypeDescriptor]
+class MethodDescriptor extends Visitor<Method, String> {
+ final List<TypeParam> typeParams;
+
+ MethodDescriptor(this.typeParams);
+
+ @override
+ String visit(Method node) {
+ final s = StringBuffer();
+ final typeParamsIncludingThis = [...typeParams, ...node.typeParams];
+ final typeDescriptor = TypeDescriptor(typeParamsIncludingThis);
+ s.write('(');
+ for (final param in node.params) {
+ final desc = param.type.accept(typeDescriptor);
+ param.type.descriptor = desc;
+ s.write(desc);
+ }
+ s.write(')');
+ final returnTypeDesc =
+ node.returnType.accept(TypeDescriptor(typeParamsIncludingThis));
+ node.returnType.descriptor = returnTypeDesc;
+ s.write(returnTypeDesc);
+ return s.toString();
+ }
+}
+
+/// JVM representation of type signatures.
+///
+/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
+class TypeDescriptor extends TypeVisitor<String> {
+ final List<TypeParam> typeParams;
+
+ TypeDescriptor(this.typeParams);
+
+ @override
+ String visitArrayType(ArrayType node) {
+ final inner = node.type.accept(this);
+ return '[$inner';
+ }
+
+ @override
+ String visitDeclaredType(DeclaredType node) {
+ final internalName = node.binaryName.replaceAll('.', '/');
+ return 'L$internalName;';
+ }
+
+ @override
+ String visitPrimitiveType(PrimitiveType node) {
+ return node.signature;
+ }
+
+ @override
+ String visitTypeVar(TypeVar node) {
+ final typeParam = typeParams.lastWhere(
+ (typeParam) => typeParam.name == node.name,
+ orElse: () {
+ print('${node.name} was not found!');
+ throw 'error';
+ },
+ );
+ return typeParam.bounds.isEmpty
+ ? super.visitTypeVar(node)
+ : typeParam.bounds.first.accept(this);
+ }
+
+ @override
+ String visitWildcard(Wildcard node) {
+ final extendsBound = node.extendsBound?.accept(this);
+ return extendsBound ?? super.visitWildcard(node);
+ }
+
+ @override
+ String visitNonPrimitiveType(ReferredType node) {
+ return 'Ljava/lang/Object;';
+ }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/unnester.dart b/pkgs/jnigen/lib/src/bindings/unnester.dart
index 0052205..d9323df 100644
--- a/pkgs/jnigen/lib/src/bindings/unnester.dart
+++ b/pkgs/jnigen/lib/src/bindings/unnester.dart
@@ -10,6 +10,8 @@
/// Nested classes are not supported in Dart. So this "unnests" them into
/// separate classes.
class Unnester extends Visitor<Classes, void> {
+ const Unnester();
+
@override
void visit(node) {
final classProcessor = _ClassUnnester();
@@ -53,9 +55,14 @@
void visit(Method node) {
assert(!node.classDecl.isStatic);
assert(node.classDecl.isNested);
- if (node.isCtor || node.isStatic) {
+ // TODO(#319): Unnest the methods in APISummarizer itself.
+ // For now the nullity of [node.descriptor] identifies if the doclet
+ // backend was used and the method would potentially need "unnesting".
+ if ((node.isCtor || node.isStatic) && node.descriptor == null) {
// Non-static nested classes take an instance of their outer class as the
- // first parameter. This is not accounted for by the summarizer, so we
+ // first parameter.
+ //
+ // This is not accounted for by the **doclet** summarizer, so we
// manually add it as the first parameter.
final parentTypeParamCount = node.classDecl.allTypeParams.length -
node.classDecl.typeParams.length;
diff --git a/pkgs/jnigen/lib/src/config/config_types.dart b/pkgs/jnigen/lib/src/config/config_types.dart
index c1d166b..8004cee 100644
--- a/pkgs/jnigen/lib/src/config/config_types.dart
+++ b/pkgs/jnigen/lib/src/config/config_types.dart
@@ -13,6 +13,7 @@
import '../logging/logging.dart';
import '../util/find_package.dart';
import 'config_exception.dart';
+import 'experiments.dart';
import 'filters.dart';
import 'yaml_reader.dart';
@@ -117,22 +118,20 @@
String? androidExample;
}
-/// Additional options to pass to the summary generator component.
-class SummarizerOptions {
- SummarizerOptions(
- {this.extraArgs = const [], this.workingDirectory, this.backend});
- List<String> extraArgs;
- Uri? workingDirectory;
- String? backend;
+extension on String {
+ /// Converts the enum name from camelCase to snake_case.
+ String toSnakeCase() {
+ return splitMapJoin(
+ RegExp('[A-Z]'),
+ onMatch: (p) => '_${p[0]!.toLowerCase()}',
+ );
+ }
}
-/// Backend for reading summary of Java libraries
-enum SummarizerBackend {
- /// Generate Java API summaries using JARs in provided `classPath`s.
- asm,
-
- /// Generate Java API summaries using source files in provided `sourcePath`s.
- doclet,
+extension<T extends Enum> on Iterable<T> {
+ Map<String, T> valuesMap() {
+ return Map.fromEntries(map((e) => MapEntry(e.name.toSnakeCase(), e)));
+ }
}
T _getEnumValueFromString<T>(
@@ -145,6 +144,35 @@
return value;
}
+/// Additional options to pass to the summary generator component.
+class SummarizerOptions {
+ SummarizerOptions(
+ {this.extraArgs = const [], this.workingDirectory, this.backend});
+ List<String> extraArgs;
+ Uri? workingDirectory;
+ SummarizerBackend? backend;
+}
+
+/// Backend for reading summary of Java libraries
+enum SummarizerBackend {
+ /// Generate Java API summaries using JARs in provided `classPath`s.
+ asm,
+
+ /// Generate Java API summaries using source files in provided `sourcePath`s.
+ doclet,
+}
+
+SummarizerBackend? getSummarizerBackend(
+ String? name,
+ SummarizerBackend? defaultVal,
+) {
+ return _getEnumValueFromString(
+ SummarizerBackend.values.valuesMap(),
+ name,
+ defaultVal,
+ );
+}
+
void _ensureIsDirectory(String name, Uri path) {
if (!path.toFilePath().endsWith(Platform.pathSeparator)) {
throw ConfigException('$name must be a directory path. If using YAML '
@@ -155,31 +183,27 @@
enum OutputStructure { packageStructure, singleFile }
OutputStructure getOutputStructure(String? name, OutputStructure defaultVal) {
- const values = {
- 'package_structure': OutputStructure.packageStructure,
- 'single_file': OutputStructure.singleFile,
- };
- return _getEnumValueFromString(values, name, defaultVal);
+ return _getEnumValueFromString(
+ OutputStructure.values.valuesMap(),
+ name,
+ defaultVal,
+ );
}
enum BindingsType { cBased, dartOnly }
extension GetConfigString on BindingsType {
- static const _configStrings = {
- BindingsType.cBased: 'c_based',
- BindingsType.dartOnly: 'dart_only',
- };
String getConfigString() {
- return _configStrings[this]!;
+ return name.toSnakeCase();
}
}
BindingsType getBindingsType(String? name, BindingsType defaultVal) {
- const values = {
- 'c_based': BindingsType.cBased,
- 'dart_only': BindingsType.dartOnly,
- };
- return _getEnumValueFromString(values, name, defaultVal);
+ return _getEnumValueFromString(
+ BindingsType.values.valuesMap(),
+ name,
+ defaultVal,
+ );
}
class CCodeOutputConfig {
@@ -296,6 +320,7 @@
Config({
required this.outputConfig,
required this.classes,
+ this.experiments,
this.exclude,
this.sourcePath,
this.classPath,
@@ -324,6 +349,8 @@
/// name suffix is `.class`.
List<String> classes;
+ Set<Experiment?>? experiments;
+
/// Methods and fields to be excluded from generated bindings.
final BindingExclusions? exclude;
@@ -351,7 +378,7 @@
final MavenDownloads? mavenDownloads;
/// Additional options for the summarizer component.
- final SummarizerOptions? summarizerOptions;
+ SummarizerOptions? summarizerOptions;
/// List of dependencies.
final List<Uri>? imports;
@@ -415,6 +442,7 @@
);
}
final classDecl = ClassDecl(
+ declKind: DeclKind.classKind,
binaryName: binaryName,
)
..path = '$importPath/$filePath'
@@ -535,7 +563,7 @@
classes: must(prov.getStringList, [], _Props.classes),
summarizerOptions: SummarizerOptions(
extraArgs: prov.getStringList(_Props.summarizerArgs) ?? const [],
- backend: prov.getString(_Props.backend),
+ backend: getSummarizerBackend(prov.getString(_Props.backend), null),
workingDirectory: prov.getPath(_Props.summarizerWorkingDir),
),
exclude: BindingExclusions(
@@ -568,6 +596,12 @@
: null,
),
preamble: prov.getString(_Props.preamble),
+ experiments: prov
+ .getStringList(_Props.experiments)
+ ?.map(
+ (e) => Experiment.fromString(e),
+ )
+ .toSet(),
imports: prov.getPathList(_Props.import),
mavenDownloads: prov.hasValue(_Props.mavenDownloads)
? MavenDownloads(
@@ -632,6 +666,7 @@
static const excludeMethods = '$exclude.methods';
static const excludeFields = '$exclude.fields';
+ static const experiments = 'enable_experiment';
static const import = 'import';
static const outputConfig = 'output';
static const bindingsType = '$outputConfig.bindings_type';
diff --git a/pkgs/jnigen/lib/src/config/experiments.dart b/pkgs/jnigen/lib/src/config/experiments.dart
new file mode 100644
index 0000000..d0eb39d
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/experiments.dart
@@ -0,0 +1,38 @@
+// 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.
+
+class Experiment {
+ static const available = [
+ interfaceImplementation,
+ ];
+
+ static const interfaceImplementation = Experiment(
+ name: 'interface_implementation',
+ description: 'Enables generation of machinery for '
+ 'implementing Java interfaces in Dart.',
+ isExpired: false,
+ );
+
+ final String name;
+ final String description;
+ final bool isExpired;
+
+ const Experiment({
+ required this.name,
+ required this.description,
+ required this.isExpired,
+ });
+
+ factory Experiment.fromString(String s) {
+ final search = available.where((element) => element.name == s);
+ if (search.isEmpty) {
+ throw 'The experiment $s is not available in this version.';
+ }
+ final result = search.single;
+ if (result.isExpired) {
+ throw 'The experiment $s can no longer be used in this version. ';
+ }
+ return result;
+ }
+}
diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart
index ceaee01..020f05d 100644
--- a/pkgs/jnigen/lib/src/elements/elements.dart
+++ b/pkgs/jnigen/lib/src/elements/elements.dart
@@ -57,9 +57,9 @@
ClassDecl({
this.annotations = const [],
this.javadoc,
+ required this.declKind,
this.modifiers = const {},
required this.binaryName,
- this.parentName,
this.typeParams = const [],
this.methods = const [],
this.fields = const [],
@@ -77,8 +77,8 @@
final List<Annotation> annotations;
final KotlinClass? kotlinClass;
final JavaDocComment? javadoc;
+ final DeclKind declKind;
final String binaryName;
- final String? parentName;
List<TypeParam> typeParams;
List<Method> methods;
List<Field> fields;
@@ -171,7 +171,13 @@
bool get isObject => superCount == 0;
- bool get isNested => parentName != null;
+ @JsonKey(includeFromJson: false)
+ late final String? parentName = binaryName.contains(r'$')
+ ? binaryName.splitMapJoin(RegExp(r'\$[^$]+$'), onMatch: (_) => '')
+ : null;
+
+ @JsonKey(includeFromJson: false)
+ late final isNested = parentName != null;
}
@JsonEnum()
@@ -206,10 +212,14 @@
@JsonKey(name: "type")
final Map<String, dynamic> typeJson;
- /// Populated by in [TypeUsage.fromJson].
+ /// Populated by [TypeUsage.fromJson].
@JsonKey(includeFromJson: false)
late final ReferredType type;
+ /// Populated by [Descriptor].
+ @JsonKey(includeFromJson: false)
+ late String descriptor;
+
String get name => type.name;
// Since json_serializable doesn't directly support union types,
@@ -255,7 +265,7 @@
name: 'byte',
signature: 'B',
dartType: 'int',
- jniType: 'jbyte',
+ boxedName: 'Byte',
cType: 'int8_t',
ffiType: 'Int8',
),
@@ -263,7 +273,7 @@
name: 'short',
signature: 'S',
dartType: 'int',
- jniType: 'jshort',
+ boxedName: 'Short',
cType: 'int16_t',
ffiType: 'Int16',
),
@@ -271,7 +281,7 @@
name: 'char',
signature: 'C',
dartType: 'int',
- jniType: 'jchar',
+ boxedName: 'Character',
cType: 'uint16_t',
ffiType: 'Uint16',
),
@@ -279,7 +289,7 @@
name: 'int',
signature: 'I',
dartType: 'int',
- jniType: 'jint',
+ boxedName: 'Integer',
cType: 'int32_t',
ffiType: 'Int32',
),
@@ -287,7 +297,7 @@
name: 'long',
signature: 'J',
dartType: 'int',
- jniType: 'jlong',
+ boxedName: 'Long',
cType: 'int64_t',
ffiType: 'Int64',
),
@@ -295,7 +305,7 @@
name: 'float',
signature: 'F',
dartType: 'double',
- jniType: 'jfloat',
+ boxedName: 'Float',
cType: 'float',
ffiType: 'Float',
),
@@ -303,7 +313,7 @@
name: 'double',
signature: 'D',
dartType: 'double',
- jniType: 'jdouble',
+ boxedName: 'Double',
cType: 'double',
ffiType: 'Double',
),
@@ -311,7 +321,7 @@
name: 'boolean',
signature: 'Z',
dartType: 'bool',
- jniType: 'jboolean',
+ boxedName: 'Boolean',
cType: 'uint8_t',
ffiType: 'Uint8',
),
@@ -319,7 +329,7 @@
name: 'void',
signature: 'V',
dartType: 'void',
- jniType: 'jvoid', // Never used
+ boxedName: 'Void', // Not used.
cType: 'void',
ffiType: 'Void',
),
@@ -329,7 +339,7 @@
required this.name,
required this.signature,
required this.dartType,
- required this.jniType,
+ required this.boxedName,
required this.cType,
required this.ffiType,
});
@@ -339,7 +349,7 @@
final String signature;
final String dartType;
- final String jniType;
+ final String boxedName;
final String cType;
final String ffiType;
@@ -466,7 +476,8 @@
/// Can be used to match with [KotlinFunction]'s descriptor.
///
/// Can create a unique signature in combination with [name].
- final String? descriptor;
+ /// Populated either by the ASM backend or [Descriptor].
+ String? descriptor;
/// The [ClassDecl] where this method is defined.
///
@@ -489,12 +500,7 @@
TypeUsage? asyncReturnType;
@JsonKey(includeFromJson: false)
- late String javaSig = _javaSig();
-
- String _javaSig() {
- final paramNames = params.map((p) => p.type.name).join(', ');
- return '${returnType.name} $name($paramNames)';
- }
+ late final String javaSig = '$name$descriptor';
bool get isCtor => name == '<init>';
diff --git a/pkgs/jnigen/lib/src/elements/elements.g.dart b/pkgs/jnigen/lib/src/elements/elements.g.dart
index 4531342..dcd35dd 100644
--- a/pkgs/jnigen/lib/src/elements/elements.g.dart
+++ b/pkgs/jnigen/lib/src/elements/elements.g.dart
@@ -14,12 +14,12 @@
javadoc: json['javadoc'] == null
? null
: JavaDocComment.fromJson(json['javadoc'] as Map<String, dynamic>),
+ declKind: $enumDecode(_$DeclKindEnumMap, json['declKind']),
modifiers: (json['modifiers'] as List<dynamic>?)
?.map((e) => e as String)
.toSet() ??
const {},
binaryName: json['binaryName'] as String,
- parentName: json['parentName'] as String?,
typeParams: (json['typeParams'] as List<dynamic>?)
?.map((e) => TypeParam.fromJson(e as Map<String, dynamic>))
.toList() ??
@@ -48,6 +48,12 @@
: KotlinClass.fromJson(json['kotlinClass'] as Map<String, dynamic>),
);
+const _$DeclKindEnumMap = {
+ DeclKind.classKind: 'CLASS',
+ DeclKind.interfaceKind: 'INTERFACE',
+ DeclKind.enumKind: 'ENUM',
+};
+
TypeUsage _$TypeUsageFromJson(Map<String, dynamic> json) => TypeUsage(
shorthand: json['shorthand'] as String,
kind: $enumDecode(_$KindEnumMap, json['kind']),
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart
index 174828e..883ca85 100644
--- a/pkgs/jnigen/lib/src/generate_bindings.dart
+++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -7,6 +7,7 @@
import 'bindings/c_generator.dart';
import 'bindings/dart_generator.dart';
+import 'bindings/descriptor.dart';
import 'bindings/excluder.dart';
import 'bindings/kotlin_processor.dart';
import 'bindings/linker.dart';
@@ -39,7 +40,8 @@
classes.accept(Excluder(config));
classes.accept(KotlinProcessor());
await classes.accept(Linker(config));
- classes.accept(Unnester());
+ classes.accept(const Unnester());
+ classes.accept(const Descriptor());
classes.accept(Renamer(config));
final cBased = config.outputConfig.bindingsType == BindingsType.cBased;
diff --git a/pkgs/jnigen/lib/src/summary/summary.dart b/pkgs/jnigen/lib/src/summary/summary.dart
index 09c13f5..1706275 100644
--- a/pkgs/jnigen/lib/src/summary/summary.dart
+++ b/pkgs/jnigen/lib/src/summary/summary.dart
@@ -44,11 +44,7 @@
this.workingDirectory,
this.backend,
}) : sourcePaths = sourcePath ?? [],
- classPaths = classPath ?? [] {
- if (backend != null && !{'asm', 'doclet'}.contains(backend)) {
- throw ArgumentError('Supported backends: asm, doclet');
- }
- }
+ classPaths = classPath ?? [];
static const sourcePathsOption = '-s';
static const classPathsOption = '-c';
@@ -60,7 +56,7 @@
List<String> classes;
Uri? workingDirectory;
- String? backend;
+ SummarizerBackend? backend;
void addSourcePaths(List<Uri> paths) {
sourcePaths.addAll(paths);
@@ -91,7 +87,7 @@
_addPathParam(args, sourcePathsOption, sourcePaths);
_addPathParam(args, classPathsOption, classPaths);
if (backend != null) {
- args.addAll(['--backend', backend!]);
+ args.addAll(['--backend', backend!.name]);
}
args.addAll(extraArgs);
args.addAll(classes);
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml
index ee70430..07be3c8 100644
--- a/pkgs/jnigen/pubspec.yaml
+++ b/pkgs/jnigen/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: jnigen
-version: 0.6.0-dev.1
+version: 0.6.0-wip.2
description: Experimental generator for FFI+JNI bindings.
repository: https://github.com/dart-lang/jnigen/tree/main/jnigen
diff --git a/pkgs/jnigen/test/config_test.dart b/pkgs/jnigen/test/config_test.dart
index 4b6617b..ae2f987 100644
--- a/pkgs/jnigen/test/config_test.dart
+++ b/pkgs/jnigen/test/config_test.dart
@@ -35,6 +35,7 @@
equals(b.outputConfig.symbolsConfig?.path),
reason: "symbolsRoot");
expect(a.sourcePath, equals(b.sourcePath), reason: "sourcePath");
+ expect(a.experiments, equals(b.experiments), reason: "experiments");
expect(a.classPath, equals(b.classPath), reason: "classPath");
expect(a.preamble, equals(b.preamble), reason: "preamble");
final am = a.mavenDownloads;
diff --git a/pkgs/jnigen/test/descriptor_test.dart b/pkgs/jnigen/test/descriptor_test.dart
new file mode 100644
index 0000000..41ac58c
--- /dev/null
+++ b/pkgs/jnigen/test/descriptor_test.dart
@@ -0,0 +1,43 @@
+// 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.
+
+import 'package:jnigen/src/bindings/descriptor.dart';
+import 'package:jnigen/src/bindings/linker.dart';
+import 'package:jnigen/src/bindings/unnester.dart';
+import 'package:jnigen/src/config/config_types.dart';
+import 'package:jnigen/src/summary/summary.dart';
+import 'package:test/test.dart';
+
+import 'simple_package_test/generate.dart' as simple_package_test;
+import 'kotlin_test/generate.dart' as kotlin_test;
+import 'jackson_core_test/generate.dart' as jackson_core_test;
+import 'test_util/test_util.dart';
+
+void main() {
+ checkLocallyBuiltDependencies();
+ test('Method descriptor generation', timeout: const Timeout.factor(3),
+ () async {
+ final configGetters = [
+ simple_package_test.getConfig,
+ kotlin_test.getConfig,
+ jackson_core_test.getConfig
+ ];
+ for (final getConfig in configGetters) {
+ final config = getConfig();
+ config.summarizerOptions =
+ SummarizerOptions(backend: SummarizerBackend.asm);
+ final classes = await getSummary(config);
+ await classes.accept(Linker(config));
+ classes.accept(const Unnester());
+ for (final decl in classes.decls.values) {
+ // Checking if the descriptor from ASM matches the one generated by
+ // [MethodDescriptor].
+ final methodDescriptor = MethodDescriptor(decl.allTypeParams);
+ for (final method in decl.methods) {
+ expect(method.descriptor, method.accept(methodDescriptor));
+ }
+ }
+ }
+ });
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/generate.dart b/pkgs/jnigen/test/jackson_core_test/generate.dart
index 7077bfa..796a760 100644
--- a/pkgs/jnigen/test/jackson_core_test/generate.dart
+++ b/pkgs/jnigen/test/jackson_core_test/generate.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/src/config/experiments.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' hide equals;
@@ -43,7 +44,7 @@
jarDir: join(thirdPartyDir, 'jar'),
),
summarizerOptions: SummarizerOptions(
- backend: useAsm ? 'asm' : null,
+ backend: useAsm ? SummarizerBackend.asm : null,
),
preamble: jacksonPreamble,
outputConfig: OutputConfig(
@@ -69,17 +70,19 @@
],
logLevel: Level.INFO,
exclude: BindingExclusions(
- // TODO(#31): Remove field exclusions.
- fields: excludeAll<Field>([
- ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'],
- ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'],
- ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'],
- ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'],
- ]),
- // TODO(#159): Remove class exclusions.
- classes: ClassNameFilter.exclude(
- 'com.fasterxml.jackson.core.JsonFactoryBuilder',
- )),
+ // TODO(#31): Remove field exclusions.
+ fields: excludeAll<Field>([
+ ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'],
+ ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'],
+ ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'],
+ ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'],
+ ]),
+ // TODO(#159): Remove class exclusions.
+ classes: ClassNameFilter.exclude(
+ 'com.fasterxml.jackson.core.JsonFactoryBuilder',
+ ),
+ ),
+ experiments: {Experiment.interfaceImplementation},
);
return config;
}
diff --git a/pkgs/jnigen/test/jackson_core_test/jnigen.yaml b/pkgs/jnigen/test/jackson_core_test/jnigen.yaml
index 6041671..cf3b4db 100644
--- a/pkgs/jnigen/test/jackson_core_test/jnigen.yaml
+++ b/pkgs/jnigen/test/jackson_core_test/jnigen.yaml
@@ -43,3 +43,6 @@
// limitations under the License.
log_level: warning
+
+enable_experiment:
+ - interface_implementation
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c
index 74bf137..0f6b727 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c
@@ -3383,7 +3383,7 @@
if (_c_JsonParser == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
load_method(_c_JsonParser, &_m_JsonParser__readValueAsTree, "readValueAsTree",
- "()Ljava/lang/Object;");
+ "()Lcom/fasterxml/jackson/core/TreeNode;");
if (_m_JsonParser__readValueAsTree == NULL)
return (JniResult){.value = {.j = 0}, .exception = check_exception()};
jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
index 0b91b29..33f5139 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
index 1c60902..032db31 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
index fe8cf88..14ce836 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
index c58fb97..71a79f1 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
index 601b404..f978756 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -2463,7 +2464,9 @@
}
static final _id_readValueAsTree = jni.Jni.accessors.getMethodIDOf(
- _class.reference, r"readValueAsTree", r"()Ljava/lang/Object;");
+ _class.reference,
+ r"readValueAsTree",
+ r"()Lcom/fasterxml/jackson/core/TreeNode;");
/// from: public T readValueAsTree()
/// The returned object must be deleted after use, by calling the `delete` method.
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
index b6bc6e5..aac454b 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -29,6 +29,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h
+++ b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
index b8e33c2..3e65d3b 100644
--- a/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
+++ b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
@@ -16,6 +16,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -61,7 +62,8 @@
/// The returned object must be deleted after use, by calling the `delete` method.
Future<jni.JString> sayHello() async {
final $p = ReceivePort();
- final $c = jni.JObject.fromRef(jni.Jni.newPortContinuation($p));
+ final $c =
+ jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
_sayHello(reference, $c.reference).object;
final $o = jni.JObjectPtr.fromAddress(await $p.first);
final $k = const jni.JStringType().getClass().reference;
@@ -87,7 +89,8 @@
jni.JString string,
) async {
final $p = ReceivePort();
- final $c = jni.JObject.fromRef(jni.Jni.newPortContinuation($p));
+ final $c =
+ jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
_sayHello1(reference, string.reference, $c.reference).object;
final $o = jni.JObjectPtr.fromAddress(await $p.first);
final $k = const jni.JStringType().getClass().reference;
diff --git a/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
index a051008..e4ceec3 100644
--- a/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
+++ b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
@@ -16,6 +16,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -54,7 +55,8 @@
/// The returned object must be deleted after use, by calling the `delete` method.
Future<jni.JString> sayHello() async {
final $p = ReceivePort();
- final $c = jni.JObject.fromRef(jni.Jni.newPortContinuation($p));
+ final $c =
+ jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
jni.Jni.accessors.callMethodWithArgs(reference, _id_sayHello,
jni.JniCallType.objectType, [$c.reference]).object;
final $o = jni.JObjectPtr.fromAddress(await $p.first);
@@ -76,7 +78,8 @@
jni.JString string,
) async {
final $p = ReceivePort();
- final $c = jni.JObject.fromRef(jni.Jni.newPortContinuation($p));
+ final $c =
+ jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
jni.Jni.accessors.callMethodWithArgs(reference, _id_sayHello1,
jni.JniCallType.objectType, [string.reference, $c.reference]).object;
final $o = jni.JObjectPtr.fromAddress(await $p.first);
diff --git a/pkgs/jnigen/test/kotlin_test/generate.dart b/pkgs/jnigen/test/kotlin_test/generate.dart
index 11b89ea..6168667 100644
--- a/pkgs/jnigen/test/kotlin_test/generate.dart
+++ b/pkgs/jnigen/test/kotlin_test/generate.dart
@@ -63,7 +63,7 @@
structure: OutputStructure.singleFile,
),
),
- summarizerOptions: SummarizerOptions(backend: 'asm'),
+ summarizerOptions: SummarizerOptions(backend: SummarizerBackend.asm),
preamble: preamble,
);
return config;
diff --git a/pkgs/jnigen/test/kotlin_test/jni.jar b/pkgs/jnigen/test/kotlin_test/jni.jar
deleted file mode 100644
index 37a1bfc..0000000
--- a/pkgs/jnigen/test/kotlin_test/jni.jar
+++ /dev/null
Binary files differ
diff --git a/pkgs/jnigen/test/package_resolver_test.dart b/pkgs/jnigen/test/package_resolver_test.dart
index 8053b5a..a29e043 100644
--- a/pkgs/jnigen/test/package_resolver_test.dart
+++ b/pkgs/jnigen/test/package_resolver_test.dart
@@ -20,9 +20,11 @@
final resolver = Resolver(
importedClasses: {
'org.apache.pdfbox.pdmodel.PDDocument': ClassDecl(
+ declKind: DeclKind.classKind,
binaryName: 'org.apache.pdfbox.pdmodel.PDDocument',
)..path = 'package:pdfbox/pdfbox.dart',
'android.os.Process': ClassDecl(
+ declKind: DeclKind.classKind,
binaryName: 'android.os.Process',
)..path = 'package:android/os.dart',
},
@@ -70,8 +72,10 @@
test(
'resolve $binaryName',
() => expect(
- resolver
- .resolvePrefix(ClassDecl(binaryName: binaryName)..path = ''),
+ resolver.resolvePrefix(ClassDecl(
+ declKind: DeclKind.classKind,
+ binaryName: binaryName,
+ )..path = ''),
equals(testCase.expectedName)));
}
}
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h
index c0713af..cbb1f7f 100644
--- a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h
@@ -45,6 +45,7 @@
#include <windows.h>
typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
static inline void init_lock(MutexLock* lock) {
InitializeCriticalSection(lock);
@@ -58,15 +59,32 @@
LeaveCriticalSection(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
DeleteCriticalSection(lock);
}
-#elif defined __DARWIN__ || defined __LINUX__ || defined __ANDROID__ || \
+static inline void init_cond(ConditionVariable* cond) {
+ InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ DeleteCriticalSection(cond);
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \
defined __GNUC__
#include <pthread.h>
typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
static inline void init_lock(MutexLock* lock) {
pthread_mutex_init(lock, NULL);
@@ -80,16 +98,49 @@
pthread_mutex_unlock(lock);
}
-static inline void _destroyLock(MutexLock* lock) {
+static inline void destroy_lock(MutexLock* lock) {
pthread_mutex_destroy(lock);
}
+static inline void init_cond(ConditionVariable* cond) {
+ pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+ pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+ pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+ pthread_cond_destroy(cond);
+}
+
#else
-#error "No locking support; Possibly unsupported platform"
+#error "No locking/condition variable support; Possibly unsupported platform"
#endif
+static inline uint64_t thread_id() {
+#ifdef _WIN32
+ return GetCurrentThreadId();
+#elif defined __APPLE__
+ return pthread_mach_thread_np(pthread_self());
+#else
+ return pthread_self();
+#endif
+}
+
+typedef struct CallbackResult {
+ MutexLock lock;
+ ConditionVariable cond;
+ int ready;
+ jobject object;
+} CallbackResult;
+
typedef struct JniLocks {
MutexLock classLoadingLock;
MutexLock methodLoadingLock;
@@ -369,6 +420,8 @@
FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
+
JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
@@ -376,3 +429,18 @@
jobject result);
FFI_PLUGIN_EXPORT
JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+ int64_t port,
+ int64_t functionPtr);
+
+JNIEXPORT jobject JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+ jobject thiz,
+ jlong port,
+ jlong threadId,
+ jlong functionPtr,
+ jobject proxy,
+ jstring methodDescriptor,
+ jobjectArray args);
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c
index 6c0f3e2..9b6e5e1 100644
--- a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c
@@ -2310,6 +2310,158 @@
return to_global_ref_result(_result);
}
+// com.github.dart_lang.jnigen.interfaces.MyInterface
+jclass _c_MyInterface = NULL;
+
+jmethodID _m_MyInterface__voidCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__voidCallback(jobject self_, jobject s) {
+ load_env();
+ load_class_global_ref(&_c_MyInterface,
+ "com/github/dart_lang/jnigen/interfaces/MyInterface");
+ if (_c_MyInterface == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_method(_c_MyInterface, &_m_MyInterface__voidCallback, "voidCallback",
+ "(Ljava/lang/String;)V");
+ if (_m_MyInterface__voidCallback == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_MyInterface__voidCallback, s);
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyInterface__stringCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__stringCallback(jobject self_, jobject s) {
+ load_env();
+ load_class_global_ref(&_c_MyInterface,
+ "com/github/dart_lang/jnigen/interfaces/MyInterface");
+ if (_c_MyInterface == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_method(_c_MyInterface, &_m_MyInterface__stringCallback, "stringCallback",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ if (_m_MyInterface__stringCallback == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ jobject _result = (*jniEnv)->CallObjectMethod(
+ jniEnv, self_, _m_MyInterface__stringCallback, s);
+ return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterface__varCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__varCallback(jobject self_, jobject t) {
+ load_env();
+ load_class_global_ref(&_c_MyInterface,
+ "com/github/dart_lang/jnigen/interfaces/MyInterface");
+ if (_c_MyInterface == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_method(_c_MyInterface, &_m_MyInterface__varCallback, "varCallback",
+ "(Ljava/lang/Object;)Ljava/lang/Object;");
+ if (_m_MyInterface__varCallback == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+ _m_MyInterface__varCallback, t);
+ return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterface__manyPrimitives = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__manyPrimitives(jobject self_,
+ int32_t a,
+ uint8_t b,
+ uint16_t c,
+ double d) {
+ load_env();
+ load_class_global_ref(&_c_MyInterface,
+ "com/github/dart_lang/jnigen/interfaces/MyInterface");
+ if (_c_MyInterface == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_method(_c_MyInterface, &_m_MyInterface__manyPrimitives, "manyPrimitives",
+ "(IZCD)J");
+ if (_m_MyInterface__manyPrimitives == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ int64_t _result = (*jniEnv)->CallLongMethod(
+ jniEnv, self_, _m_MyInterface__manyPrimitives, a, b, c, d);
+ return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+jclass _c_MyInterfaceConsumer = NULL;
+
+jmethodID _m_MyInterfaceConsumer__ctor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__ctor() {
+ load_env();
+ load_class_global_ref(
+ &_c_MyInterfaceConsumer,
+ "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+ if (_c_MyInterfaceConsumer == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_method(_c_MyInterfaceConsumer, &_m_MyInterfaceConsumer__ctor, "<init>",
+ "()V");
+ if (_m_MyInterfaceConsumer__ctor == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ jobject _result = (*jniEnv)->NewObject(jniEnv, _c_MyInterfaceConsumer,
+ _m_MyInterfaceConsumer__ctor);
+ return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterfaceConsumer__consumeOnAnotherThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__consumeOnAnotherThread(jobject myInterface,
+ jobject s,
+ int32_t a,
+ uint8_t b,
+ uint16_t c,
+ double d,
+ jobject t) {
+ load_env();
+ load_class_global_ref(
+ &_c_MyInterfaceConsumer,
+ "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+ if (_c_MyInterfaceConsumer == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_static_method(_c_MyInterfaceConsumer,
+ &_m_MyInterfaceConsumer__consumeOnAnotherThread,
+ "consumeOnAnotherThread",
+ "(Lcom/github/dart_lang/jnigen/interfaces/"
+ "MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+ if (_m_MyInterfaceConsumer__consumeOnAnotherThread == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ (*jniEnv)->CallStaticVoidMethod(
+ jniEnv, _c_MyInterfaceConsumer,
+ _m_MyInterfaceConsumer__consumeOnAnotherThread, myInterface, s, a, b, c,
+ d, t);
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyInterfaceConsumer__consumeOnSameThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__consumeOnSameThread(jobject myInterface,
+ jobject s,
+ int32_t a,
+ uint8_t b,
+ uint16_t c,
+ double d,
+ jobject t) {
+ load_env();
+ load_class_global_ref(
+ &_c_MyInterfaceConsumer,
+ "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+ if (_c_MyInterfaceConsumer == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ load_static_method(_c_MyInterfaceConsumer,
+ &_m_MyInterfaceConsumer__consumeOnSameThread,
+ "consumeOnSameThread",
+ "(Lcom/github/dart_lang/jnigen/interfaces/"
+ "MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+ if (_m_MyInterfaceConsumer__consumeOnSameThread == NULL)
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+ (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_MyInterfaceConsumer,
+ _m_MyInterfaceConsumer__consumeOnSameThread,
+ myInterface, s, a, b, c, d, t);
+ return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
// com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
jclass _c_JsonSerializable_Case = NULL;
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
index d0ad47c..7cb0110 100644
--- a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
@@ -16,6 +16,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -2827,6 +2828,372 @@
}
}
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterface
+class MyInterface<$T extends jni.JObject> extends jni.JObject {
+ @override
+ late final jni.JObjType<MyInterface<$T>> $type = type(T);
+
+ final jni.JObjType<$T> T;
+
+ MyInterface.fromRef(
+ this.T,
+ jni.JObjectPtr ref,
+ ) : super.fromRef(ref);
+
+ /// The type which includes information such as the signature of this class.
+ static $MyInterfaceType<$T> type<$T extends jni.JObject>(
+ jni.JObjType<$T> T,
+ ) {
+ return $MyInterfaceType(
+ T,
+ );
+ }
+
+ static final _voidCallback = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(ffi.Pointer<ffi.Void>,
+ ffi.Pointer<ffi.Void>)>>("MyInterface__voidCallback")
+ .asFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+ /// from: public abstract void voidCallback(java.lang.String s)
+ void voidCallback(
+ jni.JString s,
+ ) {
+ return _voidCallback(reference, s.reference).check();
+ }
+
+ static final _stringCallback = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(ffi.Pointer<ffi.Void>,
+ ffi.Pointer<ffi.Void>)>>("MyInterface__stringCallback")
+ .asFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+ /// from: public abstract java.lang.String stringCallback(java.lang.String s)
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ jni.JString stringCallback(
+ jni.JString s,
+ ) {
+ return const jni.JStringType()
+ .fromRef(_stringCallback(reference, s.reference).object);
+ }
+
+ static final _varCallback = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(ffi.Pointer<ffi.Void>,
+ ffi.Pointer<ffi.Void>)>>("MyInterface__varCallback")
+ .asFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+ /// from: public abstract T varCallback(T t)
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ $T varCallback(
+ $T t,
+ ) {
+ return T.fromRef(_varCallback(reference, t.reference).object);
+ }
+
+ static final _manyPrimitives = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>,
+ ffi.Int32,
+ ffi.Uint8,
+ ffi.Uint16,
+ ffi.Double)>>("MyInterface__manyPrimitives")
+ .asFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>, int, int, int, double)>();
+
+ /// from: public abstract long manyPrimitives(int a, boolean b, char c, double d)
+ int manyPrimitives(
+ int a,
+ bool b,
+ int c,
+ double d,
+ ) {
+ return _manyPrimitives(reference, a, b ? 1 : 0, c, d).long;
+ }
+
+ /// Maps a specific port to the implemented methods.
+ static final Map<int, Map<String, Function>> _$methods = {};
+
+ /// Maps a specific port to the type parameters.
+ static final Map<int, Map<String, jni.JObjType>> _$types = {};
+
+ ReceivePort? _$p;
+
+ static final Finalizer<ReceivePort> _$finalizer = Finalizer(($p) {
+ _$methods.remove($p.sendPort.nativePort);
+ _$types.remove($p.sendPort.nativePort);
+ $p.close();
+ });
+
+ @override
+ void delete() {
+ _$methods.remove(_$p?.sendPort.nativePort);
+ _$types.remove(_$p?.sendPort.nativePort);
+ _$p?.close();
+ _$finalizer.detach(this);
+ super.delete();
+ }
+
+ static jni.JObjectPtr _$invoke(
+ int port,
+ jni.JObjectPtr descriptor,
+ jni.JObjectPtr args,
+ ) {
+ return _$invokeMethod(
+ port,
+ $MethodInvocation.fromAddresses(
+ 0,
+ descriptor.address,
+ args.address,
+ ),
+ );
+ }
+
+ static final ffi.Pointer<
+ ffi.NativeFunction<
+ jni.JObjectPtr Function(
+ ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+ _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+ static ffi.Pointer<ffi.Void> _$invokeMethod(
+ int $p,
+ $MethodInvocation $i,
+ ) {
+ final $d = $i.methodDescriptor.toDartString(deleteOriginal: true);
+ final $a = $i.args;
+ if ($d == r"voidCallback(Ljava/lang/String;)V") {
+ _$methods[$p]![$d]!(
+ $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
+ );
+ return jni.nullptr;
+ }
+ if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
+ );
+ return $r.reference;
+ }
+ if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0].castTo(_$types[$p]!["T"]!, deleteOriginal: true),
+ );
+ return $r.reference;
+ }
+ if ($d == r"manyPrimitives(IZCD)J") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0]
+ .castTo(const jni.JIntegerType(), deleteOriginal: true)
+ .intValue(deleteOriginal: true),
+ $a[1]
+ .castTo(const jni.JBooleanType(), deleteOriginal: true)
+ .booleanValue(deleteOriginal: true),
+ $a[2]
+ .castTo(const jni.JCharacterType(), deleteOriginal: true)
+ .charValue(deleteOriginal: true),
+ $a[3]
+ .castTo(const jni.JDoubleType(), deleteOriginal: true)
+ .doubleValue(deleteOriginal: true),
+ );
+ return jni.JLong($r).reference;
+ }
+ return jni.nullptr;
+ }
+
+ factory MyInterface.implement({
+ required jni.JObjType<$T> T,
+ required void Function(jni.JString s) voidCallback,
+ required jni.JString Function(jni.JString s) stringCallback,
+ required $T Function($T t) varCallback,
+ required int Function(int a, bool b, int c, double d) manyPrimitives,
+ }) {
+ final $p = ReceivePort();
+ final $x = MyInterface.fromRef(
+ T,
+ ProtectedJniExtensions.newPortProxy(
+ r"com.github.dart_lang.jnigen.interfaces.MyInterface",
+ $p,
+ _$invokePointer,
+ ),
+ ).._$p = $p;
+ final $a = $p.sendPort.nativePort;
+ _$types[$a] = {};
+ _$methods[$a] = {};
+ _$types[$a]!["T"] = T;
+ _$methods[$a]![r"voidCallback(Ljava/lang/String;)V"] = voidCallback;
+ _$methods[$a]![r"stringCallback(Ljava/lang/String;)Ljava/lang/String;"] =
+ stringCallback;
+ _$methods[$a]![r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;"] =
+ varCallback;
+ _$methods[$a]![r"manyPrimitives(IZCD)J"] = manyPrimitives;
+ _$finalizer.attach($x, $p, detach: $x);
+ $p.listen(($m) {
+ final $i = $MethodInvocation.fromMessage($m);
+ final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+ ProtectedJniExtensions.returnResult($i.result, $r);
+ });
+ return $x;
+ }
+}
+
+class $MyInterfaceType<$T extends jni.JObject>
+ extends jni.JObjType<MyInterface<$T>> {
+ final jni.JObjType<$T> T;
+
+ const $MyInterfaceType(
+ this.T,
+ );
+
+ @override
+ String get signature =>
+ r"Lcom/github/dart_lang/jnigen/interfaces/MyInterface;";
+
+ @override
+ MyInterface<$T> fromRef(jni.JObjectPtr ref) => MyInterface.fromRef(T, ref);
+
+ @override
+ jni.JObjType get superType => const jni.JObjectType();
+
+ @override
+ final superCount = 1;
+
+ @override
+ int get hashCode => Object.hash($MyInterfaceType, T);
+
+ @override
+ bool operator ==(Object other) {
+ return other.runtimeType == ($MyInterfaceType<$T>) &&
+ other is $MyInterfaceType<$T> &&
+ T == other.T;
+ }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+class MyInterfaceConsumer extends jni.JObject {
+ @override
+ late final jni.JObjType<MyInterfaceConsumer> $type = type;
+
+ MyInterfaceConsumer.fromRef(
+ jni.JObjectPtr ref,
+ ) : super.fromRef(ref);
+
+ /// The type which includes information such as the signature of this class.
+ static const type = $MyInterfaceConsumerType();
+ static final _ctor = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+ "MyInterfaceConsumer__ctor")
+ .asFunction<jni.JniResult Function()>();
+
+ /// from: public void <init>()
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ factory MyInterfaceConsumer() {
+ return MyInterfaceConsumer.fromRef(_ctor().object);
+ }
+
+ static final _consumeOnAnotherThread = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>,
+ ffi.Pointer<ffi.Void>,
+ ffi.Int32,
+ ffi.Uint8,
+ ffi.Uint16,
+ ffi.Double,
+ ffi.Pointer<ffi.Void>)>>(
+ "MyInterfaceConsumer__consumeOnAnotherThread")
+ .asFunction<
+ jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+ int, int, int, double, ffi.Pointer<ffi.Void>)>();
+
+ /// from: static public void consumeOnAnotherThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+ static void consumeOnAnotherThread<$T extends jni.JObject>(
+ MyInterface<$T> myInterface,
+ jni.JString s,
+ int a,
+ bool b,
+ int c,
+ double d,
+ $T t, {
+ jni.JObjType<$T>? T,
+ }) {
+ T ??= jni.lowestCommonSuperType([
+ t.$type,
+ (myInterface.$type as $MyInterfaceType).T,
+ ]) as jni.JObjType<$T>;
+ return _consumeOnAnotherThread(
+ myInterface.reference, s.reference, a, b ? 1 : 0, c, d, t.reference)
+ .check();
+ }
+
+ static final _consumeOnSameThread = jniLookup<
+ ffi.NativeFunction<
+ jni.JniResult Function(
+ ffi.Pointer<ffi.Void>,
+ ffi.Pointer<ffi.Void>,
+ ffi.Int32,
+ ffi.Uint8,
+ ffi.Uint16,
+ ffi.Double,
+ ffi.Pointer<ffi.Void>)>>(
+ "MyInterfaceConsumer__consumeOnSameThread")
+ .asFunction<
+ jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+ int, int, int, double, ffi.Pointer<ffi.Void>)>();
+
+ /// from: static public void consumeOnSameThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+ static void consumeOnSameThread<$T extends jni.JObject>(
+ MyInterface<$T> myInterface,
+ jni.JString s,
+ int a,
+ bool b,
+ int c,
+ double d,
+ $T t, {
+ jni.JObjType<$T>? T,
+ }) {
+ T ??= jni.lowestCommonSuperType([
+ t.$type,
+ (myInterface.$type as $MyInterfaceType).T,
+ ]) as jni.JObjType<$T>;
+ return _consumeOnSameThread(
+ myInterface.reference, s.reference, a, b ? 1 : 0, c, d, t.reference)
+ .check();
+ }
+}
+
+class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+ const $MyInterfaceConsumerType();
+
+ @override
+ String get signature =>
+ r"Lcom/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer;";
+
+ @override
+ MyInterfaceConsumer fromRef(jni.JObjectPtr ref) =>
+ MyInterfaceConsumer.fromRef(ref);
+
+ @override
+ jni.JObjType get superType => const jni.JObjectType();
+
+ @override
+ final superCount = 1;
+
+ @override
+ int get hashCode => ($MyInterfaceConsumerType).hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ return other.runtimeType == ($MyInterfaceConsumerType) &&
+ other is $MyInterfaceConsumerType;
+ }
+}
+
/// from: com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
class JsonSerializable_Case extends jni.JObject {
@override
diff --git a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
index 3394d3d..7befe1b 100644
--- a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
@@ -16,6 +16,7 @@
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
// ignore_for_file: unused_shown_name
import "dart:isolate" show ReceivePort;
@@ -2674,6 +2675,361 @@
}
}
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterface
+class MyInterface<$T extends jni.JObject> extends jni.JObject {
+ @override
+ late final jni.JObjType<MyInterface<$T>> $type = type(T);
+
+ final jni.JObjType<$T> T;
+
+ MyInterface.fromRef(
+ this.T,
+ jni.JObjectPtr ref,
+ ) : super.fromRef(ref);
+
+ static final _class =
+ jni.Jni.findJClass(r"com/github/dart_lang/jnigen/interfaces/MyInterface");
+
+ /// The type which includes information such as the signature of this class.
+ static $MyInterfaceType<$T> type<$T extends jni.JObject>(
+ jni.JObjType<$T> T,
+ ) {
+ return $MyInterfaceType(
+ T,
+ );
+ }
+
+ static final _id_voidCallback = jni.Jni.accessors.getMethodIDOf(
+ _class.reference, r"voidCallback", r"(Ljava/lang/String;)V");
+
+ /// from: public abstract void voidCallback(java.lang.String s)
+ void voidCallback(
+ jni.JString s,
+ ) {
+ return jni.Jni.accessors.callMethodWithArgs(reference, _id_voidCallback,
+ jni.JniCallType.voidType, [s.reference]).check();
+ }
+
+ static final _id_stringCallback = jni.Jni.accessors.getMethodIDOf(
+ _class.reference,
+ r"stringCallback",
+ r"(Ljava/lang/String;)Ljava/lang/String;");
+
+ /// from: public abstract java.lang.String stringCallback(java.lang.String s)
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ jni.JString stringCallback(
+ jni.JString s,
+ ) {
+ return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+ reference,
+ _id_stringCallback,
+ jni.JniCallType.objectType,
+ [s.reference]).object);
+ }
+
+ static final _id_varCallback = jni.Jni.accessors.getMethodIDOf(
+ _class.reference,
+ r"varCallback",
+ r"(Ljava/lang/Object;)Ljava/lang/Object;");
+
+ /// from: public abstract T varCallback(T t)
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ $T varCallback(
+ $T t,
+ ) {
+ return T.fromRef(jni.Jni.accessors.callMethodWithArgs(reference,
+ _id_varCallback, jni.JniCallType.objectType, [t.reference]).object);
+ }
+
+ static final _id_manyPrimitives = jni.Jni.accessors
+ .getMethodIDOf(_class.reference, r"manyPrimitives", r"(IZCD)J");
+
+ /// from: public abstract long manyPrimitives(int a, boolean b, char c, double d)
+ int manyPrimitives(
+ int a,
+ bool b,
+ int c,
+ double d,
+ ) {
+ return jni.Jni.accessors.callMethodWithArgs(
+ reference,
+ _id_manyPrimitives,
+ jni.JniCallType.longType,
+ [jni.JValueInt(a), b ? 1 : 0, jni.JValueChar(c), d]).long;
+ }
+
+ /// Maps a specific port to the implemented methods.
+ static final Map<int, Map<String, Function>> _$methods = {};
+
+ /// Maps a specific port to the type parameters.
+ static final Map<int, Map<String, jni.JObjType>> _$types = {};
+
+ ReceivePort? _$p;
+
+ static final Finalizer<ReceivePort> _$finalizer = Finalizer(($p) {
+ _$methods.remove($p.sendPort.nativePort);
+ _$types.remove($p.sendPort.nativePort);
+ $p.close();
+ });
+
+ @override
+ void delete() {
+ _$methods.remove(_$p?.sendPort.nativePort);
+ _$types.remove(_$p?.sendPort.nativePort);
+ _$p?.close();
+ _$finalizer.detach(this);
+ super.delete();
+ }
+
+ static jni.JObjectPtr _$invoke(
+ int port,
+ jni.JObjectPtr descriptor,
+ jni.JObjectPtr args,
+ ) {
+ return _$invokeMethod(
+ port,
+ $MethodInvocation.fromAddresses(
+ 0,
+ descriptor.address,
+ args.address,
+ ),
+ );
+ }
+
+ static final ffi.Pointer<
+ ffi.NativeFunction<
+ jni.JObjectPtr Function(
+ ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+ _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+ static ffi.Pointer<ffi.Void> _$invokeMethod(
+ int $p,
+ $MethodInvocation $i,
+ ) {
+ final $d = $i.methodDescriptor.toDartString(deleteOriginal: true);
+ final $a = $i.args;
+ if ($d == r"voidCallback(Ljava/lang/String;)V") {
+ _$methods[$p]![$d]!(
+ $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
+ );
+ return jni.nullptr;
+ }
+ if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
+ );
+ return $r.reference;
+ }
+ if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0].castTo(_$types[$p]!["T"]!, deleteOriginal: true),
+ );
+ return $r.reference;
+ }
+ if ($d == r"manyPrimitives(IZCD)J") {
+ final $r = _$methods[$p]![$d]!(
+ $a[0]
+ .castTo(const jni.JIntegerType(), deleteOriginal: true)
+ .intValue(deleteOriginal: true),
+ $a[1]
+ .castTo(const jni.JBooleanType(), deleteOriginal: true)
+ .booleanValue(deleteOriginal: true),
+ $a[2]
+ .castTo(const jni.JCharacterType(), deleteOriginal: true)
+ .charValue(deleteOriginal: true),
+ $a[3]
+ .castTo(const jni.JDoubleType(), deleteOriginal: true)
+ .doubleValue(deleteOriginal: true),
+ );
+ return jni.JLong($r).reference;
+ }
+ return jni.nullptr;
+ }
+
+ factory MyInterface.implement({
+ required jni.JObjType<$T> T,
+ required void Function(jni.JString s) voidCallback,
+ required jni.JString Function(jni.JString s) stringCallback,
+ required $T Function($T t) varCallback,
+ required int Function(int a, bool b, int c, double d) manyPrimitives,
+ }) {
+ final $p = ReceivePort();
+ final $x = MyInterface.fromRef(
+ T,
+ ProtectedJniExtensions.newPortProxy(
+ r"com.github.dart_lang.jnigen.interfaces.MyInterface",
+ $p,
+ _$invokePointer,
+ ),
+ ).._$p = $p;
+ final $a = $p.sendPort.nativePort;
+ _$types[$a] = {};
+ _$methods[$a] = {};
+ _$types[$a]!["T"] = T;
+ _$methods[$a]![r"voidCallback(Ljava/lang/String;)V"] = voidCallback;
+ _$methods[$a]![r"stringCallback(Ljava/lang/String;)Ljava/lang/String;"] =
+ stringCallback;
+ _$methods[$a]![r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;"] =
+ varCallback;
+ _$methods[$a]![r"manyPrimitives(IZCD)J"] = manyPrimitives;
+ _$finalizer.attach($x, $p, detach: $x);
+ $p.listen(($m) {
+ final $i = $MethodInvocation.fromMessage($m);
+ final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+ ProtectedJniExtensions.returnResult($i.result, $r);
+ });
+ return $x;
+ }
+}
+
+class $MyInterfaceType<$T extends jni.JObject>
+ extends jni.JObjType<MyInterface<$T>> {
+ final jni.JObjType<$T> T;
+
+ const $MyInterfaceType(
+ this.T,
+ );
+
+ @override
+ String get signature =>
+ r"Lcom/github/dart_lang/jnigen/interfaces/MyInterface;";
+
+ @override
+ MyInterface<$T> fromRef(jni.JObjectPtr ref) => MyInterface.fromRef(T, ref);
+
+ @override
+ jni.JObjType get superType => const jni.JObjectType();
+
+ @override
+ final superCount = 1;
+
+ @override
+ int get hashCode => Object.hash($MyInterfaceType, T);
+
+ @override
+ bool operator ==(Object other) {
+ return other.runtimeType == ($MyInterfaceType<$T>) &&
+ other is $MyInterfaceType<$T> &&
+ T == other.T;
+ }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+class MyInterfaceConsumer extends jni.JObject {
+ @override
+ late final jni.JObjType<MyInterfaceConsumer> $type = type;
+
+ MyInterfaceConsumer.fromRef(
+ jni.JObjectPtr ref,
+ ) : super.fromRef(ref);
+
+ static final _class = jni.Jni.findJClass(
+ r"com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+
+ /// The type which includes information such as the signature of this class.
+ static const type = $MyInterfaceConsumerType();
+ static final _id_ctor =
+ jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+ /// from: public void <init>()
+ /// The returned object must be deleted after use, by calling the `delete` method.
+ factory MyInterfaceConsumer() {
+ return MyInterfaceConsumer.fromRef(jni.Jni.accessors
+ .newObjectWithArgs(_class.reference, _id_ctor, []).object);
+ }
+
+ static final _id_consumeOnAnotherThread = jni.Jni.accessors.getStaticMethodIDOf(
+ _class.reference,
+ r"consumeOnAnotherThread",
+ r"(Lcom/github/dart_lang/jnigen/interfaces/MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+
+ /// from: static public void consumeOnAnotherThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+ static void consumeOnAnotherThread<$T extends jni.JObject>(
+ MyInterface<$T> myInterface,
+ jni.JString s,
+ int a,
+ bool b,
+ int c,
+ double d,
+ $T t, {
+ jni.JObjType<$T>? T,
+ }) {
+ T ??= jni.lowestCommonSuperType([
+ t.$type,
+ (myInterface.$type as $MyInterfaceType).T,
+ ]) as jni.JObjType<$T>;
+ return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+ _id_consumeOnAnotherThread, jni.JniCallType.voidType, [
+ myInterface.reference,
+ s.reference,
+ jni.JValueInt(a),
+ b ? 1 : 0,
+ jni.JValueChar(c),
+ d,
+ t.reference
+ ]).check();
+ }
+
+ static final _id_consumeOnSameThread = jni.Jni.accessors.getStaticMethodIDOf(
+ _class.reference,
+ r"consumeOnSameThread",
+ r"(Lcom/github/dart_lang/jnigen/interfaces/MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+
+ /// from: static public void consumeOnSameThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+ static void consumeOnSameThread<$T extends jni.JObject>(
+ MyInterface<$T> myInterface,
+ jni.JString s,
+ int a,
+ bool b,
+ int c,
+ double d,
+ $T t, {
+ jni.JObjType<$T>? T,
+ }) {
+ T ??= jni.lowestCommonSuperType([
+ t.$type,
+ (myInterface.$type as $MyInterfaceType).T,
+ ]) as jni.JObjType<$T>;
+ return jni.Jni.accessors.callStaticMethodWithArgs(
+ _class.reference, _id_consumeOnSameThread, jni.JniCallType.voidType, [
+ myInterface.reference,
+ s.reference,
+ jni.JValueInt(a),
+ b ? 1 : 0,
+ jni.JValueChar(c),
+ d,
+ t.reference
+ ]).check();
+ }
+}
+
+class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+ const $MyInterfaceConsumerType();
+
+ @override
+ String get signature =>
+ r"Lcom/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer;";
+
+ @override
+ MyInterfaceConsumer fromRef(jni.JObjectPtr ref) =>
+ MyInterfaceConsumer.fromRef(ref);
+
+ @override
+ jni.JObjType get superType => const jni.JObjectType();
+
+ @override
+ final superCount = 1;
+
+ @override
+ int get hashCode => ($MyInterfaceConsumerType).hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ return other.runtimeType == ($MyInterfaceConsumerType) &&
+ other is $MyInterfaceConsumerType;
+ }
+}
+
/// from: com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
class JsonSerializable_Case extends jni.JObject {
@override
diff --git a/pkgs/jnigen/test/simple_package_test/generate.dart b/pkgs/jnigen/test/simple_package_test/generate.dart
index 18c38e0..ce38009 100644
--- a/pkgs/jnigen/test/simple_package_test/generate.dart
+++ b/pkgs/jnigen/test/simple_package_test/generate.dart
@@ -4,6 +4,7 @@
import 'dart:io';
+import 'package:jnigen/src/config/experiments.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:jnigen/jnigen.dart';
@@ -31,6 +32,8 @@
join(javaPrefix, 'generics', 'StringStack.java'),
join(javaPrefix, 'generics', 'StringValuedMap.java'),
join(javaPrefix, 'generics', 'StringKeyedMap.java'),
+ join(javaPrefix, 'interfaces', 'MyInterface.java'),
+ join(javaPrefix, 'interfaces', 'MyInterfaceConsumer.java'),
join(javaPrefix, 'annotations', 'JsonSerializable.java'),
join(javaPrefix, 'annotations', 'MyDataClass.java'),
];
@@ -57,6 +60,7 @@
'com.github.dart_lang.jnigen.simple_package',
'com.github.dart_lang.jnigen.pkg2',
'com.github.dart_lang.jnigen.generics',
+ 'com.github.dart_lang.jnigen.interfaces',
'com.github.dart_lang.jnigen.annotations',
],
logLevel: Level.INFO,
@@ -72,6 +76,7 @@
),
),
preamble: preamble,
+ experiments: {Experiment.interfaceImplementation},
);
return config;
}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java
new file mode 100644
index 0000000..97b243e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java
@@ -0,0 +1,15 @@
+// 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.jnigen.interfaces;
+
+public interface MyInterface<T> {
+ void voidCallback(String s);
+
+ String stringCallback(String s);
+
+ T varCallback(T t);
+
+ long manyPrimitives(int a, boolean b, char c, double d);
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java
new file mode 100644
index 0000000..b4eeb2f
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java
@@ -0,0 +1,21 @@
+// 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.jnigen.interfaces;
+
+public class MyInterfaceConsumer {
+ public static <T> void consumeOnAnotherThread(
+ MyInterface<T> myInterface, String s, int a, boolean b, char c, double d, T t) {
+ var thread = new Thread(() -> consumeOnSameThread(myInterface, s, a, b, c, d, t));
+ thread.start();
+ }
+
+ public static <T> void consumeOnSameThread(
+ MyInterface<T> myInterface, String s, int a, boolean b, char c, double d, T t) {
+ String result = myInterface.stringCallback(s);
+ myInterface.voidCallback(result);
+ myInterface.manyPrimitives(a, b, c, d);
+ myInterface.varCallback(t);
+ }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
index f3075ac..cd6f999 100644
--- a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
+++ b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
@@ -2,6 +2,7 @@
// 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:async';
import 'dart:io';
import 'package:test/test.dart';
@@ -528,6 +529,74 @@
});
});
});
+
+ group('interface implementation', () {
+ for (final method in {
+ 'another thread': MyInterfaceConsumer.consumeOnAnotherThread,
+ 'the same thread': MyInterfaceConsumer.consumeOnSameThread,
+ }.entries) {
+ test('MyInterface.implement on ${method.key}', () async {
+ final voidCallbackResult = Completer<JString>();
+ final varCallbackResult = Completer<JInteger>();
+ final manyPrimitivesResult = Completer<int>();
+ // We can use this trick to access self, instead of generating a `thiz`
+ // or `self` argument for each one of the callbacks.
+ late final MyInterface<JInteger> myInterface;
+ myInterface = MyInterface.implement(
+ voidCallback: (s) {
+ voidCallbackResult.complete(s);
+ },
+ stringCallback: (s) {
+ return (s.toDartString(deleteOriginal: true) * 2).toJString();
+ },
+ varCallback: (JInteger t) {
+ final result = (t.intValue(deleteOriginal: true) * 2).toJInteger();
+ varCallbackResult.complete(result);
+ return result;
+ },
+ manyPrimitives: (a, b, c, d) {
+ if (b) {
+ final result = a + c + d.toInt();
+ manyPrimitivesResult.complete(result);
+ return result;
+ } else {
+ // Call self, add to [a] when [b] is false and change b to true.
+ return myInterface.manyPrimitives(a + 1, true, c, d);
+ }
+ },
+ T: JInteger.type,
+ );
+ // [stringCallback] is going to be called first using [s].
+ // The result of it is going to be used as the argument for
+ // [voidCallback].
+ // The other two methods will be called individually using the passed
+ // arguments afterwards.
+ method.value(
+ myInterface,
+ // For stringCallback:
+ 'hello'.toJString(),
+ // For manyPrimitives:
+ -1,
+ false,
+ 3,
+ 3.14,
+ // For varCallback
+ 7.toJInteger(),
+ );
+ final voidCallback = await voidCallbackResult.future;
+ expect(voidCallback.toDartString(deleteOriginal: true), 'hellohello');
+
+ final varCallback = await varCallbackResult.future;
+ expect(varCallback.intValue(), 14);
+
+ final manyPrimitives = await manyPrimitivesResult.future;
+ expect(manyPrimitives, -1 + 3 + 3.14.toInt() + 1);
+
+ myInterface.delete();
+ });
+ }
+ });
+
group('$groupName (load tests)', () {
const k4 = 4 * 1024; // This is a round number, unlike say 4000
const k256 = 256 * 1024;
diff --git a/pkgs/jnigen/test/test_util/bindings_test_setup.dart b/pkgs/jnigen/test/test_util/bindings_test_setup.dart
index b4d292c..c493ad1 100644
--- a/pkgs/jnigen/test/test_util/bindings_test_setup.dart
+++ b/pkgs/jnigen/test/test_util/bindings_test_setup.dart
@@ -19,7 +19,7 @@
final simplePackageTest = join('test', 'simple_package_test');
final jacksonCoreTest = join('test', 'jackson_core_test');
final kotlinTest = join('test', 'kotlin_test');
-final jniJar = join(kotlinTest, 'jni.jar');
+final jniJar = join('build', 'jni_libs', 'jni.jar');
final simplePackageTestJava = join(simplePackageTest, 'java');
final kotlinTestKotlin = join(kotlinTest, 'kotlin');