[jnigen] Initial JNI support (https://github.com/dart-lang/jnigen/issues/11)
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index aa9eccb..24e7d5e 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml
@@ -72,8 +72,56 @@ uses: coverallsapp/github-action@v1.1.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} + flag-name: jni_gen_tests + parallel: true path-to-lcov: ./pkgs/jni_gen/coverage/lcov.info + ## TODO: More minimal test on windows after fixing dev dependency. + ## i.e do not rerun analyze and format steps, and do not require flutter. + ## IssueRef: https://github.com/dart-lang/jni_gen/issues/15 + + test_jni: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./pkgs/jni + steps: + - uses: actions/checkout@v3 + ## Requires flutter to analyze example. + ## Using dart alone doesn't work. + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' + - run: | + sudo apt-get update -y + sudo apt-get install -y ninja-build libgtk-3-dev + - run: dart pub get + - run: dart run bin/setup.dart + - run: flutter pub get + - name: Check formatting + run: flutter format --output=none --set-exit-if-changed . + - name: Run lints + run: flutter analyze --fatal-infos + - name: Get dependencies + run: dart pub get + - name: Run tests + run: dart test + - name: Install coverage + run: dart pub global activate coverage + - name: Collect coverage + run: dart pub global run coverage:test_with_coverage + - name: Upload coverage + uses: coverallsapp/github-action@v1.1.2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + flag-name: jni_tests + parallel: true + path-to-lcov: ./pkgs/jni/coverage/lcov.info + build_jni_example_linux: runs-on: ubuntu-latest defaults: @@ -84,11 +132,20 @@ - uses: subosito/flutter-action@v2 with: channel: 'stable' + - uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' - run: | sudo apt-get update -y sudo apt-get install -y ninja-build libgtk-3-dev + - run: dart pub get + working-directory: ./pkgs/jni + - run: dart run bin/setup.dart + working-directory: ./pkgs/jni - run: flutter config --enable-linux-desktop - run: flutter pub get + - run: flutter test - run: flutter build linux build_jni_example_windows: @@ -101,25 +158,14 @@ - uses: subosito/flutter-action@v2 with: channel: 'stable' + - uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' - run: flutter config --enable-windows-desktop - run: flutter pub get - run: flutter build windows - build_jni_example_macos: - runs-on: macos-latest - defaults: - run: - working-directory: ./pkgs/jni/example - steps: - - uses: actions/checkout@v3 - - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - architecture: x64 - - run: flutter config --enable-macos-desktop - - run: flutter pub get - - run: flutter build macos - build_jni_example_android: runs-on: ubuntu-latest defaults: @@ -138,3 +184,13 @@ - run: flutter build apk - run: flutter build appbundle + coveralls_finish: + needs: [test_jni_gen, test_jni] + runs-on: ubuntu-latest + steps: + - name: Coveralls finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true +
diff --git a/pkgs/jni/README.md b/pkgs/jni/README.md index 45ee1cf..73f122a 100644 --- a/pkgs/jni/README.md +++ b/pkgs/jni/README.md
@@ -1,92 +1,37 @@ -# jni +# jni (experimental module) -A new Flutter FFI plugin project. +This is a utility library to access JNI from Dart / Flutter code, intended as a supplement for `jnigen` code generator, as well as provide the common base components (such as managing the JVM instance) to the code generated by `jni_gen`. -## Getting Started +This library contains: -This project is a starting point for a Flutter -[FFI plugin](https://docs.flutter.dev/development/platform-integration/c-interop), -a specialized package that includes native code directly invoked with Dart FFI. +* functions to access the JNIEnv and JavaVM variables from JNI, and wrapper functions to those provided by JNI. (`Jni.getEnv`, `Jni.getJavaVM`). -## Project stucture +* Functions to spawn a JVM on desktop platforms (`Jni.spawn`). -This template uses the following structure: +* Some utility functions to make it easier to work with JNI in Dart; eg: To convert a java string object to Dart string (mostly as extension methods on `Pointer<JniEnv>`). -* `src`: Contains the native source code, and a CmakeFile.txt file for building - that source code into a dynamic library. +* Some Android-specific helpers (get application context and current activity references). -* `lib`: Contains the Dart code that defines the API of the plugin, and which - calls into the native code using `dart:ffi`. +* Some helper classes and functions to simplify one-off uses (`JniObject` and `JniClass` intended for calling functions by specifying the name and arguments. It will reduce some boilerplate when you're debugging. Note: this API is slightly incomplete). -* platform folders (`android`, `ios`, `windows`, etc.): Contains the build files - for building and bundling the native code library with the platform application. +This is intended for one-off / debugging uses of JNI, as well as providing a base library for code generated by jni_gen. -## Buidling and bundling native code +__To interface a complete java library, look forward for `jni_gen`.__ -The `pubspec.yaml` specifies FFI plugins as follows: +## Platform support +The focus of this project is Flutter Android, since Flutter Android apps already have a JVM, and JNI enables interop with existing Java code and Android Platform APIs. This project also (partially) supports Linux desktop by spawning a JVM through JNI. -```yaml - plugin: - platforms: - some_platform: - ffiPlugin: true -``` +## Version note +This library is at an early stage of development and we do not provide backwards compatibility of the API at this point. -This configuration invokes the native build for the various target platforms -and bundles the binaries in Flutter applications using these FFI plugins. +## Documentation +The test/ directory contains files with comments explaining the basics of this module, and the example/ directory contains a flutter example which also touches some Android-specifics. -This can be combined with dartPluginClass, such as when FFI is used for the -implementation of one platform in a federated plugin: +Using this library assumes some familiarity with JNI - it's threading model and object references, among other things. -```yaml - plugin: - implements: some_other_plugin - platforms: - some_platform: - dartPluginClass: SomeClass - ffiPlugin: true -``` +## jni_gen -A plugin can have both FFI and method channels: +This library is a part of `jni_gen` - a 2022 GSoC project. -```yaml - plugin: - platforms: - some_platform: - pluginClass: SomeName - ffiPlugin: true -``` - -The native build systems that are invoked by FFI (and method channel) plugins are: - -* For Android: Gradle, which invokes the Android NDK for native builds. - * See the documentation in android/build.gradle. -* For iOS and MacOS: Xcode, via CocoaPods. - * See the documentation in ios/jni.podspec. - * See the documentation in macos/jni.podspec. -* For Linux and Windows: CMake. - * See the documentation in linux/CMakeLists.txt. - * See the documentation in windows/CMakeLists.txt. - -## Binding to native code - -To use the native code, bindings in Dart are needed. -To avoid writing these by hand, they are generated from the header file -(`src/jni.h`) by `package:ffigen`. -Regenerate the bindings by running `flutter pub run ffigen --config ffigen.yaml`. - -## Invoking native code - -Very short-running native functions can be directly invoked from any isolate. -For example, see `sum` in `lib/jni.dart`. - -Longer-running functions should be invoked on a helper isolate to avoid -dropping frames in Flutter applications. -For example, see `sumAsync` in `lib/jni.dart`. - -## Flutter help - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +The broader aim of jni_gen is making Java APIs accessible from dart in an idiomatic way.
diff --git a/pkgs/jni/analysis_options.yaml b/pkgs/jni/analysis_options.yaml index a5744c1..89f8ee9 100644 --- a/pkgs/jni/analysis_options.yaml +++ b/pkgs/jni/analysis_options.yaml
@@ -1,4 +1,13 @@ include: package:flutter_lints/flutter.yaml +analyzer: + exclude: [build/**] + language: + strict-raw-types: true + +linter: + rules: + - prefer_final_locals + - prefer_const_declarations # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java new file mode 100644 index 0000000..dbf8727 --- /dev/null +++ b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java
@@ -0,0 +1,61 @@ +package dev.dart.jni; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import android.util.Log; +import android.app.Activity; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; + +import android.content.Context; + +@Keep +public class JniPlugin implements FlutterPlugin, ActivityAware { + + @Override + public void + onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + setup(binding.getApplicationContext()); + } + + public static void registerWith(Registrar registrar) { + JniPlugin plugin = new JniPlugin(); + plugin.setup(registrar.activeContext()); + } + + private void setup(Context context) { + initializeJni(context, getClass().getClassLoader()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} + + // Activity handling methods + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + Activity activity = binding.getActivity(); + setJniActivity(activity, activity.getApplicationContext()); + } + + @Override + public void onDetachedFromActivityForConfigChanges() {} + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + Activity activity = binding.getActivity(); + setJniActivity(activity, activity.getApplicationContext()); + } + + @Override + public void onDetachedFromActivity() {} + + native void initializeJni(Context context, ClassLoader classLoader); + native void setJniActivity(Activity activity, Context context); + + static { + System.loadLibrary("dartjni"); + } +} +
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart new file mode 100644 index 0000000..508f5e2 --- /dev/null +++ b/pkgs/jni/bin/setup.dart
@@ -0,0 +1,163 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:package_config/package_config.dart'; + +const _buildDir = "build-dir"; +const _srcDir = "source-dir"; +const _verbose = "verbose"; +const _cmakeArgs = "cmake-args"; +const _clean = "clean"; + +// Sets up input output channels and maintains state. +class CommandRunner { + CommandRunner({this.printCmds = false}); + bool printCmds = false; + int? time; + // TODO: time commands + // TODO: Run all commands in single shell instance + // IssueRef: https://github.com/dart-lang/jni_gen/issues/14 + Future<CommandRunner> run( + String exec, List<String> args, String workingDir) async { + if (printCmds) { + final cmd = "$exec ${args.join(" ")}"; + stderr.writeln("\n+ [$workingDir] $cmd"); + } + final process = await Process.start(exec, args, + workingDirectory: workingDir, + runInShell: Platform.isWindows, + mode: ProcessStartMode.inheritStdio); + final exitCode = await process.exitCode; + if (exitCode != 0) { + stderr.writeln("command exited with $exitCode"); + } + return this; + } +} + +class Options { + Options(ArgResults arg) + : buildDir = arg[_buildDir], + srcDir = arg[_srcDir], + cmakeArgs = arg[_cmakeArgs], + verbose = arg[_verbose] ?? false, + clean = arg[_clean] ?? false; + + String? buildDir, srcDir, cmakeArgs; + bool verbose, clean; +} + +late Options options; +void log(String msg) { + if (options.verbose) { + stderr.writeln(msg); + } +} + +/// tries to find package:jni's source folder in pub cache +/// if not possible, returns null. +Future<String?> findSources() async { + final packageConfig = await findPackageConfig(Directory.current); + if (packageConfig == null) { + return null; + } + final packages = packageConfig.packages; + for (var package in packages) { + if (package.name == 'jni') { + return package.root.resolve("src/").toFilePath(); + } + } + return null; +} + +void main(List<String> arguments) async { + final parser = ArgParser() + ..addOption(_buildDir, + abbr: 'B', help: 'Directory to place built artifacts') + ..addOption(_srcDir, + abbr: 'S', help: 'alternative path to package:jni sources') + ..addFlag(_verbose, abbr: 'v', help: 'Enable verbose output') + ..addFlag(_clean, + negatable: false, + abbr: 'C', + help: 'Clear built artifacts instead of running a build') + ..addOption(_cmakeArgs, + abbr: 'm', + help: 'additional space separated arguments to pass to CMake'); + final cli = parser.parse(arguments); + options = Options(cli); + final rest = cli.rest; + + if (rest.isNotEmpty) { + stderr.writeln("one or more unrecognized arguments: $rest"); + stderr.writeln("usage: dart run jni:setup <options>"); + stderr.writeln(parser.usage); + exitCode = 1; + return; + } + + final srcPath = options.srcDir ?? await findSources(); + + if (srcPath == null) { + stderr.writeln("No sources specified and current directory is not a " + "package root."); + exitCode = 1; + return; + } + + final srcDir = Directory(srcPath); + if (!await srcDir.exists() && !options.clean) { + throw 'Directory $srcPath does not exist'; + } + + log("srcPath: $srcPath"); + + final currentDirUri = Uri.file("."); + final buildPath = + options.buildDir ?? currentDirUri.resolve("src/build").toFilePath(); + final buildDir = Directory(buildPath); + await buildDir.create(recursive: true); + log("buildPath: $buildPath"); + + if (buildDir.absolute.uri == srcDir.absolute.uri) { + stderr.writeln("Please build in a directory different than source."); + exit(2); + } + + if (options.clean) { + await cleanup(options, srcDir.absolute.path, buildDir.absolute.path); + } else { + // pass srcDir absolute path because it will be passed to CMake as arg + // which will be running in different directory + await build(options, srcDir.absolute.path, buildDir.path); + } +} + +Future<void> build(Options options, String srcPath, String buildPath) async { + final runner = CommandRunner(printCmds: true); + final cmakeArgs = <String>[]; + if (options.cmakeArgs != null) { + cmakeArgs.addAll(options.cmakeArgs!.split(" ")); + } + cmakeArgs.add(srcPath); + await runner.run("cmake", cmakeArgs, buildPath); + await runner.run("cmake", ["--build", "."], buildPath); + if (Platform.isWindows) { + await runner.run("move", ["Debug\\dartjni.dll", "."], buildPath); + } +} + +Future<void> cleanup(Options options, String srcPath, String buildPath) async { + if (srcPath == buildPath) { + stderr.writeln('Error: build path is same as source path.'); + } + + stderr.writeln("deleting $buildPath"); + + try { + await Directory(buildPath).delete(recursive: true); + } catch (e) { + stderr.writeln("Error: cannot be deleted"); + stderr.writeln(e); + } +}
diff --git a/pkgs/jni/example/analysis_options.yaml b/pkgs/jni/example/analysis_options.yaml index 61b6c4d..7f0b15f 100644 --- a/pkgs/jni/example/analysis_options.yaml +++ b/pkgs/jni/example/analysis_options.yaml
@@ -9,6 +9,11 @@ # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml +analyzer: + exclude: [build/**] + language: + strict-raw-types: true + linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` @@ -22,6 +27,8 @@ # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: + - prefer_final_locals + - prefer_const_declarations # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
diff --git a/pkgs/jni/example/android/app/build.gradle b/pkgs/jni/example/android/app/build.gradle index 468be13..ec13299 100644 --- a/pkgs/jni/example/android/app/build.gradle +++ b/pkgs/jni/example/android/app/build.gradle
@@ -43,7 +43,6 @@ } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "dev.dart.jni_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
diff --git a/pkgs/jni/example/android/app/src/main/java/dev/dart/jni_example/AnyToast.java b/pkgs/jni/example/android/app/src/main/java/dev/dart/jni_example/AnyToast.java new file mode 100644 index 0000000..59f07f7 --- /dev/null +++ b/pkgs/jni/example/android/app/src/main/java/dev/dart/jni_example/AnyToast.java
@@ -0,0 +1,28 @@ +package dev.dart.jni_example; + +import android.app.Activity; +import android.os.Handler; +import android.content.Context; +import android.widget.Toast; +import androidx.annotation.Keep; + +@Keep +class AnyToast { + static AnyToast makeText(Activity mainActivity, Context context, CharSequence text, int duration) { + AnyToast toast = new AnyToast(); + toast.mainActivity = mainActivity; + toast.context = context; + toast.text = text; + toast.duration = duration; + return toast; + } + + void show() { + mainActivity.runOnUiThread(() -> Toast.makeText(context, text, duration).show()); + } + + Activity mainActivity; + Context context; + CharSequence text; + int duration; +}
diff --git a/pkgs/jni/example/integration_test/jni_object_test.dart b/pkgs/jni/example/integration_test/jni_object_test.dart new file mode 100644 index 0000000..85086d9 --- /dev/null +++ b/pkgs/jni/example/integration_test/jni_object_test.dart
@@ -0,0 +1,227 @@ +import 'dart:io'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:ffi/ffi.dart'; + +import 'package:jni/jni.dart'; +import 'package:jni/jni_object.dart'; + +void main() { + if (!Platform.isAndroid) { + Jni.spawn(); + } + + final jni = Jni.getInstance(); + testWidgets('get JNI Version', (tester) async { + final env = jni.getEnv(); + expect(env.GetVersion(), isNot(equals(0))); + }); + + testWidgets('Manually lookup & call Long.toHexString static method', + (tester) async { + final arena = Arena(); + final env = jni.getEnv(); + final longClass = env.FindClass("java/lang/Long".toNativeChars(arena)); + final hexMethod = env.GetStaticMethodID( + longClass, + "toHexString".toNativeChars(arena), + "(J)Ljava/lang/String;".toNativeChars(arena)); + + for (var i in [1, 80, 13, 76, 1134453224145]) { + final jres = env.CallStaticObjectMethodA( + longClass, hexMethod, Jni.jvalues([JValueLong(i)], allocator: arena)); + + final res = env.asDartString(jres); + expect(res, equals(i.toRadixString(16))); + env.DeleteLocalRef(jres); + } + env.DeleteLocalRef(longClass); + arena.releaseAll(); + }); + + testWidgets("asJString extension method", (tester) async { + final env = jni.getEnv(); + const str = "QWERTY QWERTY"; + final jstr = env.asJString(str); + expect(str, equals(env.asDartString(jstr))); + env.DeleteLocalRef(jstr); + }); + + testWidgets("Convert back and forth between dart and java string", + (tester) async { + final arena = Arena(); + final env = jni.getEnv(); + const str = "ABCD EFGH"; + final jstr = env.NewStringUTF(str.toNativeChars(arena)); + final jchars = env.GetStringUTFChars(jstr, nullptr); + final dstr = jchars.toDartString(); + env.ReleaseStringUTFChars(jstr, jchars); + expect(str, equals(dstr)); + + env.deleteAllLocalRefs([jstr]); + arena.releaseAll(); + }); + + testWidgets("Print something from Java", (tester) async { + final arena = Arena(); + final env = jni.getEnv(); + final system = env.FindClass("java/lang/System".toNativeChars(arena)); + final field = env.GetStaticFieldID(system, "out".toNativeChars(arena), + "Ljava/io/PrintStream;".toNativeChars(arena)); + final out = env.GetStaticObjectField(system, field); + final printStream = env.GetObjectClass(out); + /* + final println = env.GetMethodID(printStream, "println".toNativeChars(arena), + "(Ljava/lang/String;)V".toNativeChars(arena)); + */ + const str = "\nHello JNI!"; + final jstr = env.asJString(str); + env.deleteAllLocalRefs([system, printStream, jstr]); + arena.releaseAll(); + }); + + testWidgets("Long.intValue() using JniObject", (tester) async { + final longClass = jni.findJniClass("java/lang/Long"); + + final longCtor = longClass.getConstructorID("(J)V"); + + final long = longClass.newObject(longCtor, [176]); + + final intValue = long.callIntMethodByName("intValue", "()I", []); + expect(intValue, equals(176)); + + long.delete(); + longClass.delete(); + }); + + testWidgets("call a static method using JniClass APIs", (tester) async { + final integerClass = jni.wrapClass(jni.findClass("java/lang/Integer")); + final result = integerClass.callStaticObjectMethodByName( + "toHexString", "(I)Ljava/lang/String;", [31]); + + final resultString = result.asDartString(); + + result.delete(); + expect(resultString, equals("1f")); + + integerClass.delete(); + }); + + testWidgets("Example for using getMethodID", (tester) async { + final longClass = jni.findJniClass("java/lang/Long"); + final bitCountMethod = longClass.getStaticMethodID("bitCount", "(J)I"); + + final random = jni.newInstance("java/util/Random", "()V", []); + + final nextIntMethod = random.getMethodID("nextInt", "(I)I"); + + for (int i = 0; i < 100; i++) { + int r = random.callIntMethod(nextIntMethod, [256 * 256]); + int bits = 0; + final jbc = + longClass.callStaticIntMethod(bitCountMethod, [JValueLong(r)]); + while (r != 0) { + bits += r % 2; + r = (r / 2).floor(); + } + expect(jbc, equals(bits)); + } + + random.delete(); + longClass.delete(); + }); + + testWidgets("invoke_", (tester) async { + final m = jni.invokeLongMethod( + "java/lang/Long", "min", "(JJ)J", [JValueLong(1234), JValueLong(1324)]); + expect(m, equals(1234)); + }); + + testWidgets("retrieve_", (tester) async { + final maxLong = jni.retrieveShortField("java/lang/Short", "MAX_VALUE", "S"); + expect(maxLong, equals(32767)); + }); + + testWidgets("callStaticStringMethod", (tester) async { + final longClass = jni.findJniClass("java/lang/Long"); + const n = 1223334444; + final strFromJava = longClass.callStaticStringMethodByName( + "toOctalString", "(J)Ljava/lang/String;", [JValueLong(n)]); + expect(strFromJava, equals(n.toRadixString(8))); + longClass.delete(); + }); + + testWidgets("Passing strings in arguments", (tester) async { + final out = jni.retrieveObjectField( + "java/lang/System", "out", "Ljava/io/PrintStream;"); + // uncomment next line to see output + // (\n because test runner prints first char at end of the line) + //out.callVoidMethodByName( + // "println", "(Ljava/lang/Object;)V", ["\nWorks (Apparently)"]); + out.delete(); + }); + + testWidgets("Passing strings in arguments 2", (tester) async { + final twelve = jni.invokeByteMethod( + "java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", ["12"]); + expect(twelve, equals(12)); + }); + + testWidgets("use() method", (tester) async { + final randomInt = jni.newInstance("java/util/Random", "()V", []).use( + (random) => random.callIntMethodByName("nextInt", "(I)I", [15])); + expect(randomInt, lessThan(15)); + }); + + testWidgets("enums", (tester) async { + final ordinal = jni + .retrieveObjectField( + "java/net/Proxy\$Type", "HTTP", "Ljava/net/Proxy\$Type;") + .use((f) => f.callIntMethodByName("ordinal", "()I", [])); + expect(ordinal, equals(1)); + }); + + testWidgets("Isolate", (tester) async { + Isolate.spawn(doSomeWorkInIsolate, null); + }); + + testWidgets("JniGlobalRef", (tester) async { + final uri = jni.invokeObjectMethod( + "java/net/URI", + "create", + "(Ljava/lang/String;)Ljava/net/URI;", + ["https://www.google.com/search"]); + final rg = uri.getGlobalRef(); + await Future.delayed(const Duration(seconds: 1), () { + final env = jni.getEnv(); + // Now comment this line & try to directly use uri local ref + // in outer scope. + // + // You will likely get a segfault, because Future computation is running + // in different thread. + // + // Therefore, don't share JniObjects across functions that can be + // scheduled across threads, including async callbacks. + final uri = JniObject.fromGlobalRef(env, rg); + final scheme = + uri.callStringMethodByName("getScheme", "()Ljava/lang/String;", []); + expect(scheme, "https"); + uri.delete(); + rg.deleteIn(env); + }); + uri.delete(); + }); +} + +void doSomeWorkInIsolate(Void? _) { + final jni = Jni.getInstance(); + final random = jni.newInstance("java/util/Random", "()V", []); + // var r = random.callIntMethodByName("nextInt", "(I)I", [256]); + // expect(r, lessThan(256)); + // Expect throws an OutsideTestException + // but you can uncomment below print and see it works + // print("\n$r"); + random.delete(); +}
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart index 9f4f903..c21802b 100644 --- a/pkgs/jni/example/lib/main.dart +++ b/pkgs/jni/example/lib/main.dart
@@ -1,74 +1,218 @@ -import 'package:flutter/material.dart'; -import 'dart:async'; +// ignore_for_file: library_private_types_in_public_api -import 'package:jni/jni.dart' as jni; +import 'package:flutter/material.dart'; + +import 'dart:io'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:jni/jni.dart'; +import 'package:jni/jni_object.dart'; + +late Jni jni; + +String localToJavaString(int n) { + final jniEnv = jni.getEnv(); + final arena = Arena(); + final cls = jniEnv.FindClass("java/lang/String".toNativeChars(arena)); + final mId = jniEnv.GetStaticMethodID(cls, "valueOf".toNativeChars(), + "(I)Ljava/lang/String;".toNativeChars(arena)); + final i = arena<JValue>(); + i.ref.i = n; + final res = jniEnv.CallStaticObjectMethodA(cls, mId, i); + final str = jniEnv.asDartString(res); + jniEnv.deleteAllLocalRefs([res, cls]); + arena.releaseAll(); + return str; +} + +int random(int n) { + final arena = Arena(); + final jniEnv = jni.getEnv(); + final randomCls = jniEnv.FindClass("java/util/Random".toNativeChars(arena)); + final ctor = jniEnv.GetMethodID( + randomCls, "<init>".toNativeChars(arena), "()V".toNativeChars(arena)); + final random = jniEnv.NewObject(randomCls, ctor); + final nextInt = jniEnv.GetMethodID( + randomCls, "nextInt".toNativeChars(arena), "(I)I".toNativeChars(arena)); + final res = jniEnv.CallIntMethodA(random, nextInt, Jni.jvalues([n])); + jniEnv.deleteAllLocalRefs([randomCls, random]); + return res; +} + +double randomDouble() { + final math = jni.findJniClass("java/lang/Math"); + final random = math.callStaticDoubleMethodByName("random", "()D", []); + math.delete(); + return random; +} + +int uptime() { + final systemClock = jni.findJniClass("android/os/SystemClock"); + final uptime = + systemClock.callStaticLongMethodByName("uptimeMillis", "()J", []); + systemClock.delete(); + return uptime; +} + +void quit() { + jni + .wrap(jni.getCurrentActivity()) + .use((ac) => ac.callVoidMethodByName("finish", "()V", [])); +} + +void showToast(String text) { + // This is example for calling you app's custom java code. + // You place the AnyToast class in you app's android/ source + // Folder, with a Keep annotation or appropriate proguard rules + // to retain the class in release mode. + // In this example, AnyToast class is just a type of `Toast` that + // can be called from any thread. See + // android/app/src/main/java/dev/dart/jni_example/AnyToast.java + jni.invokeObjectMethod( + "dev/dart/jni_example/AnyToast", + "makeText", + "(Landroid/app/Activity;Landroid/content/Context;" + "Ljava/lang/CharSequence;I)" + "Ldev/dart/jni_example/AnyToast;", + [ + jni.getCurrentActivity(), + jni.getCachedApplicationContext(), + ":-)", + 0 + ]).callVoidMethodByName("show", "()V", []); +} void main() { - runApp(const MyApp()); + if (!Platform.isAndroid) { + Jni.spawn(); + } + jni = Jni.getInstance(); + final examples = [ + Example("String.valueOf(1332)", () => localToJavaString(1332)), + Example("Generate random number", () => random(180), runInitially: false), + Example("Math.random()", () => randomDouble(), runInitially: false), + if (Platform.isAndroid) ...[ + Example("Minutes of usage since reboot", + () => (uptime() / (60 * 1000)).floor()), + Example( + "Device name", + () => jni.retrieveStringField( + "android/os/Build", "DEVICE", "Ljava/lang/String;")), + Example( + "Package name", + () => jni.wrap(jni.getCurrentActivity()).use((activity) => activity + .callStringMethodByName( + "getPackageName", "()Ljava/lang/String;", [])), + ), + Example("Show toast", () => showToast("Hello from JNI!"), + runInitially: false), + Example( + "Quit", + quit, + runInitially: false, + ), + ] + ]; + runApp(MyApp(examples)); +} + +class Example { + String title; + dynamic Function() callback; + bool runInitially; + Example(this.title, this.callback, {this.runInitially = true}); } class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp(this.examples, {Key? key}) : super(key: key); + final List<Example> examples; @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { - late int sumResult; - late Future<int> sumAsyncResult; - @override void initState() { super.initState(); - sumResult = jni.sum(1, 2); - sumAsyncResult = jni.sumAsync(3, 4); } @override Widget build(BuildContext context) { - const textStyle = TextStyle(fontSize: 25); - const spacerSmall = SizedBox(height: 10); return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('Native Packages'), + title: const Text('JNI Examples'), ), - body: SingleChildScrollView( - child: Container( - padding: const EdgeInsets.all(10), - child: Column( - children: [ - const Text( - 'This calls a native function through FFI that is shipped as source in the package. ' - 'The native code is built as part of the Flutter Runner build.', - style: textStyle, - textAlign: TextAlign.center, - ), - spacerSmall, - Text( - 'sum(1, 2) = $sumResult', - style: textStyle, - textAlign: TextAlign.center, - ), - spacerSmall, - FutureBuilder<int>( - future: sumAsyncResult, - builder: (BuildContext context, AsyncSnapshot<int> value) { - final displayValue = - (value.hasData) ? value.data : 'loading'; - return Text( - 'await sumAsync(3, 4) = $displayValue', - style: textStyle, - textAlign: TextAlign.center, - ); - }, - ), - ], - ), - ), - ), + body: ListView.builder( + itemCount: widget.examples.length, + itemBuilder: (context, i) { + final eg = widget.examples[i]; + return ExampleCard(eg); + }), ), ); } } + +class ExampleCard extends StatefulWidget { + const ExampleCard(this.example, {Key? key}) : super(key: key); + final Example example; + + @override + _ExampleCardState createState() => _ExampleCardState(); +} + +class _ExampleCardState extends State<ExampleCard> { + Widget _pad(Widget w, double h, double v) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: w); + } + + bool _run = false; + + @override + void initState() { + super.initState(); + _run = widget.example.runInitially; + } + + @override + Widget build(BuildContext context) { + final eg = widget.example; + var result = ""; + var hasError = false; + if (_run) { + try { + result = eg.callback().toString(); + } on Exception catch (e) { + hasError = true; + result = e.toString(); + } on Error catch (e) { + hasError = true; + result = e.toString(); + } + } + var resultStyle = const TextStyle(fontFamily: "Monospace"); + if (hasError) { + resultStyle = const TextStyle(fontFamily: "Monospace", color: Colors.red); + } + return Card( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(eg.title, + softWrap: true, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + _pad( + Text(result.toString(), softWrap: true, style: resultStyle), 8, 16), + _pad( + ElevatedButton( + child: Text(_run ? "Run again" : "Run"), + onPressed: () => setState(() => _run = true), + ), + 8, + 8), + ]), + ); + } +}
diff --git a/pkgs/jni/example/pubspec.lock b/pkgs/jni/example/pubspec.lock index 77442b5..2f0d348 100644 --- a/pkgs/jni/example/pubspec.lock +++ b/pkgs/jni/example/pubspec.lock
@@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" async: dependency: transitive description: @@ -43,6 +57,13 @@ url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" cupertino_icons: dependency: "direct main" description: @@ -57,11 +78,30 @@ url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + ffi: + dependency: "direct main" + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_lints: dependency: "direct dev" description: @@ -74,6 +114,16 @@ description: flutter source: sdk version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" jni: dependency: "direct main" description: @@ -109,6 +159,13 @@ url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -116,6 +173,13 @@ url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -123,6 +187,13 @@ url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" sky_engine: dependency: transitive description: flutter @@ -156,6 +227,13 @@ url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" term_glyph: dependency: transitive description: @@ -170,6 +248,13 @@ url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" vector_math: dependency: transitive description: @@ -177,6 +262,19 @@ url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "8.2.2" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" sdks: dart: ">=2.17.5 <3.0.0" - flutter: ">=2.11.0"
diff --git a/pkgs/jni/example/pubspec.yaml b/pkgs/jni/example/pubspec.yaml index 1569e63..1ff0ae2 100644 --- a/pkgs/jni/example/pubspec.yaml +++ b/pkgs/jni/example/pubspec.yaml
@@ -30,6 +30,8 @@ flutter: sdk: flutter + ffi: ^2.0.0 + jni: # When depending on this package from a real application you should use: # jni: ^x.y.z @@ -45,6 +47,8 @@ dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is
diff --git a/pkgs/jni/example/test/widget_test.dart b/pkgs/jni/example/test/widget_test.dart new file mode 100644 index 0000000..c2336a0 --- /dev/null +++ b/pkgs/jni/example/test/widget_test.dart
@@ -0,0 +1,43 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:jni/jni.dart'; +import 'package:jni/jni_object.dart'; +import 'package:jni_example/main.dart'; + +// This test exists just to verify that +// when everything is correct, JNI actually runs +// However it's also kind of meaningless, because test environment +// differs substantially from the device. + +void main() { + if (!Platform.isAndroid) { + Jni.spawn(helperDir: "../src/build"); + } + final jni = Jni.getInstance(); + testWidgets("simple toString example", (tester) async { + await tester.pumpWidget(ExampleForTest(ExampleCard(Example( + "toString", + () => jni.findJniClass("java/lang/Long").use((long) => long + .callStaticStringMethodByName( + "toHexString", "(J)Ljava/lang/String;", [0x1876])))))); + expect(find.text("1876"), findsOneWidget); + }); +} + +class ExampleForTest extends StatelessWidget { + const ExampleForTest(this.widget, {Key? key}) : super(key: key); + final Widget widget; + @override + Widget build(BuildContext context) { + return MaterialApp( + title: '__TEST__', + home: Scaffold( + appBar: AppBar(title: const Text("__test__")), + body: Center(child: widget), + ), + ); + } +}
diff --git a/pkgs/jni/ffigen.yaml b/pkgs/jni/ffigen.yaml index 7626d28..f43903c 100644 --- a/pkgs/jni/ffigen.yaml +++ b/pkgs/jni/ffigen.yaml
@@ -1,19 +1,113 @@ # Run with `dart run ffigen --config ffigen.yaml`. name: JniBindings description: | - Bindings for `src/jni.h`. + Bindings for libdartjni.so which is part of jni plugin. - Regenerate bindings with `dart run ffigen --config ffigen.yaml`. -output: 'lib/jni_bindings_generated.dart' + It also transitively includes type definitions such as JNIEnv from third_party/jni.h; + + However, functions prefixed JNI_ are not usable because they are in a different shared library. + + Regenerate bindings with `flutter pub run ffigen.dart --config ffigen.yaml`. +output: 'lib/src/third_party/jni_bindings_generated.dart' headers: entry-points: - - 'src/jni.h' + - 'src/dartjni.h' include-directives: - - 'src/jni.h' + - 'src/dartjni.h' + - 'third_party/jni.h' +compiler-opts: + - '-Ithird_party/' +functions: + exclude: # Exclude init functions supposed to be defined in loaded DLL, not JNI + - 'JNI_OnLoad' + - 'JNI_OnUnload' + - 'JNI_OnLoad_L' + - 'JNI_OnUnload_L' +structs: + exclude: + - 'jni_context' + rename: + ## opaque struct definitions, base types of jfieldID and jmethodID + '_jfieldID': 'jfieldID_' + '_jmethodID': 'jmethodID_' + #'JNI(.*)': 'Jni$1' +unions: + rename: + 'jvalue': 'JValue' +enums: + rename: + 'DartJniLogLevel': 'JniLogLevel' +globals: + exclude: + - 'jni' + - 'jniEnv' +typedefs: + rename: + 'JNI(.*)': 'Jni$1' + 'jint': 'JInt' + 'jclass': 'JClass' + 'jobject': 'JObject' + 'jbyte': 'JByte' + 'jsize': 'JSize' + 'jmethodID': 'JMethodID' + 'jfieldID': 'JFieldID' + 'jboolean': 'JBoolean' + 'jthrowable': 'JThrowable' + 'jchar': 'JChar' + 'jshort': 'JShort' + 'jlong': 'JLong' + 'jfloat': 'JFloat' + 'jdouble': 'JDouble' + 'jstring': 'JString' + 'jarray': 'JArray' + 'jobjectArray': 'JObjectArray' + 'jbooleanArray': 'JBooleanArray' + 'jbyteArray': 'JByteArray' + 'jcharArray': 'JCharArray' + 'jshortArray': 'JShortArray' + 'jintArray': 'JIntArray' + 'jlongArray': 'JLongArray' + 'jfloatArray': 'JFloatArray' + 'jdoubleArray': 'JDoubleArray' + 'jweak': 'JWeak' + 'jvalue': 'JValue' preamble: | + // Autogenerated file. Do not edit. + // Generated from an annotated version of jni.h provided in Android NDK + // (NDK Version 23.1.7779620) + // The license for original file is provided below: + + /* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* + * JNI specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + // ignore_for_file: always_specify_types // ignore_for_file: camel_case_types // ignore_for_file: non_constant_identifier_names + // ignore_for_file: constant_identifier_names + // ignore_for_file: unused_field + // ignore_for_file: unused_element + // coverage:ignore-file comments: style: any length: full +
diff --git a/pkgs/jni/lib/jni.dart b/pkgs/jni/lib/jni.dart index f9a759f..b604a5b 100644 --- a/pkgs/jni/lib/jni.dart +++ b/pkgs/jni/lib/jni.dart
@@ -1,131 +1,66 @@ +// 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. -import 'dart:async'; -import 'dart:ffi'; -import 'dart:io'; -import 'dart:isolate'; - -import 'jni_bindings_generated.dart'; - -/// A very short-lived native function. +/// Package jni provides dart bindings for the Java Native Interface (JNI) on +/// Android and desktop platforms. /// -/// For very short-lived functions, it is fine to call them on the main isolate. -/// They will block the Dart execution while running the native function, so -/// only do this for native functions which are guaranteed to be short-lived. -int sum(int a, int b) => _bindings.sum(a, b); - -/// A longer lived native function, which occupies the thread calling it. +/// It's intended as a supplement to the (planned) jnigen tool, a Java wrapper +/// generator using JNI. The goal is to provide sufficiently complete +/// and ergonomic access to underlying JNI APIs. /// -/// Do not call these kind of native functions in the main isolate. They will -/// block Dart execution. This will cause dropped frames in Flutter applications. -/// Instead, call these native functions on a separate isolate. +/// Therefore, some understanding of JNI is required to use this module. /// -/// Modify this to suit your own use case. Example use cases: +/// __Java VM:__ +/// On Android, the existing JVM is used, a new JVM needs to be spawned on +/// flutter desktop & standalone targets. /// -/// 1. Reuse a single isolate for various different kinds of requests. -/// 2. Use multiple helper isolates for parallel execution. -Future<int> sumAsync(int a, int b) async { - final SendPort helperIsolateSendPort = await _helperIsolateSendPort; - final int requestId = _nextSumRequestId++; - final _SumRequest request = _SumRequest(requestId, a, b); - final Completer<int> completer = Completer<int>(); - _sumRequests[requestId] = completer; - helperIsolateSendPort.send(request); - return completer.future; -} - -const String _libName = 'jni'; - -/// The dynamic library in which the symbols for [JniBindings] can be found. -final DynamicLibrary _dylib = () { - if (Platform.isMacOS || Platform.isIOS) { - return DynamicLibrary.open('$_libName.framework/$_libName'); - } - if (Platform.isAndroid || Platform.isLinux) { - return DynamicLibrary.open('lib$_libName.so'); - } - if (Platform.isWindows) { - return DynamicLibrary.open('$_libName.dll'); - } - throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); -}(); - -/// The bindings to the native functions in [_dylib]. -final JniBindings _bindings = JniBindings(_dylib); - - -/// A request to compute `sum`. +/// ```dart +/// if (!Platform.isAndroid) { +/// // Spin up a JVM instance with custom classpath etc.. +/// Jni.spawn(/* options */); +/// } +/// Jni jni = Jni.getInstance(); +/// ``` /// -/// Typically sent from one isolate to another. -class _SumRequest { - final int id; - final int a; - final int b; - - const _SumRequest(this.id, this.a, this.b); -} - -/// A response with the result of `sum`. +/// __Dart standalone support:__ +/// On dart standalone target, we unfortunately have no mechanism to bundle +/// the wrapper libraries with the executable. Thus it needs to be explicitly +/// placed in a accessible directory and provided as an argument to Jni.spawn. /// -/// Typically sent from one isolate to another. -class _SumResponse { - final int id; - final int result; +/// This module depends on a shared library written in C. Therefore on dart +/// standalone: +/// +/// * Build the library `libdartjni.so` in src/ directory of this plugin. +/// * Bundle it appropriately with dart application. +/// * Pass the path to library as a parameter to `Jni.spawn()`. +/// +/// __JNIEnv:__ +/// The types `JNIEnv` and `JavaVM` in JNI are available as `JniEnv` and +/// `JavaVM` respectively, with extension methods to conveniently invoke the +/// function pointer members. Therefore the calling syntax will be similar to +/// JNI in C++. The first `JniEnv *` parameter is implicit. +/// +/// __Debugging__: +/// Debugging JNI errors hard in general. +/// +/// * On desktop platforms you can use JniEnv.ExceptionDescribe to print any +/// pending exception to stdout. +/// * On Android, things are slightly easier since CheckJNI is usually enabled +/// in debug builds. If you are not getting clear stack traces on JNI errors, +/// check the Android NDK page on how to enable CheckJNI using ADB. +/// * As a rule of thumb, when there's a NoClassDefFound / NoMethodFound error, +/// first check your class and method signatures for typos. +/// - const _SumResponse(this.id, this.result); -} +/// This file exports the minimum foundations of JNI. +/// +/// For a higher level API, import `'package:jni/jni_object.dart'`. +library jni; -/// Counter to identify [_SumRequest]s and [_SumResponse]s. -int _nextSumRequestId = 0; - -/// Mapping from [_SumRequest] `id`s to the completers corresponding to the correct future of the pending request. -final Map<int, Completer<int>> _sumRequests = <int, Completer<int>>{}; - -/// The SendPort belonging to the helper isolate. -Future<SendPort> _helperIsolateSendPort = () async { - // The helper isolate is going to send us back a SendPort, which we want to - // wait for. - final Completer<SendPort> completer = Completer<SendPort>(); - - // Receive port on the main isolate to receive messages from the helper. - // We receive two types of messages: - // 1. A port to send messages on. - // 2. Responses to requests we sent. - final ReceivePort receivePort = ReceivePort() - ..listen((dynamic data) { - if (data is SendPort) { - // The helper isolate sent us the port on which we can sent it requests. - completer.complete(data); - return; - } - if (data is _SumResponse) { - // The helper isolate sent us a response to a request we sent. - final Completer<int> completer = _sumRequests[data.id]!; - _sumRequests.remove(data.id); - completer.complete(data.result); - return; - } - throw UnsupportedError('Unsupported message type: ${data.runtimeType}'); - }); - - // Start the helper isolate. - await Isolate.spawn((SendPort sendPort) async { - final ReceivePort helperReceivePort = ReceivePort() - ..listen((dynamic data) { - // On the helper isolate listen to requests and respond to them. - if (data is _SumRequest) { - final int result = _bindings.sum_long_running(data.a, data.b); - final _SumResponse response = _SumResponse(data.id, result); - sendPort.send(response); - return; - } - throw UnsupportedError('Unsupported message type: ${data.runtimeType}'); - }); - - // Send the the port to the main isolate on which we can receive requests. - sendPort.send(helperReceivePort.sendPort); - }, receivePort.sendPort); - - // Wait until the helper isolate has sent us back the SendPort on which we - // can start sending requests. - return completer.future; -}(); +export 'src/third_party/jni_bindings_generated.dart' hide JNI_LOG_TAG; +export 'src/jni.dart'; +export 'src/jvalues.dart' hide JValueArgs, toJValues; +export 'src/extensions.dart' + show StringMethodsForJni, CharPtrMethodsForJni, AdditionalJniEnvMethods; +export 'src/jni_exceptions.dart';
diff --git a/pkgs/jni/lib/jni_bindings_generated.dart b/pkgs/jni/lib/jni_bindings_generated.dart deleted file mode 100644 index 1ec49c9..0000000 --- a/pkgs/jni/lib/jni_bindings_generated.dart +++ /dev/null
@@ -1,69 +0,0 @@ -// ignore_for_file: always_specify_types -// ignore_for_file: camel_case_types -// ignore_for_file: non_constant_identifier_names - -// AUTO GENERATED FILE, DO NOT EDIT. -// -// Generated by `package:ffigen`. -import 'dart:ffi' as ffi; - -/// Bindings for `src/jni.h`. -/// -/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`. -/// -class JniBindings { - /// Holds the symbol lookup function. - final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) - _lookup; - - /// The symbols are looked up in [dynamicLibrary]. - JniBindings(ffi.DynamicLibrary dynamicLibrary) - : _lookup = dynamicLibrary.lookup; - - /// The symbols are looked up with [lookup]. - JniBindings.fromLookup( - ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) - lookup) - : _lookup = lookup; - - /// A very short-lived native function. - /// - /// For very short-lived functions, it is fine to call them on the main isolate. - /// They will block the Dart execution while running the native function, so - /// only do this for native functions which are guaranteed to be short-lived. - int sum( - int a, - int b, - ) { - return _sum( - a, - b, - ); - } - - late final _sumPtr = - _lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>( - 'sum'); - late final _sum = _sumPtr.asFunction<int Function(int, int)>(); - - /// A longer lived native function, which occupies the thread calling it. - /// - /// Calling these kind of native functions in the main isolate will - /// block Dart execution and cause dropped frames in Flutter applications. - /// Consider calling such native functions from a separate isolate. - int sum_long_running( - int a, - int b, - ) { - return _sum_long_running( - a, - b, - ); - } - - late final _sum_long_runningPtr = - _lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>( - 'sum_long_running'); - late final _sum_long_running = - _sum_long_runningPtr.asFunction<int Function(int, int)>(); -}
diff --git a/pkgs/jni/lib/jni_object.dart b/pkgs/jni/lib/jni_object.dart new file mode 100644 index 0000000..f12a8fd --- /dev/null +++ b/pkgs/jni/lib/jni_object.dart
@@ -0,0 +1,14 @@ +/// jni_object library provides an easier interface to JNI's object references, +/// providing various helper methods for one-off uses. +/// +/// It consists of generated methods to access java objects and call functions +/// on them, abstracting away most error checking and string conversions etc.. +/// +/// The important types are JniClass and JniObject, which are high level +/// wrappers around JClass and JObject. +/// +/// Import this library along with `jni.dart`. +library jni_object; + +export 'src/jni_class.dart'; +export 'src/jni_object.dart';
diff --git a/pkgs/jni/lib/src/direct_methods_generated.dart b/pkgs/jni/lib/src/direct_methods_generated.dart new file mode 100644 index 0000000..fc04c0f --- /dev/null +++ b/pkgs/jni/lib/src/direct_methods_generated.dart
@@ -0,0 +1,630 @@ +// Autogenerated; DO NOT EDIT +// Generated by running the script in tool/gen_aux_methods.dart +// coverage:ignore-file +part of 'jni.dart'; + +extension JniInvokeMethods on Jni { + String invokeStringMethod(String className, String methodName, + String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticObjectMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + + final strRes = env.asDartString(result, deleteOriginal: true); + env.checkException(); + return strRes; + }); + } + + String retrieveStringField( + String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticObjectField(cls, fieldID); + + final strRes = env.asDartString(result, deleteOriginal: true); + env.checkException(); + return strRes; + }); + } + + JniObject invokeObjectMethod(String className, String methodName, + String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticObjectMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + + env.checkException(); + return JniObject.of(env, result, nullptr); + }); + } + + JniObject retrieveObjectField( + String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticObjectField(cls, fieldID); + + env.checkException(); + return JniObject.of(env, result, nullptr); + }); + } + + bool invokeBooleanMethod(String className, String methodName, + String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticBooleanMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result != 0; + }); + } + + bool retrieveBooleanField( + String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticBooleanField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result != 0; + }); + } + + int invokeByteMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticByteMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int retrieveByteField(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticByteField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int invokeCharMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticCharMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int retrieveCharField(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticCharField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int invokeShortMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticShortMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int retrieveShortField(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticShortField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int invokeIntMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticIntMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int retrieveIntField(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticIntField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int invokeLongMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticLongMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + int retrieveLongField(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticLongField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + double invokeFloatMethod(String className, String methodName, + String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticFloatMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + double retrieveFloatField( + String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticFloatField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + double invokeDoubleMethod(String className, String methodName, + String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticDoubleMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + double retrieveDoubleField( + String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStaticDoubleField(cls, fieldID); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } + + void invokeVoidMethod(String className, String methodName, String signature, + List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = + env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStaticVoidMethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + env.DeleteLocalRef(cls); + + env.checkException(); + return result; + }); + } +}
diff --git a/pkgs/jni/lib/src/extensions.dart b/pkgs/jni/lib/src/extensions.dart new file mode 100644 index 0000000..d749e3e --- /dev/null +++ b/pkgs/jni/lib/src/extensions.dart
@@ -0,0 +1,97 @@ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import 'third_party/jni_bindings_generated.dart'; + +import 'jni_exceptions.dart'; + +extension StringMethodsForJni on String { + /// Returns a Utf-8 encoded Pointer<Char> with contents same as this string. + Pointer<Char> toNativeChars([Allocator allocator = malloc]) { + return toNativeUtf8(allocator: allocator).cast<Char>(); + } +} + +extension CharPtrMethodsForJni on Pointer<Char> { + /// Same as calling `cast<Utf8>` followed by `toDartString`. + String toDartString() { + return cast<Utf8>().toDartString(); + } +} + +extension AdditionalJniEnvMethods on Pointer<JniEnv> { + /// Convenience method for converting a [JString] + /// to dart string. + /// if [deleteOriginal] is specified, jstring passed will be deleted using + /// DeleteLocalRef. + String asDartString(JString jstring, {bool deleteOriginal = false}) { + final chars = GetStringUTFChars(jstring, nullptr); + if (chars == nullptr) { + checkException(); + } + final result = chars.cast<Utf8>().toDartString(); + ReleaseStringUTFChars(jstring, chars); + if (deleteOriginal) { + DeleteLocalRef(jstring); + } + return result; + } + + /// Return a new [JString] from contents of [s]. + JString asJString(String s) { + final utf = s.toNativeUtf8().cast<Char>(); + final result = NewStringUTF(utf); + malloc.free(utf); + return result; + } + + /// Deletes all local references in [refs]. + void deleteAllLocalRefs(List<JObject> refs) { + for (final ref in refs) { + DeleteLocalRef(ref); + } + } + + /// If any exception is pending in JNI, throw it in Dart. + /// + /// If [describe] is true, a description is printed to screen. + /// To access actual exception object, use `ExceptionOccurred`. + void checkException({bool describe = false}) { + final exc = ExceptionOccurred(); + if (exc != nullptr) { + // TODO: Doing this every time is expensive. + // Should lookup and cache method reference, + // and keep it alive by keeping a reference to Exception class. + // IssueRef: https://github.com/dart-lang/jni_gen/issues/13 + final ecls = GetObjectClass(exc); + final toStr = GetMethodID(ecls, _toString, _toStringSig); + final jstr = CallObjectMethod(exc, toStr); + final dstr = asDartString(jstr); + for (final i in [jstr, ecls]) { + DeleteLocalRef(i); + } + if (describe) { + ExceptionDescribe(); + } else { + ExceptionClear(); + } + throw JniException(exc, dstr); + } + } + + /// Calls the printStackTrace on exception object + /// obtained by java + void printStackTrace(JniException je) { + final ecls = GetObjectClass(je.err); + final printStackTrace = + GetMethodID(ecls, _printStackTrace, _printStackTraceSig); + CallVoidMethod(je.err, printStackTrace); + DeleteLocalRef(ecls); + } +} + +final _toString = "toString".toNativeChars(); +final _toStringSig = "()Ljava/lang/String;".toNativeChars(); +final _printStackTrace = "printStackTrace".toNativeChars(); +final _printStackTraceSig = "()V".toNativeChars();
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart new file mode 100644 index 0000000..beac5f7 --- /dev/null +++ b/pkgs/jni/lib/src/jni.dart
@@ -0,0 +1,269 @@ +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:path/path.dart'; + +import 'third_party/jni_bindings_generated.dart'; +import 'extensions.dart'; +import 'jvalues.dart'; +import 'jni_exceptions.dart'; + +import 'jni_object.dart'; +import 'jni_class.dart'; + +part 'direct_methods_generated.dart'; + +String _getLibraryFileName(String base) { + if (Platform.isLinux || Platform.isAndroid) { + return "lib$base.so"; + } else if (Platform.isWindows) { + return "$base.dll"; + } else if (Platform.isMacOS) { + return "$base.framework/$base"; + } else { + throw UnsupportedError("cannot derive library name: unsupported platform"); + } +} + +/// Load Dart-JNI Helper library. +/// +/// If path is provided, it's used to load the library. +/// Else just the platform-specific filename is passed to DynamicLibrary.open +DynamicLibrary _loadJniHelpersLibrary( + {String? dir, String baseName = "dartjni"}) { + final fileName = _getLibraryFileName(baseName); + final libPath = (dir != null) ? join(dir, fileName) : fileName; + try { + final dylib = DynamicLibrary.open(libPath); + return dylib; + } on Error { + throw HelperNotFoundException(libPath); + } +} + +/// Jni represents a single running JNI instance. +/// +/// It provides convenience functions for looking up and invoking functions +/// without several FFI conversions. +/// +/// You can also get access to instance of underlying JavaVM and JniEnv, and +/// then use them in a way similar to JNI C++ API. +class Jni { + final JniBindings _bindings; + + Jni._(this._bindings); + + static Jni? _instance; + + /// Returns the existing Jni object. + /// + /// If not running on Android and no Jni is spawned + /// using Jni.spawn(), throws an exception. + /// + /// On Dart standalone, when calling for the first time from + /// a new isolate, make sure to pass the library path. + static Jni getInstance() { + if (_instance == null) { + final inst = Jni._(JniBindings(_loadJniHelpersLibrary())); + if (inst.getJavaVM() == nullptr) { + throw StateError("Fatal: No JVM associated with this process!" + " Did you call Jni.spawn?"); + } + // If no error, save this singleton. + _instance = inst; + } + return _instance!; + } + + /// Initialize instance from custom helper library path. + /// + /// On dart standalone, call this in new isolate before + /// doing getInstance(). + /// + /// (The reason is that dylibs need to be loaded in every isolate. + /// On flutter it's done by library. On dart standalone we don't + /// know the library path.) + static void load({required String helperDir}) { + if (_instance != null) { + throw StateError('Fatal: a JNI instance already exists in this isolate'); + } + final inst = Jni._(JniBindings(_loadJniHelpersLibrary(dir: helperDir))); + if (inst.getJavaVM() == nullptr) { + throw StateError("Fatal: No JVM associated with this process"); + } + _instance = inst; + } + + /// Spawn an instance of JVM using JNI. + /// This instance will be returned by future calls to [getInstance] + /// + /// [helperDir] is path of the directory where the wrapper library is found. + /// This parameter needs to be passed manually on __Dart standalone target__, + /// since we have no reliable way to bundle it with the package. + /// + /// [jvmOptions], [ignoreUnrecognized], & [jniVersion] are passed to the JVM. + /// Strings in [classPath], if any, are used to construct an additional + /// JVM option of the form "-Djava.class.path={paths}". + static Jni spawn({ + String? helperDir, + int logLevel = JniLogLevel.JNI_INFO, + List<String> jvmOptions = const [], + List<String> classPath = const [], + bool ignoreUnrecognized = false, + int jniVersion = JNI_VERSION_1_6, + }) { + if (_instance != null) { + throw UnsupportedError("Currently only 1 VM is supported."); + } + final dylib = _loadJniHelpersLibrary(dir: helperDir); + final inst = Jni._(JniBindings(dylib)); + _instance = inst; + inst._bindings.SetJNILogging(logLevel); + final jArgs = _createVMArgs( + options: jvmOptions, + classPath: classPath, + version: jniVersion, + ignoreUnrecognized: ignoreUnrecognized, + ); + inst._bindings.SpawnJvm(jArgs); + _freeVMArgs(jArgs); + return inst; + } + + static Pointer<JavaVMInitArgs> _createVMArgs({ + List<String> options = const [], + List<String> classPath = const [], + bool ignoreUnrecognized = false, + int version = JNI_VERSION_1_6, + }) { + final args = calloc<JavaVMInitArgs>(); + if (options.isNotEmpty || classPath.isNotEmpty) { + final count = options.length + (classPath.isNotEmpty ? 1 : 0); + + final optsPtr = (count != 0) ? calloc<JavaVMOption>(count) : nullptr; + args.ref.options = optsPtr; + for (int i = 0; i < options.length; i++) { + optsPtr.elementAt(i).ref.optionString = options[i].toNativeChars(); + } + if (classPath.isNotEmpty) { + final classPathString = classPath.join(Platform.isWindows ? ';' : ":"); + optsPtr.elementAt(count - 1).ref.optionString = + "-Djava.class.path=$classPathString".toNativeChars(); + } + args.ref.nOptions = count; + } + args.ref.ignoreUnrecognized = ignoreUnrecognized ? 1 : 0; + args.ref.version = version; + return args; + } + + static void _freeVMArgs(Pointer<JavaVMInitArgs> argPtr) { + final nOptions = argPtr.ref.nOptions; + final options = argPtr.ref.options; + if (nOptions != 0) { + for (var i = 0; i < nOptions; i++) { + calloc.free(options.elementAt(i).ref.optionString); + } + calloc.free(argPtr.ref.options); + } + calloc.free(argPtr); + } + + /// Returns pointer to current JNI JavaVM instance + Pointer<JavaVM> getJavaVM() { + return _bindings.GetJavaVM(); + } + + /// Returns JniEnv* associated with current thread. + /// + /// Do not reuse JniEnv between threads, it's only valid + /// in the thread it is obtained. + Pointer<JniEnv> getEnv() { + return _bindings.GetJniEnv(); + } + + void setJniLogging(int loggingLevel) { + _bindings.SetJNILogging(loggingLevel); + } + + /// Returns current application context on Android. + JObject getCachedApplicationContext() { + return _bindings.GetApplicationContext(); + } + + /// Returns current activity + JObject getCurrentActivity() { + return _bindings.GetCurrentActivity(); + } + + /// Get the initial classLoader of the application. + /// + /// This is especially useful on Android, where + /// JNI threads cannot access application classes using + /// the usual `JniEnv.FindClass` method. + JObject getApplicationClassLoader() { + return _bindings.GetClassLoader(); + } + + /// Returns class reference found through system-specific mechanism + JClass findClass(String qualifiedName) { + final nameChars = qualifiedName.toNativeChars(); + final cls = _bindings.LoadClass(nameChars); + calloc.free(nameChars); + if (cls == nullptr) { + getEnv().checkException(); + } + return cls; + } + + /// Returns class for [qualifiedName] found by platform-specific mechanism, + /// wrapped in a `JniClass`. + JniClass findJniClass(String qualifiedName) { + return JniClass.of(getEnv(), findClass(qualifiedName)); + } + + /// Constructs an instance of class with given args. + /// + /// Use it when you only need one instance, but not the actual class + /// nor any constructor / static methods. + JniObject newInstance( + String qualifiedName, String ctorSignature, List<dynamic> args) { + final cls = findJniClass(qualifiedName); + final ctor = cls.getMethodID("<init>", ctorSignature); + final obj = cls.newObject(ctor, args); + cls.delete(); + return obj; + } + + /// Wraps a JObject ref in a JniObject. + /// The original ref is stored in JniObject, and + /// deleted with the latter's [delete] method. + /// + /// It takes the ownership of the jobject so that it can be used like this: + /// + /// ```dart + /// final result = jni.wrap(long_expr_returning_jobject) + /// ``` + JniObject wrap(JObject obj) { + return JniObject.of(getEnv(), obj, nullptr); + } + + /// Wraps a JObject ref in a JniObject. + /// The original ref is stored in JniObject, and + /// deleted with the latter's [delete] method. + JniClass wrapClass(JClass cls) { + return JniClass.of(getEnv(), cls); + } + + /// Converts passed arguments to JValue array + /// for use in methods that take arguments. + /// + /// int, bool, double and JObject types are converted out of the box. + /// wrap values in types such as [JValueLong] + /// to convert to other primitive types instead. + static Pointer<JValue> jvalues(List<dynamic> args, + {Allocator allocator = calloc}) { + return toJValues(args, allocator: allocator); + } +}
diff --git a/pkgs/jni/lib/src/jni_class.dart b/pkgs/jni/lib/src/jni_class.dart new file mode 100644 index 0000000..b4a6763 --- /dev/null +++ b/pkgs/jni/lib/src/jni_class.dart
@@ -0,0 +1,153 @@ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import 'third_party/jni_bindings_generated.dart'; +import 'extensions.dart'; +import 'jvalues.dart'; +import 'jni_exceptions.dart'; +import 'jni_object.dart'; + +part 'jni_class_methods_generated.dart'; + +final ctorLookupChars = "<init>".toNativeChars(); + +/// Convenience wrapper around a JNI local class reference. +/// +/// Reference lifetime semantics are same as [JniObject]. +class JniClass { + final JClass _cls; + final Pointer<JniEnv> _env; + bool _deleted = false; + JniClass.of(this._env, this._cls); + + JniClass.fromJClass(Pointer<JniEnv> env, JClass cls) + : _env = env, + _cls = cls; + + JniClass.fromGlobalRef(Pointer<JniEnv> env, JniGlobalClassRef r) + : _env = env, + _cls = env.NewLocalRef(r._cls) { + if (r._deleted) { + throw UseAfterFreeException(r, r._cls); + } + } + + @pragma('vm:prefer-inline') + void _checkDeleted() { + if (_deleted) { + throw UseAfterFreeException(this, _cls); + } + } + + JMethodID getConstructorID(String signature) { + return _getMethodID("<init>", signature, false); + } + + /// Construct new object using [ctor]. + JniObject newObject(JMethodID ctor, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final newObj = _env.NewObjectA(_cls, ctor, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + _env.checkException(); + return JniObject.of(_env, newObj, nullptr); + } + + JMethodID _getMethodID(String name, String signature, bool isStatic) { + _checkDeleted(); + final methodName = name.toNativeChars(); + final methodSig = signature.toNativeChars(); + final result = isStatic + ? _env.GetStaticMethodID(_cls, methodName, methodSig) + : _env.GetMethodID(_cls, methodName, methodSig); + calloc.free(methodName); + calloc.free(methodSig); + _env.checkException(); + return result; + } + + JFieldID _getFieldID(String name, String signature, bool isStatic) { + _checkDeleted(); + final methodName = name.toNativeChars(); + final methodSig = signature.toNativeChars(); + final result = isStatic + ? _env.GetStaticFieldID(_cls, methodName, methodSig) + : _env.GetFieldID(_cls, methodName, methodSig); + calloc.free(methodName); + calloc.free(methodSig); + _env.checkException(); + return result; + } + + @pragma('vm:prefer-inline') + JMethodID getMethodID(String name, String signature) { + return _getMethodID(name, signature, false); + } + + @pragma('vm:prefer-inline') + JMethodID getStaticMethodID(String name, String signature) { + return _getMethodID(name, signature, true); + } + + @pragma('vm:prefer-inline') + JFieldID getFieldID(String name, String signature) { + return _getFieldID(name, signature, false); + } + + @pragma('vm:prefer-inline') + JFieldID getStaticFieldID(String name, String signature) { + return _getFieldID(name, signature, true); + } + + /// Returns the underlying [JClass]. + JClass get jclass { + _checkDeleted(); + return _cls; + } + + JniGlobalClassRef getGlobalRef() { + _checkDeleted(); + return JniGlobalClassRef._(_env.NewGlobalRef(_cls)); + } + + void delete() { + if (_deleted) { + throw DoubleFreeException(this, _cls); + } + _env.DeleteLocalRef(_cls); + _deleted = true; + } + + /// Use this [JniClass] to execute callback, then delete. + /// + /// Useful in expression chains. + T use<T>(T Function(JniClass) callback) { + _checkDeleted(); + final result = callback(this); + delete(); + return result; + } +} + +/// Global reference type for JniClasses +/// +/// Instead of passing local references between functions +/// that may be run on different threads, convert it +/// using [JniClass.getGlobalRef] and reconstruct using +/// [JniClass.fromGlobalRef] +class JniGlobalClassRef { + JniGlobalClassRef._(this._cls); + final JClass _cls; + JClass get jclass => _cls; + bool _deleted = false; + + void deleteIn(Pointer<JniEnv> env) { + if (_deleted) { + throw DoubleFreeException(this, _cls); + } + env.DeleteGlobalRef(_cls); + _deleted = true; + } +}
diff --git a/pkgs/jni/lib/src/jni_class_methods_generated.dart b/pkgs/jni/lib/src/jni_class_methods_generated.dart new file mode 100644 index 0000000..3e3a7ca --- /dev/null +++ b/pkgs/jni/lib/src/jni_class_methods_generated.dart
@@ -0,0 +1,408 @@ +// Autogenerated; DO NOT EDIT +// Generated by running the script in tool/gen_aux_methods.dart +// coverage:ignore-file +part of 'jni_class.dart'; + +extension JniClassCallMethods on JniClass { + /// Calls method pointed to by [methodID] with [args] as arguments + String callStaticStringMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticObjectMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + final strRes = _env.asDartString(result, deleteOriginal: true); + _env.checkException(); + return strRes; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticStringMethod]. + String callStaticStringMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticStringMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + String getStaticStringField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticObjectField(_cls, fieldID); + final strRes = _env.asDartString(result, deleteOriginal: true); + _env.checkException(); + return strRes; + } + + /// Retrieve field of given [name] and [signature] + String getStaticStringFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticStringField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + JniObject callStaticObjectMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticObjectMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return JniObject.of(_env, result, nullptr); + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticObjectMethod]. + JniObject callStaticObjectMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticObjectMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + JniObject getStaticObjectField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticObjectField(_cls, fieldID); + + _env.checkException(); + return JniObject.of(_env, result, nullptr); + } + + /// Retrieve field of given [name] and [signature] + JniObject getStaticObjectFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticObjectField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + bool callStaticBooleanMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticBooleanMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result != 0; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticBooleanMethod]. + bool callStaticBooleanMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticBooleanMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + bool getStaticBooleanField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticBooleanField(_cls, fieldID); + + _env.checkException(); + return result != 0; + } + + /// Retrieve field of given [name] and [signature] + bool getStaticBooleanFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticBooleanField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callStaticByteMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticByteMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticByteMethod]. + int callStaticByteMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticByteMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getStaticByteField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticByteField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getStaticByteFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticByteField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callStaticCharMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticCharMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticCharMethod]. + int callStaticCharMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticCharMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getStaticCharField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticCharField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getStaticCharFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticCharField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callStaticShortMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticShortMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticShortMethod]. + int callStaticShortMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticShortMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getStaticShortField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticShortField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getStaticShortFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticShortField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callStaticIntMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticIntMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticIntMethod]. + int callStaticIntMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticIntMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getStaticIntField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticIntField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getStaticIntFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticIntField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callStaticLongMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticLongMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticLongMethod]. + int callStaticLongMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticLongMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getStaticLongField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticLongField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getStaticLongFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticLongField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + double callStaticFloatMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticFloatMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticFloatMethod]. + double callStaticFloatMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticFloatMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + double getStaticFloatField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticFloatField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + double getStaticFloatFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticFloatField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + double callStaticDoubleMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticDoubleMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticDoubleMethod]. + double callStaticDoubleMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticDoubleMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + double getStaticDoubleField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetStaticDoubleField(_cls, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + double getStaticDoubleFieldByName(String name, String signature) { + final fID = getStaticFieldID(name, signature); + final result = getStaticDoubleField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + void callStaticVoidMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallStaticVoidMethodA(_cls, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getStaticMethodID] + /// and [callStaticVoidMethod]. + void callStaticVoidMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getStaticMethodID(name, signature); + final result = callStaticVoidMethod(mID, args); + return result; + } +}
diff --git a/pkgs/jni/lib/src/jni_exceptions.dart b/pkgs/jni/lib/src/jni_exceptions.dart new file mode 100644 index 0000000..1533437 --- /dev/null +++ b/pkgs/jni/lib/src/jni_exceptions.dart
@@ -0,0 +1,49 @@ +import 'dart:ffi'; + +import 'third_party/jni_bindings_generated.dart'; + +class UseAfterFreeException implements Exception { + dynamic object; + Pointer<Void> ptr; + UseAfterFreeException(this.object, this.ptr); + + @override + String toString() { + return "use after free on $ptr through $object"; + } +} + +class DoubleFreeException implements Exception { + dynamic object; + Pointer<Void> ptr; + DoubleFreeException(this.object, this.ptr); + + @override + String toString() { + return "double on $ptr through $object"; + } +} + +class JniException implements Exception { + /// Exception object pointer from JNI. + final JObject err; + + /// brief description, usually initialized with error message from Java. + final String msg; + JniException(this.err, this.msg); + + @override + String toString() => msg; + + void deleteIn(Pointer<JniEnv> env) => env.DeleteLocalRef(err); +} + +class HelperNotFoundException implements Exception { + HelperNotFoundException(this.path); + final String path; + + @override + String toString() => "Lookup for helper library $path failed.\n" + "Please ensure that `dartjni` shared library is built.\n" + "If the library is already built, double check the path."; +}
diff --git a/pkgs/jni/lib/src/jni_object.dart b/pkgs/jni/lib/src/jni_object.dart new file mode 100644 index 0000000..217c832 --- /dev/null +++ b/pkgs/jni/lib/src/jni_object.dart
@@ -0,0 +1,180 @@ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import 'third_party/jni_bindings_generated.dart'; +import 'extensions.dart'; +import 'jni_class.dart'; +import 'jvalues.dart'; +import 'jni_exceptions.dart'; + +part 'jni_object_methods_generated.dart'; + +/// JniObject is a convenience wrapper around a JNI local object reference. +/// +/// It holds the object, its associated associated jniEnv etc.. +/// It should be distroyed with [delete] method after done. +/// +/// It's valid only in the thread it was created. +/// When passing to code that might run in a different thread (eg: a callback), +/// consider obtaining a global reference and reconstructing the object. +class JniObject { + JClass _cls; + final JObject _obj; + final Pointer<JniEnv> _env; + bool _deleted = false; + JniObject.of(this._env, this._obj, this._cls); + + @pragma('vm:prefer-inline') + void _checkDeleted() { + if (_deleted) { + throw UseAfterFreeException(this, _obj); + } + } + + JniObject.fromJObject(Pointer<JniEnv> env, JObject obj) + : _env = env, + _obj = obj, + _cls = nullptr; + + /// Reconstructs a JniObject from [r] + /// + /// [r] still needs to be explicitly deleted when + /// it's no longer needed to construct any JniObjects. + JniObject.fromGlobalRef(Pointer<JniEnv> env, JniGlobalObjectRef r) + : _env = env, + _obj = env.NewLocalRef(r._obj), + _cls = env.NewLocalRef(r._cls) { + if (r._deleted) { + throw UseAfterFreeException(r, r._obj); + } + } + + /// Delete the local reference contained by this object. + /// + /// Do not use a JniObject after calling [delete]. + void delete() { + if (_deleted == true) { + throw DoubleFreeException(this, _obj); + } + _env.DeleteLocalRef(_obj); + if (_cls != nullptr) { + _env.DeleteLocalRef(_cls); + } + _deleted = true; + } + + /// Returns underlying [JObject] of this [JniObject]. + JObject get jobject { + _checkDeleted(); + return _obj; + } + + /// Returns underlying [JClass] of this [JniObject]. + JObject get jclass { + _checkDeleted(); + if (_cls == nullptr) { + _cls = _env.GetObjectClass(_obj); + } + return _cls; + } + + /// Get a JniClass of this object's class. + JniClass getClass() { + _checkDeleted(); + if (_cls == nullptr) { + return JniClass.of(_env, _env.GetObjectClass(_obj)); + } + return JniClass.of(_env, _env.NewLocalRef(_cls)); + } + + /// if the underlying JObject is string + /// converts it to string representation. + String asDartString() { + _checkDeleted(); + return _env.asDartString(_obj); + } + + /// Returns method id for [name] on this object. + JMethodID getMethodID(String name, String signature) { + _checkDeleted(); + if (_cls == nullptr) { + _cls = _env.GetObjectClass(_obj); + } + final methodName = name.toNativeChars(); + final methodSig = signature.toNativeChars(); + final result = _env.GetMethodID(_cls, methodName, methodSig); + calloc.free(methodName); + calloc.free(methodSig); + _env.checkException(); + return result; + } + + /// Returns field id for [name] on this object. + JFieldID getFieldID(String name, String signature) { + _checkDeleted(); + if (_cls == nullptr) { + _cls = _env.GetObjectClass(_obj); + } + final methodName = name.toNativeChars(); + final methodSig = signature.toNativeChars(); + final result = _env.GetFieldID(_cls, methodName, methodSig); + calloc.free(methodName); + calloc.free(methodSig); + _env.checkException(); + return result; + } + + /// Get a global reference. + /// + /// This is useful for passing a JniObject between threads. + JniGlobalObjectRef getGlobalRef() { + _checkDeleted(); + return JniGlobalObjectRef._( + _env.NewGlobalRef(_obj), + _env.NewGlobalRef(_cls), + ); + } + + /// Use this [JniObject] to execute callback, then delete. + /// + /// Useful in expression chains. + T use<T>(T Function(JniObject) callback) { + _checkDeleted(); + try { + final result = callback(this); + delete(); + return result; + } catch (e) { + delete(); + rethrow; + } + } +} + +/// High level wrapper to a JNI global reference. +/// which is safe to be passed through threads. +/// +/// In a different thread, actual object can be reconstructed +/// using [JniObject.fromGlobalRef] +/// +/// It should be explicitly deleted after done, using +/// [deleteIn] method, passing some env, eg: obtained using [Jni.getEnv]. +class JniGlobalObjectRef { + final JObject _obj; + final JClass _cls; + bool _deleted = false; + JniGlobalObjectRef._(this._obj, this._cls); + + JObject get jobject => _obj; + JObject get jclass => _cls; + + void deleteIn(Pointer<JniEnv> env) { + if (_deleted == true) { + throw DoubleFreeException(this, _obj); + } + env.DeleteGlobalRef(_obj); + env.DeleteGlobalRef(_cls); + _deleted = true; + } +}
diff --git a/pkgs/jni/lib/src/jni_object_methods_generated.dart b/pkgs/jni/lib/src/jni_object_methods_generated.dart new file mode 100644 index 0000000..490f8fc --- /dev/null +++ b/pkgs/jni/lib/src/jni_object_methods_generated.dart
@@ -0,0 +1,402 @@ +// Autogenerated; DO NOT EDIT +// Generated by running the script in tool/gen_aux_methods.dart +// coverage:ignore-file +part of 'jni_object.dart'; + +extension JniObjectCallMethods on JniObject { + /// Calls method pointed to by [methodID] with [args] as arguments + String callStringMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallObjectMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + final strRes = _env.asDartString(result, deleteOriginal: true); + _env.checkException(); + return strRes; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callStringMethod]. + String callStringMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callStringMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + String getStringField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetObjectField(_obj, fieldID); + final strRes = _env.asDartString(result, deleteOriginal: true); + _env.checkException(); + return strRes; + } + + /// Retrieve field of given [name] and [signature] + String getStringFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getStringField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + JniObject callObjectMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallObjectMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return JniObject.of(_env, result, nullptr); + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callObjectMethod]. + JniObject callObjectMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callObjectMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + JniObject getObjectField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetObjectField(_obj, fieldID); + + _env.checkException(); + return JniObject.of(_env, result, nullptr); + } + + /// Retrieve field of given [name] and [signature] + JniObject getObjectFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getObjectField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + bool callBooleanMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallBooleanMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result != 0; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callBooleanMethod]. + bool callBooleanMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callBooleanMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + bool getBooleanField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetBooleanField(_obj, fieldID); + + _env.checkException(); + return result != 0; + } + + /// Retrieve field of given [name] and [signature] + bool getBooleanFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getBooleanField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callByteMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallByteMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callByteMethod]. + int callByteMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callByteMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getByteField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetByteField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getByteFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getByteField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callCharMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallCharMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callCharMethod]. + int callCharMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callCharMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getCharField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetCharField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getCharFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getCharField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callShortMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallShortMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callShortMethod]. + int callShortMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callShortMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getShortField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetShortField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getShortFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getShortField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callIntMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallIntMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callIntMethod]. + int callIntMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callIntMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getIntField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetIntField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getIntFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getIntField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + int callLongMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallLongMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callLongMethod]. + int callLongMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callLongMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + int getLongField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetLongField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + int getLongFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getLongField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + double callFloatMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallFloatMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callFloatMethod]. + double callFloatMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callFloatMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + double getFloatField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetFloatField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + double getFloatFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getFloatField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + double callDoubleMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallDoubleMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callDoubleMethod]. + double callDoubleMethodByName( + String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callDoubleMethod(mID, args); + return result; + } + + /// Retrieves the value of the field denoted by [fieldID] + double getDoubleField(JFieldID fieldID) { + _checkDeleted(); + final result = _env.GetDoubleField(_obj, fieldID); + + _env.checkException(); + return result; + } + + /// Retrieve field of given [name] and [signature] + double getDoubleFieldByName(String name, String signature) { + final fID = getFieldID(name, signature); + final result = getDoubleField(fID); + return result; + } + + /// Calls method pointed to by [methodID] with [args] as arguments + void callVoidMethod(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.CallVoidMethodA(_obj, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + + _env.checkException(); + return result; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [getMethodID] + /// and [callVoidMethod]. + void callVoidMethodByName(String name, String signature, List<dynamic> args) { + final mID = getMethodID(name, signature); + final result = callVoidMethod(mID, args); + return result; + } +}
diff --git a/pkgs/jni/lib/src/jvalues.dart b/pkgs/jni/lib/src/jvalues.dart new file mode 100644 index 0000000..63945b0 --- /dev/null +++ b/pkgs/jni/lib/src/jvalues.dart
@@ -0,0 +1,137 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +import 'third_party/jni_bindings_generated.dart'; +import 'extensions.dart'; +import 'jni_object.dart'; + +void _fillJValue(Pointer<JValue> pos, dynamic arg) { + // switch on runtimeType is not guaranteed to work? + switch (arg.runtimeType) { + case int: + pos.ref.i = arg; + break; + case bool: + pos.ref.z = arg ? 1 : 0; + break; + case Pointer<Void>: + case Pointer<Never>: + pos.ref.l = arg; + break; + case double: + pos.ref.d = arg; + break; + case JValueFloat: + pos.ref.f = (arg as JValueFloat).value; + break; + case JValueLong: + pos.ref.j = (arg as JValueLong).value; + break; + case JValueShort: + pos.ref.s = (arg as JValueShort).value; + break; + case JValueChar: + pos.ref.c = (arg as JValueChar).value; + break; + case JValueByte: + pos.ref.b = (arg as JValueByte).value; + break; + default: + throw "cannot convert ${arg.runtimeType} to jvalue"; + } +} + +/// Converts passed arguments to JValue array +/// for use in methods that take arguments. +/// +/// int, bool, double and JObject types are converted out of the box. +/// wrap values in types such as [JValueLong] +/// to convert to other primitive types instead. +Pointer<JValue> toJValues(List<dynamic> args, {Allocator allocator = calloc}) { + final result = allocator<JValue>(args.length); + for (int i = 0; i < args.length; i++) { + final arg = args[i]; + final pos = result.elementAt(i); + _fillJValue(pos, arg); + } + return result; +} + +/// Use this class as wrapper to convert an integer +/// to Java `long` in jvalues method. +class JValueLong { + int value; + JValueLong(this.value); +} + +/// Use this class as wrapper to convert an integer +/// to Java `short` in jvalues method. +class JValueShort { + int value; + JValueShort(this.value); +} + +/// Use this class as wrapper to convert an integer +/// to Java `byte` in jvalues method. +class JValueByte { + int value; + JValueByte(this.value); +} + +/// Use this class as wrapper to convert an double +/// to Java `float` in jvalues method. +class JValueFloat { + double value; + JValueFloat(this.value); +} + +/// Use this class as wrapper to convert an integer +/// to Java `char` in jvalues method. +class JValueChar { + int value; + JValueChar(this.value); + JValueChar.fromString(String s) : value = 0 { + if (s.length != 1) { + throw "Expected string of length 1"; + } + value = s.codeUnitAt(0).toInt(); + } +} + +/// class used to convert dart types passed to convenience methods +/// into their corresponding Java values. +/// +/// Similar to Jni.jvalues, but instead of a pointer, an instance +/// with a dispose method is returned. +/// This allows us to take dart strings. +/// +/// Returned value is allocated using provided allocator. +/// But default allocator may be used for string conversions. +class JValueArgs { + late Pointer<JValue> values; + final List<JObject> createdRefs = []; + + JValueArgs(List<dynamic> args, Pointer<JniEnv> env, + [Allocator allocator = malloc]) { + values = allocator<JValue>(args.length); + for (int i = 0; i < args.length; i++) { + final arg = args[i]; + final ptr = values.elementAt(i); + if (arg is String) { + final jstr = env.asJString(arg); + ptr.ref.l = jstr; + createdRefs.add(jstr); + } else if (arg is JniObject) { + ptr.ref.l = arg.jobject; + } else { + _fillJValue(ptr, arg); + } + } + } + + void disposeIn(Pointer<JniEnv> env) { + for (var ref in createdRefs) { + env.DeleteLocalRef(ref); + } + } +}
diff --git a/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart new file mode 100644 index 0000000..005029c --- /dev/null +++ b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
@@ -0,0 +1,3086 @@ +// Autogenerated file. Do not edit. +// Generated from an annotated version of jni.h provided in Android NDK +// (NDK Version 23.1.7779620) +// The license for original file is provided below: + +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * JNI specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + +// ignore_for_file: always_specify_types +// ignore_for_file: camel_case_types +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: constant_identifier_names +// ignore_for_file: unused_field +// ignore_for_file: unused_element +// coverage:ignore-file + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +/// Bindings for libdartjni.so which is part of jni plugin. +/// +/// It also transitively includes type definitions such as JNIEnv from third_party/jni.h; +/// +/// However, functions prefixed JNI_ are not usable because they are in a different shared library. +/// +/// Regenerate bindings with `flutter pub run ffigen.dart --config ffigen.yaml`. +/// +class JniBindings { + /// Holds the symbol lookup function. + final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + JniBindings(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + JniBindings.fromLookup( + ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + lookup) + : _lookup = lookup; + + /// VM initialization functions. + /// + /// Note these are the only symbols exported for JNI by the VM. + int JNI_GetDefaultJavaVMInitArgs( + ffi.Pointer<ffi.Void> arg0, + ) { + return _JNI_GetDefaultJavaVMInitArgs( + arg0, + ); + } + + late final _JNI_GetDefaultJavaVMInitArgsPtr = + _lookup<ffi.NativeFunction<JInt Function(ffi.Pointer<ffi.Void>)>>( + 'JNI_GetDefaultJavaVMInitArgs'); + late final _JNI_GetDefaultJavaVMInitArgs = _JNI_GetDefaultJavaVMInitArgsPtr + .asFunction<int Function(ffi.Pointer<ffi.Void>)>(); + + int JNI_CreateJavaVM( + ffi.Pointer<ffi.Pointer<JavaVM>> arg0, + ffi.Pointer<ffi.Pointer<JniEnv>> arg1, + ffi.Pointer<ffi.Void> arg2, + ) { + return _JNI_CreateJavaVM( + arg0, + arg1, + arg2, + ); + } + + late final _JNI_CreateJavaVMPtr = _lookup< + ffi.NativeFunction< + JInt Function( + ffi.Pointer<ffi.Pointer<JavaVM>>, + ffi.Pointer<ffi.Pointer<JniEnv>>, + ffi.Pointer<ffi.Void>)>>('JNI_CreateJavaVM'); + late final _JNI_CreateJavaVM = _JNI_CreateJavaVMPtr.asFunction< + int Function(ffi.Pointer<ffi.Pointer<JavaVM>>, + ffi.Pointer<ffi.Pointer<JniEnv>>, ffi.Pointer<ffi.Void>)>(); + + int JNI_GetCreatedJavaVMs( + ffi.Pointer<ffi.Pointer<JavaVM>> arg0, + int arg1, + ffi.Pointer<JSize> arg2, + ) { + return _JNI_GetCreatedJavaVMs( + arg0, + arg1, + arg2, + ); + } + + late final _JNI_GetCreatedJavaVMsPtr = _lookup< + ffi.NativeFunction< + JInt Function(ffi.Pointer<ffi.Pointer<JavaVM>>, JSize, + ffi.Pointer<JSize>)>>('JNI_GetCreatedJavaVMs'); + late final _JNI_GetCreatedJavaVMs = _JNI_GetCreatedJavaVMsPtr.asFunction< + int Function( + ffi.Pointer<ffi.Pointer<JavaVM>>, int, ffi.Pointer<JSize>)>(); + + ffi.Pointer<JavaVM> GetJavaVM() { + return _GetJavaVM(); + } + + late final _GetJavaVMPtr = + _lookup<ffi.NativeFunction<ffi.Pointer<JavaVM> Function()>>('GetJavaVM'); + late final _GetJavaVM = + _GetJavaVMPtr.asFunction<ffi.Pointer<JavaVM> Function()>(); + + ffi.Pointer<JniEnv> GetJniEnv() { + return _GetJniEnv(); + } + + late final _GetJniEnvPtr = + _lookup<ffi.NativeFunction<ffi.Pointer<JniEnv> Function()>>('GetJniEnv'); + late final _GetJniEnv = + _GetJniEnvPtr.asFunction<ffi.Pointer<JniEnv> Function()>(); + + ffi.Pointer<JniEnv> SpawnJvm( + ffi.Pointer<JavaVMInitArgs> args, + ) { + return _SpawnJvm( + args, + ); + } + + late final _SpawnJvmPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer<JniEnv> Function( + ffi.Pointer<JavaVMInitArgs>)>>('SpawnJvm'); + late final _SpawnJvm = _SpawnJvmPtr.asFunction< + ffi.Pointer<JniEnv> Function(ffi.Pointer<JavaVMInitArgs>)>(); + + JClass LoadClass( + ffi.Pointer<ffi.Char> name, + ) { + return _LoadClass( + name, + ); + } + + late final _LoadClassPtr = + _lookup<ffi.NativeFunction<JClass Function(ffi.Pointer<ffi.Char>)>>( + 'LoadClass'); + late final _LoadClass = + _LoadClassPtr.asFunction<JClass Function(ffi.Pointer<ffi.Char>)>(); + + JObject GetClassLoader() { + return _GetClassLoader(); + } + + late final _GetClassLoaderPtr = + _lookup<ffi.NativeFunction<JObject Function()>>('GetClassLoader'); + late final _GetClassLoader = + _GetClassLoaderPtr.asFunction<JObject Function()>(); + + JObject GetApplicationContext() { + return _GetApplicationContext(); + } + + late final _GetApplicationContextPtr = + _lookup<ffi.NativeFunction<JObject Function()>>('GetApplicationContext'); + late final _GetApplicationContext = + _GetApplicationContextPtr.asFunction<JObject Function()>(); + + JObject GetCurrentActivity() { + return _GetCurrentActivity(); + } + + late final _GetCurrentActivityPtr = + _lookup<ffi.NativeFunction<JObject Function()>>('GetCurrentActivity'); + late final _GetCurrentActivity = + _GetCurrentActivityPtr.asFunction<JObject Function()>(); + + void SetJNILogging( + int level, + ) { + return _SetJNILogging( + level, + ); + } + + late final _SetJNILoggingPtr = + _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>('SetJNILogging'); + late final _SetJNILogging = + _SetJNILoggingPtr.asFunction<void Function(int)>(); +} + +class jfieldID_ extends ffi.Opaque {} + +class jmethodID_ extends ffi.Opaque {} + +/// JNI invocation interface. +class JNIInvokeInterface extends ffi.Struct { + external ffi.Pointer<ffi.Void> reserved0; + + external ffi.Pointer<ffi.Void> reserved1; + + external ffi.Pointer<ffi.Void> reserved2; + + external ffi.Pointer<ffi.NativeFunction<JInt Function(ffi.Pointer<JavaVM>)>> + DestroyJavaVM; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<JniEnv>>, + ffi.Pointer<ffi.Void>)>> AttachCurrentThread; + + external ffi.Pointer<ffi.NativeFunction<JInt Function(ffi.Pointer<JavaVM>)>> + DetachCurrentThread; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<ffi.Void>>, + JInt)>> GetEnv; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<JniEnv>>, + ffi.Pointer<ffi.Void>)>> AttachCurrentThreadAsDaemon; +} + +extension JNIInvokeInterfaceExtension on ffi.Pointer<JavaVM> { + @pragma('vm:prefer-inline') + int DestroyJavaVM() { + return value.ref.DestroyJavaVM + .asFunction<int Function(ffi.Pointer<JavaVM>)>()(this); + } + + @pragma('vm:prefer-inline') + int AttachCurrentThread( + ffi.Pointer<ffi.Pointer<JniEnv>> p_env, ffi.Pointer<ffi.Void> thr_args) { + return value.ref.AttachCurrentThread.asFunction< + int Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<JniEnv>>, + ffi.Pointer<ffi.Void>)>()(this, p_env, thr_args); + } + + @pragma('vm:prefer-inline') + int DetachCurrentThread() { + return value.ref.DetachCurrentThread + .asFunction<int Function(ffi.Pointer<JavaVM>)>()(this); + } + + @pragma('vm:prefer-inline') + int GetEnv(ffi.Pointer<ffi.Pointer<ffi.Void>> p_env, int version) { + return value.ref.GetEnv.asFunction< + int Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<ffi.Void>>, + int)>()(this, p_env, version); + } + + @pragma('vm:prefer-inline') + int AttachCurrentThreadAsDaemon( + ffi.Pointer<ffi.Pointer<JniEnv>> p_env, ffi.Pointer<ffi.Void> thr_args) { + return value.ref.AttachCurrentThreadAsDaemon.asFunction< + int Function(ffi.Pointer<JavaVM>, ffi.Pointer<ffi.Pointer<JniEnv>>, + ffi.Pointer<ffi.Void>)>()(this, p_env, thr_args); + } +} + +typedef JInt = ffi.Int32; +typedef JavaVM = ffi.Pointer<JNIInvokeInterface>; +typedef JniEnv = ffi.Pointer<JNINativeInterface>; + +/// Table of interface function pointers. +class JNINativeInterface extends ffi.Struct { + external ffi.Pointer<ffi.Void> reserved0; + + external ffi.Pointer<ffi.Void> reserved1; + + external ffi.Pointer<ffi.Void> reserved2; + + external ffi.Pointer<ffi.Void> reserved3; + + external ffi.Pointer<ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>)>> + GetVersion; + + external ffi.Pointer< + ffi.NativeFunction< + JClass Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>, JObject, + ffi.Pointer<JByte>, JSize)>> DefineClass; + + external ffi.Pointer< + ffi.NativeFunction< + JClass Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>> + FindClass; + + external ffi.Pointer< + ffi.NativeFunction<JMethodID Function(ffi.Pointer<JniEnv1>, JObject)>> + FromReflectedMethod; + + external ffi.Pointer< + ffi.NativeFunction<JFieldID Function(ffi.Pointer<JniEnv1>, JObject)>> + FromReflectedField; + + /// spec doesn't show jboolean parameter + external ffi.Pointer< + ffi.NativeFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID, JBoolean)>> + ToReflectedMethod; + + external ffi.Pointer< + ffi.NativeFunction<JClass Function(ffi.Pointer<JniEnv1>, JClass)>> + GetSuperclass; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JClass, JClass)>> + IsAssignableFrom; + + /// spec doesn't show jboolean parameter + external ffi.Pointer< + ffi.NativeFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JBoolean)>> + ToReflectedField; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JThrowable)>> + Throw; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function( + ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>)>> ThrowNew; + + external ffi.Pointer< + ffi.NativeFunction<JThrowable Function(ffi.Pointer<JniEnv1>)>> + ExceptionOccurred; + + external ffi + .Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1>)>> + ExceptionDescribe; + + external ffi + .Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1>)>> + ExceptionClear; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>> + FatalError; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JInt)>> + PushLocalFrame; + + external ffi.Pointer< + ffi.NativeFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>> + PopLocalFrame; + + external ffi.Pointer< + ffi.NativeFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>> + NewGlobalRef; + + external ffi.Pointer< + ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1>, JObject)>> + DeleteGlobalRef; + + external ffi.Pointer< + ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1>, JObject)>> + DeleteLocalRef; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JObject)>> + IsSameObject; + + external ffi.Pointer< + ffi.NativeFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>> + NewLocalRef; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JInt)>> + EnsureLocalCapacity; + + external ffi.Pointer< + ffi.NativeFunction<JObject Function(ffi.Pointer<JniEnv1>, JClass)>> + AllocObject; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> NewObject; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _NewObjectV; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> NewObjectA; + + external ffi.Pointer< + ffi.NativeFunction<JClass Function(ffi.Pointer<JniEnv1>, JObject)>> + GetObjectClass; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JClass)>> + IsInstanceOf; + + external ffi.Pointer< + ffi.NativeFunction< + JMethodID Function(ffi.Pointer<JniEnv1>, JClass, + ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetMethodID; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallObjectMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallObjectMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallObjectMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallBooleanMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallBooleanMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallBooleanMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallByteMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallByteMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallByteMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallCharMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallCharMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallCharMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallShortMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallShortMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallShortMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallIntMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallIntMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallIntMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallLongMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallLongMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallLongMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallFloatMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallFloatMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallFloatMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallDoubleMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallDoubleMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallDoubleMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JMethodID)>> + CallVoidMethod; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallVoidMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>> CallVoidMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualObjectMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualObjectMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualObjectMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualBooleanMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualBooleanMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualBooleanMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualByteMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualByteMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualByteMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualCharMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualCharMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualCharMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualShortMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualShortMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualShortMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualIntMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualIntMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualIntMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualLongMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualLongMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualLongMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualFloatMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualFloatMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualFloatMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualDoubleMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualDoubleMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualDoubleMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>> + CallNonvirtualVoidMethod; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallNonvirtualVoidMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallNonvirtualVoidMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JFieldID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>> GetFieldID; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetObjectField; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetBooleanField; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetByteField; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetCharField; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetShortField; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> GetIntField; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetLongField; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetFloatField; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JObject, JFieldID)>> + GetDoubleField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JObject)>> + SetObjectField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JBoolean)>> + SetBooleanField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JByte)>> SetByteField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JChar)>> SetCharField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JShort)>> SetShortField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, JInt)>> + SetIntField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JLong)>> SetLongField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JFloat)>> SetFloatField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID, JDouble)>> + SetDoubleField; + + external ffi.Pointer< + ffi.NativeFunction< + JMethodID Function(ffi.Pointer<JniEnv1>, JClass, + ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetStaticMethodID; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticObjectMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticObjectMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticObjectMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticBooleanMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticBooleanMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticBooleanMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticByteMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticByteMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticByteMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticCharMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticCharMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticCharMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticShortMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticShortMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticShortMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticIntMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticIntMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticIntMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticLongMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticLongMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticLongMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticFloatMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticFloatMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticFloatMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticDoubleMethod; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticDoubleMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticDoubleMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JMethodID)>> + CallStaticVoidMethod; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<__va_list_tag>)>> _CallStaticVoidMethodV; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>> CallStaticVoidMethodA; + + external ffi.Pointer< + ffi.NativeFunction< + JFieldID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>> GetStaticFieldID; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticObjectField; + + external ffi.Pointer< + ffi.NativeFunction< + JBoolean Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticBooleanField; + + external ffi.Pointer< + ffi.NativeFunction< + JByte Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticByteField; + + external ffi.Pointer< + ffi.NativeFunction< + JChar Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticCharField; + + external ffi.Pointer< + ffi.NativeFunction< + JShort Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticShortField; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticIntField; + + external ffi.Pointer< + ffi.NativeFunction< + JLong Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticLongField; + + external ffi.Pointer< + ffi.NativeFunction< + JFloat Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticFloatField; + + external ffi.Pointer< + ffi.NativeFunction< + JDouble Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>> + GetStaticDoubleField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JObject)>> + SetStaticObjectField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JBoolean)>> + SetStaticBooleanField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, JByte)>> + SetStaticByteField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, JChar)>> + SetStaticCharField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JShort)>> + SetStaticShortField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, JInt)>> + SetStaticIntField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, JLong)>> + SetStaticLongField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JFloat)>> + SetStaticFloatField; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID, JDouble)>> + SetStaticDoubleField; + + external ffi.Pointer< + ffi.NativeFunction< + JString Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<JChar>, JSize)>> NewString; + + external ffi.Pointer< + ffi.NativeFunction<JSize Function(ffi.Pointer<JniEnv1>, JString)>> + GetStringLength; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JChar> Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JBoolean>)>> + GetStringChars; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JChar>)>> + ReleaseStringChars; + + external ffi.Pointer< + ffi.NativeFunction< + JString Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>> + NewStringUTF; + + external ffi.Pointer< + ffi.NativeFunction<JSize Function(ffi.Pointer<JniEnv1>, JString)>> + GetStringUTFLength; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<ffi.Char> Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JBoolean>)>> + GetStringUTFChars; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<ffi.Char>)>> + ReleaseStringUTFChars; + + external ffi.Pointer< + ffi.NativeFunction<JSize Function(ffi.Pointer<JniEnv1>, JArray)>> + GetArrayLength; + + external ffi.Pointer< + ffi.NativeFunction< + JObjectArray Function( + ffi.Pointer<JniEnv1>, JSize, JClass, JObject)>> NewObjectArray; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObjectArray, JSize)>> + GetObjectArrayElement; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JObjectArray, JSize, JObject)>> + SetObjectArrayElement; + + external ffi.Pointer< + ffi.NativeFunction< + JBooleanArray Function(ffi.Pointer<JniEnv1>, JSize)>> NewBooleanArray; + + external ffi.Pointer< + ffi.NativeFunction<JByteArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewByteArray; + + external ffi.Pointer< + ffi.NativeFunction<JCharArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewCharArray; + + external ffi.Pointer< + ffi.NativeFunction<JShortArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewShortArray; + + external ffi.Pointer< + ffi.NativeFunction<JIntArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewIntArray; + + external ffi.Pointer< + ffi.NativeFunction<JLongArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewLongArray; + + external ffi.Pointer< + ffi.NativeFunction<JFloatArray Function(ffi.Pointer<JniEnv1>, JSize)>> + NewFloatArray; + + external ffi.Pointer< + ffi.NativeFunction< + JDoubleArray Function(ffi.Pointer<JniEnv1>, JSize)>> NewDoubleArray; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JBoolean> Function( + ffi.Pointer<JniEnv1>, JBooleanArray, ffi.Pointer<JBoolean>)>> + GetBooleanArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JByte> Function( + ffi.Pointer<JniEnv1>, JByteArray, ffi.Pointer<JBoolean>)>> + GetByteArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JChar> Function( + ffi.Pointer<JniEnv1>, JCharArray, ffi.Pointer<JBoolean>)>> + GetCharArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JShort> Function( + ffi.Pointer<JniEnv1>, JShortArray, ffi.Pointer<JBoolean>)>> + GetShortArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JInt> Function( + ffi.Pointer<JniEnv1>, JIntArray, ffi.Pointer<JBoolean>)>> + GetIntArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JLong> Function( + ffi.Pointer<JniEnv1>, JLongArray, ffi.Pointer<JBoolean>)>> + GetLongArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JFloat> Function( + ffi.Pointer<JniEnv1>, JFloatArray, ffi.Pointer<JBoolean>)>> + GetFloatArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JDouble> Function( + ffi.Pointer<JniEnv1>, JDoubleArray, ffi.Pointer<JBoolean>)>> + GetDoubleArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JBooleanArray, + ffi.Pointer<JBoolean>, JInt)>> ReleaseBooleanArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JByteArray, ffi.Pointer<JByte>, JInt)>> + ReleaseByteArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JCharArray, ffi.Pointer<JChar>, JInt)>> + ReleaseCharArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JShortArray, + ffi.Pointer<JShort>, JInt)>> ReleaseShortArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JIntArray, ffi.Pointer<JInt>, JInt)>> + ReleaseIntArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JLongArray, ffi.Pointer<JLong>, JInt)>> + ReleaseLongArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JFloatArray, + ffi.Pointer<JFloat>, JInt)>> ReleaseFloatArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JDoubleArray, + ffi.Pointer<JDouble>, JInt)>> ReleaseDoubleArrayElements; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JBooleanArray, JSize, JSize, + ffi.Pointer<JBoolean>)>> GetBooleanArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JByteArray, JSize, JSize, + ffi.Pointer<JByte>)>> GetByteArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JCharArray, JSize, JSize, + ffi.Pointer<JChar>)>> GetCharArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JShortArray, JSize, JSize, + ffi.Pointer<JShort>)>> GetShortArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JIntArray, JSize, JSize, + ffi.Pointer<JInt>)>> GetIntArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JLongArray, JSize, JSize, + ffi.Pointer<JLong>)>> GetLongArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JFloatArray, JSize, JSize, + ffi.Pointer<JFloat>)>> GetFloatArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JDoubleArray, JSize, JSize, + ffi.Pointer<JDouble>)>> GetDoubleArrayRegion; + + /// spec shows these without const; some jni.h do, some don't + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JBooleanArray, JSize, JSize, + ffi.Pointer<JBoolean>)>> SetBooleanArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JByteArray, JSize, JSize, + ffi.Pointer<JByte>)>> SetByteArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JCharArray, JSize, JSize, + ffi.Pointer<JChar>)>> SetCharArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JShortArray, JSize, JSize, + ffi.Pointer<JShort>)>> SetShortArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JIntArray, JSize, JSize, + ffi.Pointer<JInt>)>> SetIntArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JLongArray, JSize, JSize, + ffi.Pointer<JLong>)>> SetLongArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JFloatArray, JSize, JSize, + ffi.Pointer<JFloat>)>> SetFloatArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JDoubleArray, JSize, JSize, + ffi.Pointer<JDouble>)>> SetDoubleArrayRegion; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function(ffi.Pointer<JniEnv1>, JClass, + ffi.Pointer<JNINativeMethod>, JInt)>> RegisterNatives; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JClass)>> + UnregisterNatives; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JObject)>> + MonitorEnter; + + external ffi.Pointer< + ffi.NativeFunction<JInt Function(ffi.Pointer<JniEnv1>, JObject)>> + MonitorExit; + + external ffi.Pointer< + ffi.NativeFunction< + JInt Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Pointer<JavaVM>>)>> + GetJavaVM; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JString, JSize, JSize, + ffi.Pointer<JChar>)>> GetStringRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<JniEnv1>, JString, JSize, JSize, + ffi.Pointer<ffi.Char>)>> GetStringUTFRegion; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<ffi.Void> Function( + ffi.Pointer<JniEnv1>, JArray, ffi.Pointer<JBoolean>)>> + GetPrimitiveArrayCritical; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JArray, ffi.Pointer<ffi.Void>, JInt)>> + ReleasePrimitiveArrayCritical; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<JChar> Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JBoolean>)>> + GetStringCritical; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JChar>)>> + ReleaseStringCritical; + + external ffi.Pointer< + ffi.NativeFunction<JWeak Function(ffi.Pointer<JniEnv1>, JObject)>> + NewWeakGlobalRef; + + external ffi.Pointer< + ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1>, JWeak)>> + DeleteWeakGlobalRef; + + external ffi + .Pointer<ffi.NativeFunction<JBoolean Function(ffi.Pointer<JniEnv1>)>> + ExceptionCheck; + + external ffi.Pointer< + ffi.NativeFunction< + JObject Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Void>, JLong)>> + NewDirectByteBuffer; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Pointer<ffi.Void> Function(ffi.Pointer<JniEnv1>, JObject)>> + GetDirectBufferAddress; + + external ffi.Pointer< + ffi.NativeFunction<JLong Function(ffi.Pointer<JniEnv1>, JObject)>> + GetDirectBufferCapacity; + + /// added in JNI 1.6 + external ffi.Pointer< + ffi.NativeFunction<ffi.Int32 Function(ffi.Pointer<JniEnv1>, JObject)>> + GetObjectRefType; +} + +extension JNINativeInterfaceExtension on ffi.Pointer<JniEnv> { + @pragma('vm:prefer-inline') + int GetVersion() { + return value.ref.GetVersion + .asFunction<int Function(ffi.Pointer<JniEnv1>)>()(this); + } + + @pragma('vm:prefer-inline') + JClass DefineClass(ffi.Pointer<ffi.Char> name, JObject loader, + ffi.Pointer<JByte> buf, int bufLen) { + return value.ref.DefineClass.asFunction< + JClass Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>, JObject, + ffi.Pointer<JByte>, int)>()(this, name, loader, buf, bufLen); + } + + @pragma('vm:prefer-inline') + JClass FindClass(ffi.Pointer<ffi.Char> name) { + return value.ref.FindClass.asFunction< + JClass Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>()(this, name); + } + + @pragma('vm:prefer-inline') + JMethodID FromReflectedMethod(JObject method) { + return value.ref.FromReflectedMethod + .asFunction<JMethodID Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, method); + } + + @pragma('vm:prefer-inline') + JFieldID FromReflectedField(JObject field) { + return value.ref.FromReflectedField + .asFunction<JFieldID Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, field); + } + + /// spec doesn't show jboolean parameter + /// + /// This is an automatically generated extension method + @pragma('vm:prefer-inline') + JObject ToReflectedMethod(JClass cls, JMethodID methodId, int isStatic) { + return value.ref.ToReflectedMethod.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, int)>()( + this, cls, methodId, isStatic); + } + + @pragma('vm:prefer-inline') + JClass GetSuperclass(JClass clazz) { + return value.ref.GetSuperclass + .asFunction<JClass Function(ffi.Pointer<JniEnv1>, JClass)>()( + this, clazz); + } + + @pragma('vm:prefer-inline') + int IsAssignableFrom(JClass clazz1, JClass clazz2) { + return value.ref.IsAssignableFrom + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JClass)>()( + this, clazz1, clazz2); + } + + /// spec doesn't show jboolean parameter + /// + /// This is an automatically generated extension method + @pragma('vm:prefer-inline') + JObject ToReflectedField(JClass cls, JFieldID fieldID, int isStatic) { + return value.ref.ToReflectedField.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, cls, fieldID, isStatic); + } + + @pragma('vm:prefer-inline') + int Throw(JThrowable obj) { + return value.ref.Throw + .asFunction<int Function(ffi.Pointer<JniEnv1>, JThrowable)>()( + this, obj); + } + + @pragma('vm:prefer-inline') + int ThrowNew(JClass clazz, ffi.Pointer<ffi.Char> message) { + return value.ref.ThrowNew.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, + ffi.Pointer<ffi.Char>)>()(this, clazz, message); + } + + @pragma('vm:prefer-inline') + JThrowable ExceptionOccurred() { + return value.ref.ExceptionOccurred + .asFunction<JThrowable Function(ffi.Pointer<JniEnv1>)>()(this); + } + + @pragma('vm:prefer-inline') + void ExceptionDescribe() { + return value.ref.ExceptionDescribe + .asFunction<void Function(ffi.Pointer<JniEnv1>)>()(this); + } + + @pragma('vm:prefer-inline') + void ExceptionClear() { + return value.ref.ExceptionClear + .asFunction<void Function(ffi.Pointer<JniEnv1>)>()(this); + } + + @pragma('vm:prefer-inline') + void FatalError(ffi.Pointer<ffi.Char> msg) { + return value.ref.FatalError.asFunction< + void Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>()(this, msg); + } + + @pragma('vm:prefer-inline') + int PushLocalFrame(int capacity) { + return value.ref.PushLocalFrame + .asFunction<int Function(ffi.Pointer<JniEnv1>, int)>()(this, capacity); + } + + @pragma('vm:prefer-inline') + JObject PopLocalFrame(JObject result) { + return value.ref.PopLocalFrame + .asFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, result); + } + + @pragma('vm:prefer-inline') + JObject NewGlobalRef(JObject obj) { + return value.ref.NewGlobalRef + .asFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, obj); + } + + @pragma('vm:prefer-inline') + void DeleteGlobalRef(JObject globalRef) { + return value.ref.DeleteGlobalRef + .asFunction<void Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, globalRef); + } + + @pragma('vm:prefer-inline') + void DeleteLocalRef(JObject localRef) { + return value.ref.DeleteLocalRef + .asFunction<void Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, localRef); + } + + @pragma('vm:prefer-inline') + int IsSameObject(JObject ref1, JObject ref2) { + return value.ref.IsSameObject + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject, JObject)>()( + this, ref1, ref2); + } + + @pragma('vm:prefer-inline') + JObject NewLocalRef(JObject ref) { + return value.ref.NewLocalRef + .asFunction<JObject Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, ref); + } + + @pragma('vm:prefer-inline') + int EnsureLocalCapacity(int capacity) { + return value.ref.EnsureLocalCapacity + .asFunction<int Function(ffi.Pointer<JniEnv1>, int)>()(this, capacity); + } + + @pragma('vm:prefer-inline') + JObject AllocObject(JClass clazz) { + return value.ref.AllocObject + .asFunction<JObject Function(ffi.Pointer<JniEnv1>, JClass)>()( + this, clazz); + } + + @pragma('vm:prefer-inline') + JObject NewObject(JClass arg0, JMethodID arg1) { + return value.ref.NewObject.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + JObject NewObjectA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.NewObjectA.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + JClass GetObjectClass(JObject obj) { + return value.ref.GetObjectClass + .asFunction<JClass Function(ffi.Pointer<JniEnv1>, JObject)>()( + this, obj); + } + + @pragma('vm:prefer-inline') + int IsInstanceOf(JObject obj, JClass clazz) { + return value.ref.IsInstanceOf + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject, JClass)>()( + this, obj, clazz); + } + + @pragma('vm:prefer-inline') + JMethodID GetMethodID( + JClass clazz, ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> sig) { + return value.ref.GetMethodID.asFunction< + JMethodID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>()(this, clazz, name, sig); + } + + @pragma('vm:prefer-inline') + JObject CallObjectMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallObjectMethod.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + JObject CallObjectMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallObjectMethodA.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallBooleanMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallBooleanMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallBooleanMethodA( + JObject obj, JMethodID methodId, ffi.Pointer<JValue> args) { + return value.ref.CallBooleanMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodId, args); + } + + @pragma('vm:prefer-inline') + int CallByteMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallByteMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallByteMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallByteMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallCharMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallCharMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallCharMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallCharMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallShortMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallShortMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallShortMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallShortMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallIntMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallIntMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallIntMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallIntMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallLongMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallLongMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallLongMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallLongMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallFloatMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallFloatMethod.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + double CallFloatMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallFloatMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallDoubleMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallDoubleMethod.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + double CallDoubleMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallDoubleMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + void CallVoidMethod(JObject arg0, JMethodID arg1) { + return value.ref.CallVoidMethod.asFunction< + void Function( + ffi.Pointer<JniEnv1>, JObject, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + void CallVoidMethodA( + JObject obj, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallVoidMethodA.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, methodID, args); + } + + @pragma('vm:prefer-inline') + JObject CallNonvirtualObjectMethod( + JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualObjectMethod.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JClass, + JMethodID)>()(this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + JObject CallNonvirtualObjectMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualObjectMethodA.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualBooleanMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualBooleanMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualBooleanMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualBooleanMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualByteMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualByteMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualByteMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualByteMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualCharMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualCharMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualCharMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualCharMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualShortMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualShortMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualShortMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualShortMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualIntMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualIntMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualIntMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualIntMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualLongMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualLongMethod.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + int CallNonvirtualLongMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualLongMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallNonvirtualFloatMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualFloatMethod.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JClass, + JMethodID)>()(this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + double CallNonvirtualFloatMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualFloatMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallNonvirtualDoubleMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualDoubleMethod.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JClass, + JMethodID)>()(this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + double CallNonvirtualDoubleMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualDoubleMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + void CallNonvirtualVoidMethod(JObject arg0, JClass arg1, JMethodID arg2) { + return value.ref.CallNonvirtualVoidMethod.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID)>()( + this, arg0, arg1, arg2); + } + + @pragma('vm:prefer-inline') + void CallNonvirtualVoidMethodA( + JObject obj, JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallNonvirtualVoidMethodA.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, obj, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + JFieldID GetFieldID( + JClass clazz, ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> sig) { + return value.ref.GetFieldID.asFunction< + JFieldID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>()(this, clazz, name, sig); + } + + @pragma('vm:prefer-inline') + JObject GetObjectField(JObject obj, JFieldID fieldID) { + return value.ref.GetObjectField.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetBooleanField(JObject obj, JFieldID fieldID) { + return value.ref.GetBooleanField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetByteField(JObject obj, JFieldID fieldID) { + return value.ref.GetByteField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetCharField(JObject obj, JFieldID fieldID) { + return value.ref.GetCharField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetShortField(JObject obj, JFieldID fieldID) { + return value.ref.GetShortField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetIntField(JObject obj, JFieldID fieldID) { + return value.ref.GetIntField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + int GetLongField(JObject obj, JFieldID fieldID) { + return value.ref.GetLongField.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + double GetFloatField(JObject obj, JFieldID fieldID) { + return value.ref.GetFloatField.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + double GetDoubleField(JObject obj, JFieldID fieldID) { + return value.ref.GetDoubleField.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JObject, JFieldID)>()(this, obj, fieldID); + } + + @pragma('vm:prefer-inline') + void SetObjectField(JObject obj, JFieldID fieldID, JObject val) { + return value.ref.SetObjectField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, JObject)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetBooleanField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetBooleanField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetByteField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetByteField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetCharField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetCharField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetShortField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetShortField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetIntField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetIntField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetLongField(JObject obj, JFieldID fieldID, int val) { + return value.ref.SetLongField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, int)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetFloatField(JObject obj, JFieldID fieldID, double val) { + return value.ref.SetFloatField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, double)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetDoubleField(JObject obj, JFieldID fieldID, double val) { + return value.ref.SetDoubleField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObject, JFieldID, double)>()( + this, obj, fieldID, val); + } + + @pragma('vm:prefer-inline') + JMethodID GetStaticMethodID( + JClass clazz, ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> sig) { + return value.ref.GetStaticMethodID.asFunction< + JMethodID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>()(this, clazz, name, sig); + } + + @pragma('vm:prefer-inline') + JObject CallStaticObjectMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticObjectMethod.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + JObject CallStaticObjectMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticObjectMethodA.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticBooleanMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticBooleanMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticBooleanMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticBooleanMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticByteMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticByteMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticByteMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticByteMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticCharMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticCharMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticCharMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticCharMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticShortMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticShortMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticShortMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticShortMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticIntMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticIntMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticIntMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticIntMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + int CallStaticLongMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticLongMethod.asFunction< + int Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + int CallStaticLongMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticLongMethodA.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallStaticFloatMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticFloatMethod.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + double CallStaticFloatMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticFloatMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + double CallStaticDoubleMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticDoubleMethod.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + double CallStaticDoubleMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticDoubleMethodA.asFunction< + double Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + void CallStaticVoidMethod(JClass arg0, JMethodID arg1) { + return value.ref.CallStaticVoidMethod.asFunction< + void Function( + ffi.Pointer<JniEnv1>, JClass, JMethodID)>()(this, arg0, arg1); + } + + @pragma('vm:prefer-inline') + void CallStaticVoidMethodA( + JClass clazz, JMethodID methodID, ffi.Pointer<JValue> args) { + return value.ref.CallStaticVoidMethodA.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JMethodID, + ffi.Pointer<JValue>)>()(this, clazz, methodID, args); + } + + @pragma('vm:prefer-inline') + JFieldID GetStaticFieldID( + JClass clazz, ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> sig) { + return value.ref.GetStaticFieldID.asFunction< + JFieldID Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Char>)>()(this, clazz, name, sig); + } + + @pragma('vm:prefer-inline') + JObject GetStaticObjectField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticObjectField.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID)>()(this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticBooleanField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticBooleanField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticByteField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticByteField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticCharField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticCharField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticShortField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticShortField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticIntField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticIntField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + int GetStaticLongField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticLongField + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass, JFieldID)>()( + this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + double GetStaticFloatField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticFloatField.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID)>()(this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + double GetStaticDoubleField(JClass clazz, JFieldID fieldID) { + return value.ref.GetStaticDoubleField.asFunction< + double Function( + ffi.Pointer<JniEnv1>, JClass, JFieldID)>()(this, clazz, fieldID); + } + + @pragma('vm:prefer-inline') + void SetStaticObjectField(JClass clazz, JFieldID fieldID, JObject val) { + return value.ref.SetStaticObjectField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, JObject)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticBooleanField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticBooleanField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticByteField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticByteField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticCharField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticCharField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticShortField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticShortField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticIntField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticIntField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticLongField(JClass clazz, JFieldID fieldID, int val) { + return value.ref.SetStaticLongField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, int)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticFloatField(JClass clazz, JFieldID fieldID, double val) { + return value.ref.SetStaticFloatField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, double)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + void SetStaticDoubleField(JClass clazz, JFieldID fieldID, double val) { + return value.ref.SetStaticDoubleField.asFunction< + void Function(ffi.Pointer<JniEnv1>, JClass, JFieldID, double)>()( + this, clazz, fieldID, val); + } + + @pragma('vm:prefer-inline') + JString NewString(ffi.Pointer<JChar> unicodeChars, int len) { + return value.ref.NewString.asFunction< + JString Function(ffi.Pointer<JniEnv1>, ffi.Pointer<JChar>, int)>()( + this, unicodeChars, len); + } + + @pragma('vm:prefer-inline') + int GetStringLength(JString string) { + return value.ref.GetStringLength + .asFunction<int Function(ffi.Pointer<JniEnv1>, JString)>()( + this, string); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JChar> GetStringChars( + JString string, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetStringChars.asFunction< + ffi.Pointer<JChar> Function(ffi.Pointer<JniEnv1>, JString, + ffi.Pointer<JBoolean>)>()(this, string, isCopy); + } + + @pragma('vm:prefer-inline') + void ReleaseStringChars(JString string, ffi.Pointer<JChar> isCopy) { + return value.ref.ReleaseStringChars.asFunction< + void Function(ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JChar>)>()( + this, string, isCopy); + } + + @pragma('vm:prefer-inline') + JString NewStringUTF(ffi.Pointer<ffi.Char> bytes) { + return value.ref.NewStringUTF.asFunction< + JString Function( + ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Char>)>()(this, bytes); + } + + @pragma('vm:prefer-inline') + int GetStringUTFLength(JString string) { + return value.ref.GetStringUTFLength + .asFunction<int Function(ffi.Pointer<JniEnv1>, JString)>()( + this, string); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<ffi.Char> GetStringUTFChars( + JString string, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetStringUTFChars.asFunction< + ffi.Pointer<ffi.Char> Function(ffi.Pointer<JniEnv1>, JString, + ffi.Pointer<JBoolean>)>()(this, string, isCopy); + } + + @pragma('vm:prefer-inline') + void ReleaseStringUTFChars(JString string, ffi.Pointer<ffi.Char> utf) { + return value.ref.ReleaseStringUTFChars.asFunction< + void Function(ffi.Pointer<JniEnv1>, JString, + ffi.Pointer<ffi.Char>)>()(this, string, utf); + } + + @pragma('vm:prefer-inline') + int GetArrayLength(JArray array) { + return value.ref.GetArrayLength + .asFunction<int Function(ffi.Pointer<JniEnv1>, JArray)>()(this, array); + } + + @pragma('vm:prefer-inline') + JObjectArray NewObjectArray( + int length, JClass elementClass, JObject initialElement) { + return value.ref.NewObjectArray.asFunction< + JObjectArray Function(ffi.Pointer<JniEnv1>, int, JClass, + JObject)>()(this, length, elementClass, initialElement); + } + + @pragma('vm:prefer-inline') + JObject GetObjectArrayElement(JObjectArray array, int index) { + return value.ref.GetObjectArrayElement.asFunction< + JObject Function( + ffi.Pointer<JniEnv1>, JObjectArray, int)>()(this, array, index); + } + + @pragma('vm:prefer-inline') + void SetObjectArrayElement(JObjectArray array, int index, JObject val) { + return value.ref.SetObjectArrayElement.asFunction< + void Function(ffi.Pointer<JniEnv1>, JObjectArray, int, JObject)>()( + this, array, index, val); + } + + @pragma('vm:prefer-inline') + JBooleanArray NewBooleanArray(int length) { + return value.ref.NewBooleanArray + .asFunction<JBooleanArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JByteArray NewByteArray(int length) { + return value.ref.NewByteArray + .asFunction<JByteArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JCharArray NewCharArray(int length) { + return value.ref.NewCharArray + .asFunction<JCharArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JShortArray NewShortArray(int length) { + return value.ref.NewShortArray + .asFunction<JShortArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JIntArray NewIntArray(int length) { + return value.ref.NewIntArray + .asFunction<JIntArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JLongArray NewLongArray(int length) { + return value.ref.NewLongArray + .asFunction<JLongArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JFloatArray NewFloatArray(int length) { + return value.ref.NewFloatArray + .asFunction<JFloatArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + JDoubleArray NewDoubleArray(int length) { + return value.ref.NewDoubleArray + .asFunction<JDoubleArray Function(ffi.Pointer<JniEnv1>, int)>()( + this, length); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JBoolean> GetBooleanArrayElements( + JBooleanArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetBooleanArrayElements.asFunction< + ffi.Pointer<JBoolean> Function(ffi.Pointer<JniEnv1>, JBooleanArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JByte> GetByteArrayElements( + JByteArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetByteArrayElements.asFunction< + ffi.Pointer<JByte> Function(ffi.Pointer<JniEnv1>, JByteArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JChar> GetCharArrayElements( + JCharArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetCharArrayElements.asFunction< + ffi.Pointer<JChar> Function(ffi.Pointer<JniEnv1>, JCharArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JShort> GetShortArrayElements( + JShortArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetShortArrayElements.asFunction< + ffi.Pointer<JShort> Function(ffi.Pointer<JniEnv1>, JShortArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JInt> GetIntArrayElements( + JIntArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetIntArrayElements.asFunction< + ffi.Pointer<JInt> Function(ffi.Pointer<JniEnv1>, JIntArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JLong> GetLongArrayElements( + JLongArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetLongArrayElements.asFunction< + ffi.Pointer<JLong> Function(ffi.Pointer<JniEnv1>, JLongArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JFloat> GetFloatArrayElements( + JFloatArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetFloatArrayElements.asFunction< + ffi.Pointer<JFloat> Function(ffi.Pointer<JniEnv1>, JFloatArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JDouble> GetDoubleArrayElements( + JDoubleArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetDoubleArrayElements.asFunction< + ffi.Pointer<JDouble> Function(ffi.Pointer<JniEnv1>, JDoubleArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + void ReleaseBooleanArrayElements( + JBooleanArray array, ffi.Pointer<JBoolean> elems, int mode) { + return value.ref.ReleaseBooleanArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JBooleanArray, + ffi.Pointer<JBoolean>, int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseByteArrayElements( + JByteArray array, ffi.Pointer<JByte> elems, int mode) { + return value.ref.ReleaseByteArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JByteArray, ffi.Pointer<JByte>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseCharArrayElements( + JCharArray array, ffi.Pointer<JChar> elems, int mode) { + return value.ref.ReleaseCharArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JCharArray, ffi.Pointer<JChar>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseShortArrayElements( + JShortArray array, ffi.Pointer<JShort> elems, int mode) { + return value.ref.ReleaseShortArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JShortArray, ffi.Pointer<JShort>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseIntArrayElements( + JIntArray array, ffi.Pointer<JInt> elems, int mode) { + return value.ref.ReleaseIntArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JIntArray, ffi.Pointer<JInt>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseLongArrayElements( + JLongArray array, ffi.Pointer<JLong> elems, int mode) { + return value.ref.ReleaseLongArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JLongArray, ffi.Pointer<JLong>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseFloatArrayElements( + JFloatArray array, ffi.Pointer<JFloat> elems, int mode) { + return value.ref.ReleaseFloatArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JFloatArray, ffi.Pointer<JFloat>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void ReleaseDoubleArrayElements( + JDoubleArray array, ffi.Pointer<JDouble> elems, int mode) { + return value.ref.ReleaseDoubleArrayElements.asFunction< + void Function(ffi.Pointer<JniEnv1>, JDoubleArray, ffi.Pointer<JDouble>, + int)>()(this, array, elems, mode); + } + + @pragma('vm:prefer-inline') + void GetBooleanArrayRegion( + JBooleanArray array, int start, int len, ffi.Pointer<JBoolean> buf) { + return value.ref.GetBooleanArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JBooleanArray, int, int, + ffi.Pointer<JBoolean>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetByteArrayRegion( + JByteArray array, int start, int len, ffi.Pointer<JByte> buf) { + return value.ref.GetByteArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JByteArray, int, int, + ffi.Pointer<JByte>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetCharArrayRegion( + JCharArray array, int start, int len, ffi.Pointer<JChar> buf) { + return value.ref.GetCharArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JCharArray, int, int, + ffi.Pointer<JChar>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetShortArrayRegion( + JShortArray array, int start, int len, ffi.Pointer<JShort> buf) { + return value.ref.GetShortArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JShortArray, int, int, + ffi.Pointer<JShort>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetIntArrayRegion( + JIntArray array, int start, int len, ffi.Pointer<JInt> buf) { + return value.ref.GetIntArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JIntArray, int, int, + ffi.Pointer<JInt>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetLongArrayRegion( + JLongArray array, int start, int len, ffi.Pointer<JLong> buf) { + return value.ref.GetLongArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JLongArray, int, int, + ffi.Pointer<JLong>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetFloatArrayRegion( + JFloatArray array, int start, int len, ffi.Pointer<JFloat> buf) { + return value.ref.GetFloatArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JFloatArray, int, int, + ffi.Pointer<JFloat>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetDoubleArrayRegion( + JDoubleArray array, int start, int len, ffi.Pointer<JDouble> buf) { + return value.ref.GetDoubleArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JDoubleArray, int, int, + ffi.Pointer<JDouble>)>()(this, array, start, len, buf); + } + + /// spec shows these without const; some jni.h do, some don't + /// + /// This is an automatically generated extension method + @pragma('vm:prefer-inline') + void SetBooleanArrayRegion( + JBooleanArray array, int start, int len, ffi.Pointer<JBoolean> buf) { + return value.ref.SetBooleanArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JBooleanArray, int, int, + ffi.Pointer<JBoolean>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetByteArrayRegion( + JByteArray array, int start, int len, ffi.Pointer<JByte> buf) { + return value.ref.SetByteArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JByteArray, int, int, + ffi.Pointer<JByte>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetCharArrayRegion( + JCharArray array, int start, int len, ffi.Pointer<JChar> buf) { + return value.ref.SetCharArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JCharArray, int, int, + ffi.Pointer<JChar>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetShortArrayRegion( + JShortArray array, int start, int len, ffi.Pointer<JShort> buf) { + return value.ref.SetShortArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JShortArray, int, int, + ffi.Pointer<JShort>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetIntArrayRegion( + JIntArray array, int start, int len, ffi.Pointer<JInt> buf) { + return value.ref.SetIntArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JIntArray, int, int, + ffi.Pointer<JInt>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetLongArrayRegion( + JLongArray array, int start, int len, ffi.Pointer<JLong> buf) { + return value.ref.SetLongArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JLongArray, int, int, + ffi.Pointer<JLong>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetFloatArrayRegion( + JFloatArray array, int start, int len, ffi.Pointer<JFloat> buf) { + return value.ref.SetFloatArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JFloatArray, int, int, + ffi.Pointer<JFloat>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + void SetDoubleArrayRegion( + JDoubleArray array, int start, int len, ffi.Pointer<JDouble> buf) { + return value.ref.SetDoubleArrayRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JDoubleArray, int, int, + ffi.Pointer<JDouble>)>()(this, array, start, len, buf); + } + + @pragma('vm:prefer-inline') + int RegisterNatives( + JClass clazz, ffi.Pointer<JNINativeMethod> methods, int nMethods) { + return value.ref.RegisterNatives.asFunction< + int Function(ffi.Pointer<JniEnv1>, JClass, ffi.Pointer<JNINativeMethod>, + int)>()(this, clazz, methods, nMethods); + } + + @pragma('vm:prefer-inline') + int UnregisterNatives(JClass clazz) { + return value.ref.UnregisterNatives + .asFunction<int Function(ffi.Pointer<JniEnv1>, JClass)>()(this, clazz); + } + + @pragma('vm:prefer-inline') + int MonitorEnter(JObject obj) { + return value.ref.MonitorEnter + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject)>()(this, obj); + } + + @pragma('vm:prefer-inline') + int MonitorExit(JObject obj) { + return value.ref.MonitorExit + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject)>()(this, obj); + } + + @pragma('vm:prefer-inline') + int GetJavaVM(ffi.Pointer<ffi.Pointer<JavaVM>> vm) { + return value.ref.GetJavaVM.asFunction< + int Function(ffi.Pointer<JniEnv1>, + ffi.Pointer<ffi.Pointer<JavaVM>>)>()(this, vm); + } + + @pragma('vm:prefer-inline') + void GetStringRegion( + JString str, int start, int len, ffi.Pointer<JChar> buf) { + return value.ref.GetStringRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JString, int, int, + ffi.Pointer<JChar>)>()(this, str, start, len, buf); + } + + @pragma('vm:prefer-inline') + void GetStringUTFRegion( + JString str, int start, int len, ffi.Pointer<ffi.Char> buf) { + return value.ref.GetStringUTFRegion.asFunction< + void Function(ffi.Pointer<JniEnv1>, JString, int, int, + ffi.Pointer<ffi.Char>)>()(this, str, start, len, buf); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<ffi.Void> GetPrimitiveArrayCritical( + JArray array, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetPrimitiveArrayCritical.asFunction< + ffi.Pointer<ffi.Void> Function(ffi.Pointer<JniEnv1>, JArray, + ffi.Pointer<JBoolean>)>()(this, array, isCopy); + } + + @pragma('vm:prefer-inline') + void ReleasePrimitiveArrayCritical( + JArray array, ffi.Pointer<ffi.Void> carray, int mode) { + return value.ref.ReleasePrimitiveArrayCritical.asFunction< + void Function(ffi.Pointer<JniEnv1>, JArray, ffi.Pointer<ffi.Void>, + int)>()(this, array, carray, mode); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<JChar> GetStringCritical( + JString str, ffi.Pointer<JBoolean> isCopy) { + return value.ref.GetStringCritical.asFunction< + ffi.Pointer<JChar> Function(ffi.Pointer<JniEnv1>, JString, + ffi.Pointer<JBoolean>)>()(this, str, isCopy); + } + + @pragma('vm:prefer-inline') + void ReleaseStringCritical(JString str, ffi.Pointer<JChar> carray) { + return value.ref.ReleaseStringCritical.asFunction< + void Function(ffi.Pointer<JniEnv1>, JString, ffi.Pointer<JChar>)>()( + this, str, carray); + } + + @pragma('vm:prefer-inline') + JWeak NewWeakGlobalRef(JObject obj) { + return value.ref.NewWeakGlobalRef + .asFunction<JWeak Function(ffi.Pointer<JniEnv1>, JObject)>()(this, obj); + } + + @pragma('vm:prefer-inline') + void DeleteWeakGlobalRef(JWeak obj) { + return value.ref.DeleteWeakGlobalRef + .asFunction<void Function(ffi.Pointer<JniEnv1>, JWeak)>()(this, obj); + } + + @pragma('vm:prefer-inline') + int ExceptionCheck() { + return value.ref.ExceptionCheck + .asFunction<int Function(ffi.Pointer<JniEnv1>)>()(this); + } + + @pragma('vm:prefer-inline') + JObject NewDirectByteBuffer(ffi.Pointer<ffi.Void> address, int capacity) { + return value.ref.NewDirectByteBuffer.asFunction< + JObject Function(ffi.Pointer<JniEnv1>, ffi.Pointer<ffi.Void>, + int)>()(this, address, capacity); + } + + @pragma('vm:prefer-inline') + ffi.Pointer<ffi.Void> GetDirectBufferAddress(JObject buf) { + return value.ref.GetDirectBufferAddress.asFunction< + ffi.Pointer<ffi.Void> Function( + ffi.Pointer<JniEnv1>, JObject)>()(this, buf); + } + + @pragma('vm:prefer-inline') + int GetDirectBufferCapacity(JObject buf) { + return value.ref.GetDirectBufferCapacity + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject)>()(this, buf); + } + + /// added in JNI 1.6 + /// + /// This is an automatically generated extension method + @pragma('vm:prefer-inline') + int GetObjectRefType(JObject obj) { + return value.ref.GetObjectRefType + .asFunction<int Function(ffi.Pointer<JniEnv1>, JObject)>()(this, obj); + } +} + +typedef JniEnv1 = ffi.Pointer<JNINativeInterface>; +typedef JClass = JObject; + +/// Reference types, in C. +typedef JObject = ffi.Pointer<ffi.Void>; +typedef JByte = ffi.Int8; + +/// "cardinal indices and sizes" +typedef JSize = JInt; +typedef JMethodID = ffi.Pointer<jmethodID_>; +typedef JFieldID = ffi.Pointer<jfieldID_>; + +/// Primitive types that match up with Java equivalents. +typedef JBoolean = ffi.Uint8; +typedef JThrowable = JObject; + +class __va_list_tag extends ffi.Struct { + @ffi.UnsignedInt() + external int gp_offset; + + @ffi.UnsignedInt() + external int fp_offset; + + external ffi.Pointer<ffi.Void> overflow_arg_area; + + external ffi.Pointer<ffi.Void> reg_save_area; +} + +class JValue extends ffi.Union { + @JBoolean() + external int z; + + @JByte() + external int b; + + @JChar() + external int c; + + @JShort() + external int s; + + @JInt() + external int i; + + @JLong() + external int j; + + @JFloat() + external double f; + + @JDouble() + external double d; + + external JObject l; +} + +typedef JChar = ffi.Uint16; +typedef JShort = ffi.Int16; +typedef JLong = ffi.Int64; +typedef JFloat = ffi.Float; +typedef JDouble = ffi.Double; +typedef JString = JObject; +typedef JArray = JObject; +typedef JObjectArray = JArray; +typedef JBooleanArray = JArray; +typedef JByteArray = JArray; +typedef JCharArray = JArray; +typedef JShortArray = JArray; +typedef JIntArray = JArray; +typedef JLongArray = JArray; +typedef JFloatArray = JArray; +typedef JDoubleArray = JArray; + +class JNINativeMethod extends ffi.Struct { + external ffi.Pointer<ffi.Char> name; + + external ffi.Pointer<ffi.Char> signature; + + external ffi.Pointer<ffi.Void> fnPtr; +} + +typedef JWeak = JObject; + +abstract class jobjectRefType { + static const int JNIInvalidRefType = 0; + static const int JNILocalRefType = 1; + static const int JNIGlobalRefType = 2; + static const int JNIWeakGlobalRefType = 3; +} + +/// C++ object wrapper. +/// +/// This is usually overlaid on a C struct whose first element is a +/// JNINativeInterface*. We rely somewhat on compiler behavior. +class _JNIEnv extends ffi.Struct { + /// do not rename this; it does not seem to be entirely opaque + external ffi.Pointer<JNINativeInterface> functions; +} + +/// C++ version. +class _JavaVM extends ffi.Struct { + external ffi.Pointer<JNIInvokeInterface> functions; +} + +class JavaVMAttachArgs extends ffi.Struct { + /// must be >= JNI_VERSION_1_2 + @JInt() + external int version; + + /// NULL or name of thread as modified UTF-8 str + external ffi.Pointer<ffi.Char> name; + + /// global ref of a ThreadGroup object, or NULL + external JObject group; +} + +/// JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no +/// longer supported.) +class JavaVMOption extends ffi.Struct { + external ffi.Pointer<ffi.Char> optionString; + + external ffi.Pointer<ffi.Void> extraInfo; +} + +class JavaVMInitArgs extends ffi.Struct { + /// use JNI_VERSION_1_2 or later + @JInt() + external int version; + + @JInt() + external int nOptions; + + external ffi.Pointer<JavaVMOption> options; + + @JBoolean() + external int ignoreUnrecognized; +} + +abstract class JniLogLevel { + static const int JNI_VERBOSE = 2; + static const int JNI_DEBUG = 3; + static const int JNI_INFO = 4; + static const int JNI_WARN = 5; + static const int JNI_ERROR = 6; +} + +const int JNI_FALSE = 0; + +const int JNI_TRUE = 1; + +const int JNI_VERSION_1_1 = 65537; + +const int JNI_VERSION_1_2 = 65538; + +const int JNI_VERSION_1_4 = 65540; + +const int JNI_VERSION_1_6 = 65542; + +const int JNI_OK = 0; + +const int JNI_ERR = -1; + +const int JNI_EDETACHED = -2; + +const int JNI_EVERSION = -3; + +const int JNI_ENOMEM = -4; + +const int JNI_EEXIST = -5; + +const int JNI_EINVAL = -6; + +const int JNI_COMMIT = 1; + +const int JNI_ABORT = 2; + +const String JNI_LOG_TAG = 'Dart-JNI';
diff --git a/pkgs/jni/pubspec.yaml b/pkgs/jni/pubspec.yaml index e1c1314..5d057a4 100644 --- a/pkgs/jni/pubspec.yaml +++ b/pkgs/jni/pubspec.yaml
@@ -1,79 +1,53 @@ name: jni -description: A new Flutter FFI plugin project. +description: Library to access JNI from dart and flutter version: 0.0.1 -homepage: +homepage: https://github.com/dart-lang/jnigen environment: - sdk: ">=2.17.5 <3.0.0" - flutter: ">=2.11.0" + sdk: ">=2.17.1 <3.0.0" + #flutter: ">=2.11.0" dependencies: - flutter: - sdk: flutter + ## Commented out to support dart standalone + # flutter: + # sdk: flutter plugin_platform_interface: ^2.0.2 + ffi: ^2.0.0 + path: ^1.8.0 + package_config: ^2.1.0 + args: ^2.3.1 dev_dependencies: - ffi: ^1.1.2 - ffigen: ^4.1.2 - flutter_test: - sdk: flutter + ## Temporarily linking to a personal fork of ffigen + ## so that changes in FFIGen can be reviewed. + # + ## If this is vendored directly, it will not be possible to + ## review changes in ffigen. + # + ## Will be removed in a later PR by vendoring the patched ffigen. + # + ## After running `dart run ffigen --config ffigen.yaml` there + ## should be no changes. + ## + ## TODO: vendor ffigen fork + ffigen: + git: https://github.com/mahesh-hegde/ffigen_patch_jni.git + #path: third_party/ffigen_patch flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec + test: ^1.21.1 # The following section is specific to Flutter packages. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. - # - # Please refer to README.md for a detailed explanation. plugin: platforms: - android: - ffiPlugin: true linux: ffiPlugin: true - macos: - ffiPlugin: true windows: ffiPlugin: true + macos: + ffiPlugin: true + android: + ffiPlugin: true + package: dev.dart.jni + pluginClass: JniPlugin - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni/src/.gitignore b/pkgs/jni/src/.gitignore new file mode 100644 index 0000000..2270743 --- /dev/null +++ b/pkgs/jni/src/.gitignore
@@ -0,0 +1,8 @@ +CMakeFiles/ +Makefile +cmake_install.cmake +libdartjni.so +CMakeCache.txt +.cache +compile_commands.json +
diff --git a/pkgs/jni/src/CMakeLists.txt b/pkgs/jni/src/CMakeLists.txt index c9ab651..e9cfd80 100644 --- a/pkgs/jni/src/CMakeLists.txt +++ b/pkgs/jni/src/CMakeLists.txt
@@ -6,12 +6,26 @@ project(jni_library VERSION 0.0.1 LANGUAGES C) add_library(jni SHARED - "jni.c" + "dartjni.c" ) set_target_properties(jni PROPERTIES - PUBLIC_HEADER jni.h - OUTPUT_NAME "jni" + PUBLIC_HEADER dartjni.h + OUTPUT_NAME "dartjni" ) target_compile_definitions(jni PUBLIC DART_SHARED_LIB) + +if(WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES + LINK_FLAGS "/DELAYLOAD:jvm.dll") +endif() + +if (ANDROID) + target_link_libraries(jni log) +else() + find_package(Java REQUIRED) + find_package(JNI REQUIRED) + include_directories(${JNI_INCLUDE_DIRS}) + target_link_libraries(jni ${JNI_LIBRARIES}) +endif()
diff --git a/pkgs/jni/src/README.md b/pkgs/jni/src/README.md new file mode 100644 index 0000000..c0c3119 --- /dev/null +++ b/pkgs/jni/src/README.md
@@ -0,0 +1,27 @@ +## LSP instructions + +If using an LSP based editor plugin and the syntax highlighting / code completion is not working, there are 2 ways to fix that. + +* __Create a compile_flags.txt with following content__: + +`-I<path_to_folder_containing_jni_headers>` + +This might need the OS specific include folder as well, so that transitively included headers can be found. Example: + +``` +-I/usr/lib/jvm/java-11-openjdk-amd64/include +-I/usr/lib/jvm/java-11-openjdk-amd64/include/linux +``` + +Note that this file should contain one compilation flag per line. + +* __create a compilation database by prefixing `bear` to your cmake build__. + +Run `cmake --build` command from your source, prefixed with `bear`. + +`bear -- cmake --build <build_dir>` + +On some distro versions of `bear` command, the `--` needs to be omitted. + +`bear cmake --build <build_dir>` +
diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c new file mode 100644 index 0000000..cf4e7d5 --- /dev/null +++ b/pkgs/jni/src/dartjni.c
@@ -0,0 +1,139 @@ +#include <jni.h> +#include <stdint.h> + +#include "dartjni.h" + +struct jni_context jni = {NULL, NULL, NULL, NULL, NULL}; + +thread_local JNIEnv *jniEnv = NULL; + +int jni_log_level = JNI_INFO; + +FFI_PLUGIN_EXPORT +void SetJNILogging(int level) { + jni_log_level = level; +} + +void jni_log(int level, const char *format, ...) { + // TODO: Not working + // IssueRef: https://github.com/dart-lang/jni_gen/issues/16 + if (level >= jni_log_level) { + va_list args; + va_start(args, format); +#ifdef __ANDROID__ + __android_log_print(level, JNI_LOG_TAG, format, args); +#else + // fprintf(stderr, "%s: ", JNI_LOG_TAG); + vfprintf(stderr, format, args); +#endif + va_end(args); + } +} + +/// Get JVM associated with current process. +/// Returns NULL if no JVM is running. +FFI_PLUGIN_EXPORT +JavaVM *GetJavaVM() { return jni.jvm; } + +/// Returns Application classLoader (on Android), +/// which can be used to load application and platform classes. +/// ... +/// On other platforms, NULL is returned. +FFI_PLUGIN_EXPORT +jobject GetClassLoader() { + attach_thread(); + return (*jniEnv)->NewLocalRef(jniEnv, jni.classLoader); +} + + +/// Load class through platform-specific mechanism +/// ... +/// Currently uses application classloader on android, +/// and JNIEnv->FindClass on other platforms. +FFI_PLUGIN_EXPORT +jclass LoadClass(const char *name) { + jclass cls = NULL; + attach_thread(); + load_class(&cls, name); + return cls; +}; + +FFI_PLUGIN_EXPORT +JNIEnv *GetJniEnv() { + if (jni.jvm == NULL) { + return NULL; + } + attach_thread(); + return jniEnv; +} + +/// Returns application context on Android. +/// +/// On other platforms, NULL is returned. +FFI_PLUGIN_EXPORT +jobject GetApplicationContext() { + // Any publicly callable method + // can be called from an unattached thread. + // I Learned this the hard way. + attach_thread(); + return (*jniEnv)->NewLocalRef(jniEnv, jni.appContext); +} + +/// Returns current activity of the app +FFI_PLUGIN_EXPORT +jobject GetCurrentActivity() { + attach_thread(); + return (*jniEnv)->NewLocalRef(jniEnv, jni.currentActivity); +} + +#ifdef __ANDROID__ +JNIEXPORT void JNICALL Java_dev_dart_jni_JniPlugin_initializeJni( + JNIEnv *env, jobject obj, jobject appContext, jobject classLoader) { + jniEnv = env; + (*env)->GetJavaVM(env, &jni.jvm); + jni.classLoader = (*env)->NewGlobalRef(env, classLoader); + jni.appContext = (*env)->NewGlobalRef(env, appContext); + jclass classLoaderClass = (*env)->GetObjectClass(env, classLoader); + jni.loadClassMethod = + (*env)->GetMethodID(env, classLoaderClass, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); +} + +JNIEXPORT void JNICALL Java_dev_dart_jni_JniPlugin_setJniActivity(JNIEnv *env, jobject obj, jobject activity, jobject context) { + jniEnv = env; + if (jni.currentActivity != NULL) { + (*env)->DeleteGlobalRef(env, jni.currentActivity); + } + jni.currentActivity = (*env)->NewGlobalRef(env, activity); + if (jni.appContext != NULL) { + (*env)->DeleteGlobalRef(env, jni.appContext); + } + jni.appContext = (*env)->NewGlobalRef(env, context); +} + +// Sometimes you may get linker error trying to link JNI_CreateJavaVM APIs +// on Android NDK. So IFDEF is required. +#else +FFI_PLUGIN_EXPORT +JNIEnv *SpawnJvm(JavaVMInitArgs *initArgs) { + JavaVMOption jvmopt[1]; + char class_path[] = "-Djava.class.path=."; + jvmopt[0].optionString = class_path; + JavaVMInitArgs vmArgs; + if (!initArgs) { + vmArgs.version = JNI_VERSION_1_2; + vmArgs.nOptions = 1; + vmArgs.options = jvmopt; + vmArgs.ignoreUnrecognized = JNI_TRUE; + initArgs = &vmArgs; + } + jni_log(JNI_DEBUG, "JNI Version: %d\n", initArgs->version); + const long flag = + JNI_CreateJavaVM(&jni.jvm, __ENVP_CAST &jniEnv, initArgs); + if (flag == JNI_ERR) { + return NULL; + } + return jniEnv; +} +#endif +
diff --git a/pkgs/jni/src/dartjni.h b/pkgs/jni/src/dartjni.h new file mode 100644 index 0000000..5b7fb5b --- /dev/null +++ b/pkgs/jni/src/dartjni.h
@@ -0,0 +1,123 @@ +#include <stdint.h> +#include <stdio.h> +#include <jni.h> +#include <stdlib.h> + +#if _WIN32 +#include <windows.h> +#else +#include <pthread.h> +#include <unistd.h> +#endif + +#if _WIN32 +#define FFI_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FFI_PLUGIN_EXPORT +#endif + +#if defined _WIN32 +#define thread_local __declspec(thread) +#else +#define thread_local __thread +#endif + +#ifdef __ANDROID__ +#include<android/log.h> +#endif + +#define JNI_LOG_TAG "Dart-JNI" + +#ifdef __ANDROID__ +#define __ENVP_CAST (JNIEnv **) +#else +#define __ENVP_CAST (void **) +#endif + +struct jni_context { + JavaVM *jvm; + jobject classLoader; + jmethodID loadClassMethod; + jobject currentActivity; + jobject appContext; +}; + +extern thread_local JNIEnv *jniEnv; +extern struct jni_context jni; + +enum DartJniLogLevel { + JNI_VERBOSE = 2, JNI_DEBUG, JNI_INFO, JNI_WARN, JNI_ERROR +}; + +FFI_PLUGIN_EXPORT JavaVM *GetJavaVM(void); + +FFI_PLUGIN_EXPORT JNIEnv *GetJniEnv(void); + +FFI_PLUGIN_EXPORT JNIEnv *SpawnJvm(JavaVMInitArgs *args); + +FFI_PLUGIN_EXPORT jclass LoadClass(const char *name); + +FFI_PLUGIN_EXPORT jobject GetClassLoader(void); + +FFI_PLUGIN_EXPORT jobject GetApplicationContext(void); + +FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void); + +FFI_PLUGIN_EXPORT void SetJNILogging(int level); + +/// For use by jni_gen's generated code +/// don't use these. + +// `static inline` because `inline` doesn't work, it may still not +// inline the function in which case a linker error may be produced. +// +// There has to be a better way to do this. Either to force inlining on target +// platforms, or just leave it as normal function. + +static inline void __load_class_into(jclass *cls, const char *name) { +#ifdef __ANDROID__ + jstring className = (*jniEnv)->NewStringUTF(jniEnv, name); + *cls = (*jniEnv)->CallObjectMethod( + jniEnv, jni.classLoader, jni.loadClassMethod, className); + (*jniEnv)->DeleteLocalRef(jniEnv, className); +#else + *cls = (*jniEnv)->FindClass(jniEnv, name); +#endif +} + +static inline void load_class(jclass *cls, const char *name) { + if (*cls == NULL) { + __load_class_into(cls, name); + } +} + +static inline void load_class_gr(jclass *cls, const char *name) { + if (*cls == NULL) { + jclass tmp; + __load_class_into(&tmp, name); + *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp); + (*jniEnv)->DeleteLocalRef(jniEnv, tmp); + } +} + +static inline void attach_thread() { + if (jniEnv == NULL) { + (*jni.jvm)->AttachCurrentThread(jni.jvm, __ENVP_CAST &jniEnv, + NULL); + } +} + +static inline void load_method(jclass cls, jmethodID *res, const char *name, + const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig); + } +} + +static inline void load_static_method(jclass cls, jmethodID *res, + const char *name, const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig); + } +} +
diff --git a/pkgs/jni/src/jni.c b/pkgs/jni/src/jni.c deleted file mode 100644 index d87e04e..0000000 --- a/pkgs/jni/src/jni.c +++ /dev/null
@@ -1,23 +0,0 @@ -#include "jni.h" - -// A very short-lived native function. -// -// For very short-lived functions, it is fine to call them on the main isolate. -// They will block the Dart execution while running the native function, so -// only do this for native functions which are guaranteed to be short-lived. -FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b) { return a + b; } - -// A longer-lived native function, which occupies the thread calling it. -// -// Do not call these kind of native functions in the main isolate. They will -// block Dart execution. This will cause dropped frames in Flutter applications. -// Instead, call these native functions on a separate isolate. -FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b) { - // Simulate work. -#if _WIN32 - Sleep(5000); -#else - usleep(5000 * 1000); -#endif - return a + b; -}
diff --git a/pkgs/jni/src/jni.h b/pkgs/jni/src/jni.h deleted file mode 100644 index 084c642..0000000 --- a/pkgs/jni/src/jni.h +++ /dev/null
@@ -1,30 +0,0 @@ -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> - -#if _WIN32 -#include <windows.h> -#else -#include <pthread.h> -#include <unistd.h> -#endif - -#if _WIN32 -#define FFI_PLUGIN_EXPORT __declspec(dllexport) -#else -#define FFI_PLUGIN_EXPORT -#endif - -// A very short-lived native function. -// -// For very short-lived functions, it is fine to call them on the main isolate. -// They will block the Dart execution while running the native function, so -// only do this for native functions which are guaranteed to be short-lived. -FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b); - -// A longer lived native function, which occupies the thread calling it. -// -// Do not call these kind of native functions in the main isolate. They will -// block Dart execution. This will cause dropped frames in Flutter applications. -// Instead, call these native functions on a separate isolate. -FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b);
diff --git a/pkgs/jni/test/exception_test.dart b/pkgs/jni/test/exception_test.dart new file mode 100644 index 0000000..9a3194c --- /dev/null +++ b/pkgs/jni/test/exception_test.dart
@@ -0,0 +1,58 @@ +import 'dart:io'; + +import 'package:test/test.dart'; + +import 'package:jni/jni.dart'; +import 'package:jni/jni_object.dart'; + +void main() { + if (!Platform.isAndroid) { + bool caught = false; + try { + // If library does not exist, a helpful exception should be thrown. + // we can't test this directly because + // `test` schedules functions asynchronously + Jni.spawn(helperDir: "wrong_dir"); + } on HelperNotFoundException catch (_) { + // stderr.write("\n$_\n"); + Jni.spawn(helperDir: "src/build"); + caught = true; + } + if (!caught) { + throw "Expected HelperNotFoundException\n" + "Read exception_test.dart for details."; + } + } + final jni = Jni.getInstance(); + + test("double free throws exception", () { + final r = jni.newInstance("java/util/Random", "()V", []); + r.delete(); + expect(r.delete, throwsA(isA<DoubleFreeException>())); + }); + + test("Use after free throws exception", () { + final r = jni.newInstance("java/util/Random", "()V", []); + r.delete(); + expect(() => r.callIntMethodByName("nextInt", "(I)I", [256]), + throwsA(isA<UseAfterFreeException>())); + }); + + test("An exception in JNI throws JniException in Dart", () { + final r = jni.newInstance("java/util/Random", "()V", []); + expect(() => r.callIntMethodByName("nextInt", "(I)I", [-1]), + throwsA(isA<JniException>())); + }); + // Using printStackTrace from env + /* + test("uncommented to print java stack trace", () { + final r = jni.newInstance("java/util/Random", "()V", []); + try { + r.callIntMethodByName("nextInt", "(I)I", [-1]); + } on JniException catch (e) { + jni.getEnv().printStackTrace(e); + // optionally rethrow error + } + }); + */ +}
diff --git a/pkgs/jni/test/jni_object_test.dart b/pkgs/jni/test/jni_object_test.dart new file mode 100644 index 0000000..c671323 --- /dev/null +++ b/pkgs/jni/test/jni_object_test.dart
@@ -0,0 +1,222 @@ +import 'dart:io'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:test/test.dart'; + +import 'package:jni/jni.dart'; +import 'package:jni/jni_object.dart'; + +void main() { + // Don't forget to initialize JNI. + if (!Platform.isAndroid) { + Jni.spawn(helperDir: "src/build"); + } + + final jni = Jni.getInstance(); + + // The API based on JniEnv is intended to closely mimic C API + // And thus can be too verbose for simple experimenting and one-off uses + // JniObject API provides an easier way to perform some common operations. + // + // However, this is only meant for experimenting and very simple uses. + // For anything complicated, use JNIGen (The main part of this GSoC project) + // which will be both more efficient and ergonomic. + test("Long.intValue() using JniObject", () { + // findJniClass on a Jni object returns a JniClass + // which wraps a local class reference and env, and + // provides convenience functions. + final longClass = jni.findJniClass("java/lang/Long"); + + // looks for a constructor with given signature. + // equivalently you can lookup a method with name <init> + final longCtor = longClass.getConstructorID("(J)V"); + + // note that the arguments are just passed as a list + final long = longClass.newObject(longCtor, [176]); + + final intValue = long.callIntMethodByName("intValue", "()I", []); + expect(intValue, equals(176)); + + // delete any JniObject and JniClass instances using .delete() after use. + long.delete(); + longClass.delete(); + }); + + test("call a static method using JniClass APIs", () { + // you can use wrapClass to wrap a raw JClass (which is basically void*) + // Original ref is saved & will be deleted when you delete the + // wrapped JniClass. + final integerClass = jni.wrapClass(jni.findClass("java/lang/Integer")); + final result = integerClass.callStaticObjectMethodByName( + "toHexString", "(I)Ljava/lang/String;", [31]); + + // if the object is supposed to be a Java string + // you can call asDartString on it. + final resultString = result.asDartString(); + + // Dart string is a copy, original object can be deleted. + result.delete(); + expect(resultString, equals("1f")); + + // Also don't forget to delete the class + integerClass.delete(); + }); + + test("Call method with null argument, expect exception", () { + final integerClass = jni.findJniClass("java/lang/Integer"); + expect( + () => integerClass.callStaticIntMethodByName( + "parseInt", "(Ljava/lang/String;)I", [nullptr]), + throwsException); + integerClass.delete(); + }); + + test("Try to find a non-exisiting class, expect exception", () { + expect(() => jni.findJniClass("java/lang/NotExists"), throwsException); + }); + + /// call<Type>MethodByName will be expensive if making same call many times + /// Use getMethodID to get a method ID and use it in subsequent calls + test("Example for using getMethodID", () { + final longClass = jni.findJniClass("java/lang/Long"); + final bitCountMethod = longClass.getStaticMethodID("bitCount", "(J)I"); + + // Use newInstance if you want only one instance. + // It finds the class, gets constructor ID and constructs an instance. + final random = jni.newInstance("java/util/Random", "()V", []); + + // You don't need a JniClass reference to get instance method IDs + final nextIntMethod = random.getMethodID("nextInt", "(I)I"); + + for (int i = 0; i < 100; i++) { + int r = random.callIntMethod(nextIntMethod, [256 * 256]); + int bits = 0; + final jbc = + longClass.callStaticIntMethod(bitCountMethod, [JValueLong(r)]); + while (r != 0) { + bits += r % 2; + r = (r / 2).floor(); + } + expect(jbc, equals(bits)); + } + + random.delete(); + longClass.delete(); + }); + + // Actually it's not even required to get a reference to class + test("invoke_", () { + final m = jni.invokeLongMethod( + "java/lang/Long", "min", "(JJ)J", [JValueLong(1234), JValueLong(1324)]); + expect(m, equals(1234)); + }); + + test("retrieve_", () { + final maxLong = jni.retrieveShortField("java/lang/Short", "MAX_VALUE", "S"); + expect(maxLong, equals(32767)); + }); + + // Use callStringMethod if all you care about is a string result + test("callStaticStringMethod", () { + final longClass = jni.findJniClass("java/lang/Long"); + const n = 1223334444; + final strFromJava = longClass.callStaticStringMethodByName( + "toOctalString", "(J)Ljava/lang/String;", [JValueLong(n)]); + expect(strFromJava, equals(n.toRadixString(8))); + longClass.delete(); + }); + + // In JniObject, JniClass, and retrieve_/invoke_ methods + // you can also pass Dart strings, apart from range of types + // allowed by Jni.jvalues + // They will be converted automatically. + test("Passing strings in arguments", () { + final out = jni.retrieveObjectField( + "java/lang/System", "out", "Ljava/io/PrintStream;"); + // uncomment next line to see output + // (\n because test runner prints first char at end of the line) + //out.callVoidMethodByName( + // "println", "(Ljava/lang/Object;)V", ["\nWorks (Apparently)"]); + out.delete(); + }); + + test("Passing strings in arguments 2", () { + final twelve = jni.invokeByteMethod( + "java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", ["12"]); + expect(twelve, equals(12)); + }); + + // You can use() method on JniObject for using once and deleting + test("use() method", () { + final randomInt = jni.newInstance("java/util/Random", "()V", []).use( + (random) => random.callIntMethodByName("nextInt", "(I)I", [15])); + expect(randomInt, lessThan(15)); + }); + + test("enums", () { + // Don't forget to escape $ in nested type names + final ordinal = jni + .retrieveObjectField( + "java/net/Proxy\$Type", "HTTP", "Ljava/net/Proxy\$Type;") + .use((f) => f.callIntMethodByName("ordinal", "()I", [])); + expect(ordinal, equals(1)); + }); + + test("Isolate", () { + Isolate.spawn(doSomeWorkInIsolate, null); + }); + + // JniObject is valid only in thread it is obtained + // so it can be safely shared with a function that can run in + // different thread. + // + // Eg: Dart has a thread pool, which means async methods may get scheduled + // in different thread. + // + // In that case, convert the JniObject into `JniGlobalObjectRef` using + // getGlobalRef() and reconstruct the object in use site using fromJniObject + // constructor. + test("JniGlobalRef", () async { + final uri = jni.invokeObjectMethod( + "java/net/URI", + "create", + "(Ljava/lang/String;)Ljava/net/URI;", + ["https://www.google.com/search"]); + final rg = uri.getGlobalRef(); + await Future.delayed(const Duration(seconds: 1), () { + final env = jni.getEnv(); + // Now comment this line & try to directly use uri local ref + // in outer scope. + // + // You will likely get a segfault, because Future computation is running + // in different thread. + // + // Therefore, don't share JniObjects across functions that can be + // scheduled across threads, including async callbacks. + final uri = JniObject.fromGlobalRef(env, rg); + final scheme = + uri.callStringMethodByName("getScheme", "()Ljava/lang/String;", []); + expect(scheme, "https"); + uri.delete(); + rg.deleteIn(env); + }); + uri.delete(); + }); +} + +void doSomeWorkInIsolate(Void? _) { + // On standalone target, make sure to call load + // when doing getInstance first time in a new isolate. + // + // otherwise getInstance will throw a "library not found" exception. + Jni.load(helperDir: "src/build"); + final jni = Jni.getInstance(); + final random = jni.newInstance("java/util/Random", "()V", []); + // final r = random.callIntMethodByName("nextInt", "(I)I", [256]); + // expect(r, lessThan(256)); + // Expect throws an OutsideTestException + // but you can uncomment below print and see it works + // print("\n$r"); + random.delete(); +}
diff --git a/pkgs/jni/test/jni_test.dart b/pkgs/jni/test/jni_test.dart new file mode 100644 index 0000000..9982cf1 --- /dev/null +++ b/pkgs/jni/test/jni_test.dart
@@ -0,0 +1,123 @@ +import 'dart:io'; +import 'dart:ffi'; + +import 'package:test/test.dart'; +import 'package:ffi/ffi.dart'; +import 'package:jni/jni.dart'; + +void main() { + // Running on Android through flutter, this plugin + // will bind to Android runtime's JVM. + // On other platforms eg Flutter desktop or Dart standalone + // You need to manually create a JVM at beginning. + // + // On flutter desktop, the C wrappers are bundled, and helperPath param + // is not required. + // + // On dart standalone, however, there's no way to bundle the wrappers. + // You have to manually pass the path to the `dartjni` dynamic library. + + if (!Platform.isAndroid) { + Jni.spawn(helperDir: "src/build"); + } + + final jni = Jni.getInstance(); + + test('get JNI Version', () { + // get a dart binding of JNIEnv object + // It's a thin wrapper over C's JNIEnv*, and provides + // all methods of it (without need to pass the first self parameter), + // plus few extension methods to make working in dart easier. + final env = jni.getEnv(); + expect(env.GetVersion(), isNot(equals(0))); + }); + + test('Manually lookup & call Long.toHexString static method', () { + // create an arena for allocating anything native + // it's convenient way to release all natively allocated strings + // and values at once. + final arena = Arena(); + final env = jni.getEnv(); + + // Method names on JniEnv* from C JNI API are capitalized + // like in original, while other extension methods + // follow Dart naming conventions. + final longClass = env.FindClass("java/lang/Long".toNativeChars(arena)); + // Refer JNI spec on how to construct method signatures + // Passing wrong signature leads to a segfault + final hexMethod = env.GetStaticMethodID( + longClass, + "toHexString".toNativeChars(arena), + "(J)Ljava/lang/String;".toNativeChars(arena)); + + for (var i in [1, 80, 13, 76, 1134453224145]) { + // Use Jni.jvalues method to easily construct native argument arrays + // if your argument is int, bool, or JObject (`Pointer<Void>`) + // it can be directly placed in the list. To convert into different primitive + // types, use JValue<Type> wrappers. + final jres = env.CallStaticObjectMethodA( + longClass, hexMethod, Jni.jvalues([JValueLong(i)], allocator: arena)); + + // use asDartString extension method on Pointer<JniEnv> + // to convert a String jobject result to string + final res = env.asDartString(jres); + expect(res, equals(i.toRadixString(16))); + + // Any object or class result from java is a local reference + // and needs to be deleted explicitly. + // Note that method and field IDs aren't local references. + // But they are valid only until a reference to corresponding + // java class exists. + env.DeleteLocalRef(jres); + } + env.DeleteLocalRef(longClass); + arena.releaseAll(); + }); + + test("asJString extension method", () { + final env = jni.getEnv(); + const str = "QWERTY QWERTY"; + // convenience method that wraps + // converting dart string to native string, + // instantiating java string, and freeing the native string + final jstr = env.asJString(str); + expect(str, equals(env.asDartString(jstr))); + env.DeleteLocalRef(jstr); + }); + + test("Convert back and forth between dart and java string", () { + final arena = Arena(); + final env = jni.getEnv(); + const str = "ABCD EFGH"; + // This is what asJString and asDartString do internally + final jstr = env.NewStringUTF(str.toNativeChars(arena)); + final jchars = env.GetStringUTFChars(jstr, nullptr); + final dstr = jchars.toDartString(); + env.ReleaseStringUTFChars(jstr, jchars); + expect(str, equals(dstr)); + + // delete multiple local references using this method + env.deleteAllLocalRefs([jstr]); + arena.releaseAll(); + }); + + test("Print something from Java", () { + final arena = Arena(); + final env = jni.getEnv(); + final system = env.FindClass("java/lang/System".toNativeChars(arena)); + final field = env.GetStaticFieldID(system, "out".toNativeChars(arena), + "Ljava/io/PrintStream;".toNativeChars(arena)); + final out = env.GetStaticObjectField(system, field); + final printStream = env.GetObjectClass(out); + /* + final println = env.GetMethodID(printStream, "println".toNativeChars(arena), + "(Ljava/lang/String;)V".toNativeChars(arena)); + */ + const str = "\nHello JNI!"; + final jstr = env.asJString(str); + // test runner can't compare what's printed by Java, leaving it + // env.CallVoidMethodA(out, println, Jni.jvalues([jstr])); + env.deleteAllLocalRefs([system, printStream, jstr]); + arena.releaseAll(); + }); +}
diff --git a/pkgs/jni/third_party/jni.h b/pkgs/jni/third_party/jni.h new file mode 100644 index 0000000..1e46927 --- /dev/null +++ b/pkgs/jni/third_party/jni.h
@@ -0,0 +1,1146 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * JNI specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + +/* ANNOTATED COPY FOR DART JNI LIBRARY */ + +#pragma once + +#include <stdarg.h> +#include <stdint.h> + +/* Primitive types that match up with Java equivalents. */ +typedef uint8_t jboolean; /* unsigned 8 bits */ +typedef int8_t jbyte; /* signed 8 bits */ +typedef uint16_t jchar; /* unsigned 16 bits */ +typedef int16_t jshort; /* signed 16 bits */ +typedef int32_t jint; /* signed 32 bits */ +typedef int64_t jlong; /* signed 64 bits */ +typedef float jfloat; /* 32-bit IEEE 754 */ +typedef double jdouble; /* 64-bit IEEE 754 */ + +/* "cardinal indices and sizes" */ +typedef jint jsize; + +#ifdef __cplusplus +/* + * Reference types, in C++ + */ +class _jobject {}; +class _jclass : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jobjectArray : public _jarray {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jthrowable : public _jobject {}; + +typedef _jobject* jobject; +typedef _jclass* jclass; +typedef _jstring* jstring; +typedef _jarray* jarray; +typedef _jobjectArray* jobjectArray; +typedef _jbooleanArray* jbooleanArray; +typedef _jbyteArray* jbyteArray; +typedef _jcharArray* jcharArray; +typedef _jshortArray* jshortArray; +typedef _jintArray* jintArray; +typedef _jlongArray* jlongArray; +typedef _jfloatArray* jfloatArray; +typedef _jdoubleArray* jdoubleArray; +typedef _jthrowable* jthrowable; +typedef _jobject* jweak; + + +#else /* not __cplusplus */ + +/* + * Reference types, in C. + */ +typedef void* jobject; +typedef jobject jclass; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jobjectArray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jobject jthrowable; +typedef jobject jweak; + +#endif /* not __cplusplus */ + +struct _jfieldID; /* opaque structure */ +typedef struct _jfieldID* jfieldID; /* field IDs */ + +struct _jmethodID; /* opaque structure */ +typedef struct _jmethodID* jmethodID; /* method IDs */ + +struct JNIInvokeInterface; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +typedef enum jobjectRefType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + +typedef struct { + const char* name; + const char* signature; + void* fnPtr; +} JNINativeMethod; + +struct _JNIEnv; +struct _JavaVM; +typedef const struct JNINativeInterface* C_JNIEnv; + +#if defined(__cplusplus) +typedef _JNIEnv JNIEnv; +typedef _JavaVM JavaVM; +#else +typedef const struct JNINativeInterface* JNIEnv; +typedef const struct JNIInvokeInterface* JavaVM; +#endif + +/* + * Table of interface function pointers. + */ +struct JNINativeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + void* reserved3; + + jint (*GetVersion)(JNIEnv *env); + + jclass (*DefineClass)(JNIEnv *env, const char* name, jobject loader, const jbyte* buf, + jsize bufLen); + jclass (*FindClass)(JNIEnv* env, const char* name); + + jmethodID (*FromReflectedMethod)(JNIEnv* env, jobject method); + jfieldID (*FromReflectedField)(JNIEnv* env, jobject field); + + /* spec doesn't show jboolean parameter */ + + jobject (*ToReflectedMethod)(JNIEnv* env, jclass cls, jmethodID methodId, jboolean isStatic); + + jclass (*GetSuperclass)(JNIEnv* env, jclass clazz); + jboolean (*IsAssignableFrom)(JNIEnv* env, jclass clazz1, jclass clazz2); + + /* spec doesn't show jboolean parameter */ + + jobject (*ToReflectedField)(JNIEnv* env, jclass cls, jfieldID fieldID, jboolean isStatic); + + jint (*Throw)(JNIEnv* env, jthrowable obj); + jint (*ThrowNew)(JNIEnv *env, jclass clazz, const char *message); + jthrowable (*ExceptionOccurred)(JNIEnv* env); + void (*ExceptionDescribe)(JNIEnv* env); + void (*ExceptionClear)(JNIEnv* env); + void (*FatalError)(JNIEnv* env, const char* msg); + + jint (*PushLocalFrame)(JNIEnv* env, jint capacity); + jobject (*PopLocalFrame)(JNIEnv* env, jobject result); + + jobject (*NewGlobalRef)(JNIEnv* env, jobject obj); + void (*DeleteGlobalRef)(JNIEnv* env, jobject globalRef); + void (*DeleteLocalRef)(JNIEnv* env, jobject localRef); + jboolean (*IsSameObject)(JNIEnv* env, jobject ref1, jobject ref2); + + jobject (*NewLocalRef)(JNIEnv* env, jobject ref); + jint (*EnsureLocalCapacity)(JNIEnv* env, jint capacity); + + jobject (*AllocObject)(JNIEnv* env, jclass clazz); + jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); + jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*NewObjectA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + + jclass (*GetObjectClass)(JNIEnv* env, jobject obj); + jboolean (*IsInstanceOf)(JNIEnv* env, jobject obj, jclass clazz); + jmethodID (*GetMethodID)(JNIEnv* env, jclass clazz, const char* name, const char* sig); + + jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); + jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jobject (*CallObjectMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); + jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jboolean (*CallBooleanMethodA)(JNIEnv* env, jobject obj, jmethodID methodId, const jvalue* args); + jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); + jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jbyte (*CallByteMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); + jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jchar (*CallCharMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); + jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jshort (*CallShortMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); + jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jint (*CallIntMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); + jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jlong (*CallLongMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); + jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jfloat (*CallFloatMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); + jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jdouble (*CallDoubleMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); + void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list); + void (*CallVoidMethodA)(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args); + + jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jobject (*CallNonvirtualObjectMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jbyte (*CallNonvirtualByteMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jchar (*CallNonvirtualCharMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jshort (*CallNonvirtualShortMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jint (*CallNonvirtualIntMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jlong (*CallNonvirtualLongMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jfloat (*CallNonvirtualFloatMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + void (*CallNonvirtualVoidMethodA)(JNIEnv* env, jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args); + + jfieldID (*GetFieldID)(JNIEnv* env, jclass clazz, const char* name, const char* sig); + + jobject (*GetObjectField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jboolean (*GetBooleanField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jbyte (*GetByteField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jchar (*GetCharField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jshort (*GetShortField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jint (*GetIntField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jlong (*GetLongField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jfloat (*GetFloatField)(JNIEnv* env, jobject obj, jfieldID fieldID); + jdouble (*GetDoubleField)(JNIEnv* env, jobject obj, jfieldID fieldID); + + void (*SetObjectField)(JNIEnv* env, jobject obj, jfieldID fieldID, jobject val); + void (*SetBooleanField)(JNIEnv* env, jobject obj, jfieldID fieldID, jboolean val); + void (*SetByteField)(JNIEnv* env, jobject obj, jfieldID fieldID, jbyte val); + void (*SetCharField)(JNIEnv* env, jobject obj, jfieldID fieldID, jchar val); + void (*SetShortField)(JNIEnv* env, jobject obj, jfieldID fieldID, jshort val); + void (*SetIntField)(JNIEnv* env, jobject obj, jfieldID fieldID, jint val); + void (*SetLongField)(JNIEnv* env, jobject obj, jfieldID fieldID, jlong val); + void (*SetFloatField)(JNIEnv* env, jobject obj, jfieldID fieldID, jfloat val); + void (*SetDoubleField)(JNIEnv* env, jobject obj, jfieldID fieldID, jdouble val); + + jmethodID (*GetStaticMethodID)(JNIEnv* env, jclass clazz, const char* name, const char* sig); + + jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); + jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*CallStaticObjectMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); + jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID, + va_list); + jboolean (*CallStaticBooleanMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); + jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jbyte (*CallStaticByteMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); + jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jchar (*CallStaticCharMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); + jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jshort (*CallStaticShortMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); + jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jint (*CallStaticIntMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); + jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jlong (*CallStaticLongMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); + jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jfloat (*CallStaticFloatMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); + jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jdouble (*CallStaticDoubleMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); + void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list); + void (*CallStaticVoidMethodA)(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args); + + jfieldID (*GetStaticFieldID)(JNIEnv* env, jclass clazz, const char* name, + const char* sig); + + jobject (*GetStaticObjectField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jboolean (*GetStaticBooleanField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jbyte (*GetStaticByteField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jchar (*GetStaticCharField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jshort (*GetStaticShortField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jint (*GetStaticIntField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jlong (*GetStaticLongField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jfloat (*GetStaticFloatField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + jdouble (*GetStaticDoubleField)(JNIEnv* env, jclass clazz, jfieldID fieldID); + + void (*SetStaticObjectField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jobject val); + void (*SetStaticBooleanField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jboolean val); + void (*SetStaticByteField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jbyte val); + void (*SetStaticCharField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jchar val); + void (*SetStaticShortField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jshort val); + void (*SetStaticIntField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jint val); + void (*SetStaticLongField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jlong val); + void (*SetStaticFloatField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jfloat val); + void (*SetStaticDoubleField)(JNIEnv* env, jclass clazz, jfieldID fieldID, jdouble val); + + jstring (*NewString)(JNIEnv* env, const jchar* unicodeChars, jsize len); + jsize (*GetStringLength)(JNIEnv* env, jstring string); + const jchar* (*GetStringChars)(JNIEnv* env, jstring string, jboolean* isCopy); + void (*ReleaseStringChars)(JNIEnv* env, jstring string, const jchar* isCopy); + jstring (*NewStringUTF)(JNIEnv* env, const char* bytes); + jsize (*GetStringUTFLength)(JNIEnv* env, jstring string); + const char* (*GetStringUTFChars)(JNIEnv* env, jstring string, jboolean* isCopy); + void (*ReleaseStringUTFChars)(JNIEnv* env, jstring string, const char* utf); + jsize (*GetArrayLength)(JNIEnv* env, jarray array); + jobjectArray (*NewObjectArray)(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement); + jobject (*GetObjectArrayElement)(JNIEnv* env, jobjectArray array, jsize index); + void (*SetObjectArrayElement)(JNIEnv* env, jobjectArray array, jsize index, jobject val); + + jbooleanArray (*NewBooleanArray)(JNIEnv* env, jsize length); + jbyteArray (*NewByteArray)(JNIEnv* env, jsize length); + jcharArray (*NewCharArray)(JNIEnv* env, jsize length); + jshortArray (*NewShortArray)(JNIEnv* env, jsize length); + jintArray (*NewIntArray)(JNIEnv* env, jsize length); + jlongArray (*NewLongArray)(JNIEnv* env, jsize length); + jfloatArray (*NewFloatArray)(JNIEnv* env, jsize length); + jdoubleArray (*NewDoubleArray)(JNIEnv* env, jsize length); + + jboolean* (*GetBooleanArrayElements)(JNIEnv* env, jbooleanArray array, jboolean* isCopy); + jbyte* (*GetByteArrayElements)(JNIEnv* env, jbyteArray array, jboolean* isCopy); + jchar* (*GetCharArrayElements)(JNIEnv* env, jcharArray array, jboolean* isCopy); + jshort* (*GetShortArrayElements)(JNIEnv* env, jshortArray array, jboolean* isCopy); + jint* (*GetIntArrayElements)(JNIEnv* env, jintArray array, jboolean* isCopy); + jlong* (*GetLongArrayElements)(JNIEnv* env, jlongArray array, jboolean* isCopy); + jfloat* (*GetFloatArrayElements)(JNIEnv* env, jfloatArray array, jboolean* isCopy); + jdouble* (*GetDoubleArrayElements)(JNIEnv* env, jdoubleArray array, jboolean* isCopy); + + void (*ReleaseBooleanArrayElements)(JNIEnv* env, jbooleanArray array, + jboolean* elems, jint mode); + void (*ReleaseByteArrayElements)(JNIEnv* env, jbyteArray array, + jbyte* elems, jint mode); + void (*ReleaseCharArrayElements)(JNIEnv* env, jcharArray array, + jchar* elems, jint mode); + void (*ReleaseShortArrayElements)(JNIEnv* env, jshortArray array, + jshort* elems, jint mode); + void (*ReleaseIntArrayElements)(JNIEnv* env, jintArray array, + jint* elems, jint mode); + void (*ReleaseLongArrayElements)(JNIEnv* env, jlongArray array, + jlong* elems, jint mode); + void (*ReleaseFloatArrayElements)(JNIEnv* env, jfloatArray array, + jfloat* elems, jint mode); + void (*ReleaseDoubleArrayElements)(JNIEnv* env, jdoubleArray array, + jdouble* elems, jint mode); + + void (*GetBooleanArrayRegion)(JNIEnv* env, jbooleanArray array, + jsize start, jsize len, jboolean* buf); + void (*GetByteArrayRegion)(JNIEnv* env, jbyteArray array, + jsize start, jsize len, jbyte* buf); + void (*GetCharArrayRegion)(JNIEnv* env, jcharArray array, + jsize start, jsize len, jchar* buf); + void (*GetShortArrayRegion)(JNIEnv* env, jshortArray array, + jsize start, jsize len, jshort* buf); + void (*GetIntArrayRegion)(JNIEnv* env, jintArray array, + jsize start, jsize len, jint* buf); + void (*GetLongArrayRegion)(JNIEnv* env, jlongArray array, + jsize start, jsize len, jlong* buf); + void (*GetFloatArrayRegion)(JNIEnv* env, jfloatArray array, + jsize start, jsize len, jfloat* buf); + void (*GetDoubleArrayRegion)(JNIEnv* env, jdoubleArray array, + jsize start, jsize len, jdouble* buf); + + /* spec shows these without const; some jni.h do, some don't */ + void (*SetBooleanArrayRegion)(JNIEnv* env, jbooleanArray array, + jsize start, jsize len, const jboolean* buf); + void (*SetByteArrayRegion)(JNIEnv* env, jbyteArray array, + jsize start, jsize len, const jbyte* buf); + void (*SetCharArrayRegion)(JNIEnv* env, jcharArray array, + jsize start, jsize len, const jchar* buf); + void (*SetShortArrayRegion)(JNIEnv* env, jshortArray array, + jsize start, jsize len, const jshort* buf); + void (*SetIntArrayRegion)(JNIEnv* env, jintArray array, + jsize start, jsize len, const jint* buf); + void (*SetLongArrayRegion)(JNIEnv* env, jlongArray array, + jsize start, jsize len, const jlong* buf); + void (*SetFloatArrayRegion)(JNIEnv* env, jfloatArray array, + jsize start, jsize len, const jfloat* buf); + void (*SetDoubleArrayRegion)(JNIEnv* env, jdoubleArray array, + jsize start, jsize len, const jdouble* buf); + + jint (*RegisterNatives)(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, + jint nMethods); + jint (*UnregisterNatives)(JNIEnv* env, jclass clazz); + jint (*MonitorEnter)(JNIEnv* env, jobject obj); + jint (*MonitorExit)(JNIEnv* env, jobject obj); + jint (*GetJavaVM)(JNIEnv* env, JavaVM** vm); + + void (*GetStringRegion)(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf); + void (*GetStringUTFRegion)(JNIEnv* env, jstring str, jsize start, jsize len, char* buf); + + void* (*GetPrimitiveArrayCritical)(JNIEnv* env, jarray array, jboolean* isCopy); + void (*ReleasePrimitiveArrayCritical)(JNIEnv* env, jarray array, void* carray, jint mode); + + const jchar* (*GetStringCritical)(JNIEnv* env, jstring str, jboolean* isCopy); + void (*ReleaseStringCritical)(JNIEnv* env, jstring str, const jchar* carray); + + jweak (*NewWeakGlobalRef)(JNIEnv* env, jobject obj); + void (*DeleteWeakGlobalRef)(JNIEnv* env, jweak obj); + + jboolean (*ExceptionCheck)(JNIEnv* env); + + jobject (*NewDirectByteBuffer)(JNIEnv* env, void* address, jlong capacity); + void* (*GetDirectBufferAddress)(JNIEnv* env, jobject buf); + jlong (*GetDirectBufferCapacity)(JNIEnv* env, jobject buf); + + /* added in JNI 1.6 */ + jobjectRefType (*GetObjectRefType)(JNIEnv* env, jobject obj); +}; + +/* + * C++ object wrapper. + * + * This is usually overlaid on a C struct whose first element is a + * JNINativeInterface*. We rely somewhat on compiler behavior. + */ +struct _JNIEnv { + /* do not rename this; it does not seem to be entirely opaque */ + const struct JNINativeInterface* functions; + +#if defined(__cplusplus) + + jint GetVersion() + { return functions->GetVersion(this); } + + jclass DefineClass(const char *name, jobject loader, const jbyte* buf, + jsize bufLen) + { return functions->DefineClass(this, name, loader, buf, bufLen); } + + jclass FindClass(const char* name) + { return functions->FindClass(this, name); } + + jmethodID FromReflectedMethod(jobject method) + { return functions->FromReflectedMethod(this, method); } + + jfieldID FromReflectedField(jobject field) + { return functions->FromReflectedField(this, field); } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) + { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } + + jclass GetSuperclass(jclass clazz) + { return functions->GetSuperclass(this, clazz); } + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) + { return functions->IsAssignableFrom(this, clazz1, clazz2); } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) + { return functions->ToReflectedField(this, cls, fieldID, isStatic); } + + jint Throw(jthrowable obj) + { return functions->Throw(this, obj); } + + jint ThrowNew(jclass clazz, const char* message) + { return functions->ThrowNew(this, clazz, message); } + + jthrowable ExceptionOccurred() + { return functions->ExceptionOccurred(this); } + + void ExceptionDescribe() + { functions->ExceptionDescribe(this); } + + void ExceptionClear() + { functions->ExceptionClear(this); } + + void FatalError(const char* msg) + { functions->FatalError(this, msg); } + + jint PushLocalFrame(jint capacity) + { return functions->PushLocalFrame(this, capacity); } + + jobject PopLocalFrame(jobject result) + { return functions->PopLocalFrame(this, result); } + + jobject NewGlobalRef(jobject obj) + { return functions->NewGlobalRef(this, obj); } + + void DeleteGlobalRef(jobject globalRef) + { functions->DeleteGlobalRef(this, globalRef); } + + void DeleteLocalRef(jobject localRef) + { functions->DeleteLocalRef(this, localRef); } + + jboolean IsSameObject(jobject ref1, jobject ref2) + { return functions->IsSameObject(this, ref1, ref2); } + + jobject NewLocalRef(jobject ref) + { return functions->NewLocalRef(this, ref); } + + jint EnsureLocalCapacity(jint capacity) + { return functions->EnsureLocalCapacity(this, capacity); } + + jobject AllocObject(jclass clazz) + { return functions->AllocObject(this, clazz); } + + jobject NewObject(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + jobject result = functions->NewObjectV(this, clazz, methodID, args); + va_end(args); + return result; + } + + jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) + { return functions->NewObjectV(this, clazz, methodID, args); } + + jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args) + { return functions->NewObjectA(this, clazz, methodID, args); } + + jclass GetObjectClass(jobject obj) + { return functions->GetObjectClass(this, obj); } + + jboolean IsInstanceOf(jobject obj, jclass clazz) + { return functions->IsInstanceOf(this, obj, clazz); } + + jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetMethodID(this, clazz, name, sig); } + +#define CALL_TYPE_METHOD(_jtype, _jname) \ + _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->Call##_jname##MethodV(this, obj, methodID, \ + args); \ + va_end(args); \ + return result; \ + } +#define CALL_TYPE_METHODV(_jtype, _jname) \ + _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \ + va_list args) \ + { return functions->Call##_jname##MethodV(this, obj, methodID, args); } +#define CALL_TYPE_METHODA(_jtype, _jname) \ + _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \ + const jvalue* args) \ + { return functions->Call##_jname##MethodA(this, obj, methodID, args); } + +#define CALL_TYPE(_jtype, _jname) \ + CALL_TYPE_METHOD(_jtype, _jname) \ + CALL_TYPE_METHODV(_jtype, _jname) \ + CALL_TYPE_METHODA(_jtype, _jname) + + CALL_TYPE(jobject, Object) + CALL_TYPE(jboolean, Boolean) + CALL_TYPE(jbyte, Byte) + CALL_TYPE(jchar, Char) + CALL_TYPE(jshort, Short) + CALL_TYPE(jint, Int) + CALL_TYPE(jlong, Long) + CALL_TYPE(jfloat, Float) + CALL_TYPE(jdouble, Double) + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallVoidMethodV(this, obj, methodID, args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) + { functions->CallVoidMethodV(this, obj, methodID, args); } + void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args) + { functions->CallVoidMethodA(this, obj, methodID, args); } + +#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz, \ + jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallNonvirtual##_jname##MethodV(this, obj, \ + clazz, methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz, \ + jmethodID methodID, va_list args) \ + { return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz, \ + methodID, args); } +#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz, \ + jmethodID methodID, const jvalue* args) \ + { return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz, \ + methodID, args); } + +#define CALL_NONVIRT_TYPE(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) + + CALL_NONVIRT_TYPE(jobject, Object) + CALL_NONVIRT_TYPE(jboolean, Boolean) + CALL_NONVIRT_TYPE(jbyte, Byte) + CALL_NONVIRT_TYPE(jchar, Char) + CALL_NONVIRT_TYPE(jshort, Short) + CALL_NONVIRT_TYPE(jint, Int) + CALL_NONVIRT_TYPE(jlong, Long) + CALL_NONVIRT_TYPE(jfloat, Float) + CALL_NONVIRT_TYPE(jdouble, Double) + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) + { functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args) + { functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args); } + + jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetFieldID(this, clazz, name, sig); } + + jobject GetObjectField(jobject obj, jfieldID fieldID) + { return functions->GetObjectField(this, obj, fieldID); } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) + { return functions->GetBooleanField(this, obj, fieldID); } + jbyte GetByteField(jobject obj, jfieldID fieldID) + { return functions->GetByteField(this, obj, fieldID); } + jchar GetCharField(jobject obj, jfieldID fieldID) + { return functions->GetCharField(this, obj, fieldID); } + jshort GetShortField(jobject obj, jfieldID fieldID) + { return functions->GetShortField(this, obj, fieldID); } + jint GetIntField(jobject obj, jfieldID fieldID) + { return functions->GetIntField(this, obj, fieldID); } + jlong GetLongField(jobject obj, jfieldID fieldID) + { return functions->GetLongField(this, obj, fieldID); } + jfloat GetFloatField(jobject obj, jfieldID fieldID) + { return functions->GetFloatField(this, obj, fieldID); } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) + { return functions->GetDoubleField(this, obj, fieldID); } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject val) + { functions->SetObjectField(this, obj, fieldID, val); } + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean val) + { functions->SetBooleanField(this, obj, fieldID, val); } + void SetByteField(jobject obj, jfieldID fieldID, jbyte val) + { functions->SetByteField(this, obj, fieldID, val); } + void SetCharField(jobject obj, jfieldID fieldID, jchar val) + { functions->SetCharField(this, obj, fieldID, val); } + void SetShortField(jobject obj, jfieldID fieldID, jshort val) + { functions->SetShortField(this, obj, fieldID, val); } + void SetIntField(jobject obj, jfieldID fieldID, jint val) + { functions->SetIntField(this, obj, fieldID, val); } + void SetLongField(jobject obj, jfieldID fieldID, jlong val) + { functions->SetLongField(this, obj, fieldID, val); } + void SetFloatField(jobject obj, jfieldID fieldID, jfloat val) + { functions->SetFloatField(this, obj, fieldID, val); } + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble val) + { functions->SetDoubleField(this, obj, fieldID, val); } + + jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticMethodID(this, clazz, name, sig); } + +#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, \ + ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallStatic##_jname##MethodV(this, clazz, \ + methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID, \ + va_list args) \ + { return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \ + args); } +#define CALL_STATIC_TYPE_METHODA(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID, \ + const jvalue* args) \ + { return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \ + args); } + +#define CALL_STATIC_TYPE(_jtype, _jname) \ + CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODA(_jtype, _jname) + + CALL_STATIC_TYPE(jobject, Object) + CALL_STATIC_TYPE(jboolean, Boolean) + CALL_STATIC_TYPE(jbyte, Byte) + CALL_STATIC_TYPE(jchar, Char) + CALL_STATIC_TYPE(jshort, Short) + CALL_STATIC_TYPE(jint, Int) + CALL_STATIC_TYPE(jlong, Long) + CALL_STATIC_TYPE(jfloat, Float) + CALL_STATIC_TYPE(jdouble, Double) + + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallStaticVoidMethodV(this, clazz, methodID, args); + va_end(args); + } + void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args) + { functions->CallStaticVoidMethodV(this, clazz, methodID, args); } + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args) + { functions->CallStaticVoidMethodA(this, clazz, methodID, args); } + + jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticFieldID(this, clazz, name, sig); } + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticObjectField(this, clazz, fieldID); } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticBooleanField(this, clazz, fieldID); } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticByteField(this, clazz, fieldID); } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticCharField(this, clazz, fieldID); } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticShortField(this, clazz, fieldID); } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticIntField(this, clazz, fieldID); } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticLongField(this, clazz, fieldID); } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticFloatField(this, clazz, fieldID); } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticDoubleField(this, clazz, fieldID); } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject val) + { functions->SetStaticObjectField(this, clazz, fieldID, val); } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean val) + { functions->SetStaticBooleanField(this, clazz, fieldID, val); } + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte val) + { functions->SetStaticByteField(this, clazz, fieldID, val); } + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar val) + { functions->SetStaticCharField(this, clazz, fieldID, val); } + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort val) + { functions->SetStaticShortField(this, clazz, fieldID, val); } + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint val) + { functions->SetStaticIntField(this, clazz, fieldID, val); } + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong val) + { functions->SetStaticLongField(this, clazz, fieldID, val); } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat val) + { functions->SetStaticFloatField(this, clazz, fieldID, val); } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble val) + { functions->SetStaticDoubleField(this, clazz, fieldID, val); } + + jstring NewString(const jchar* unicodeChars, jsize len) + { return functions->NewString(this, unicodeChars, len); } + + jsize GetStringLength(jstring string) + { return functions->GetStringLength(this, string); } + + const jchar* GetStringChars(jstring string, jboolean* isCopy) + { return functions->GetStringChars(this, string, isCopy); } + + void ReleaseStringChars(jstring string, const jchar* chars) + { functions->ReleaseStringChars(this, string, chars); } + + jstring NewStringUTF(const char* bytes) + { return functions->NewStringUTF(this, bytes); } + + jsize GetStringUTFLength(jstring string) + { return functions->GetStringUTFLength(this, string); } + + const char* GetStringUTFChars(jstring string, jboolean* isCopy) + { return functions->GetStringUTFChars(this, string, isCopy); } + + void ReleaseStringUTFChars(jstring string, const char* utf) + { functions->ReleaseStringUTFChars(this, string, utf); } + + jsize GetArrayLength(jarray array) + { return functions->GetArrayLength(this, array); } + + jobjectArray NewObjectArray(jsize length, jclass elementClass, + jobject initialElement) + { return functions->NewObjectArray(this, length, elementClass, + initialElement); } + + jobject GetObjectArrayElement(jobjectArray array, jsize index) + { return functions->GetObjectArrayElement(this, array, index); } + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject val) + { functions->SetObjectArrayElement(this, array, index, val); } + + jbooleanArray NewBooleanArray(jsize length) + { return functions->NewBooleanArray(this, length); } + jbyteArray NewByteArray(jsize length) + { return functions->NewByteArray(this, length); } + jcharArray NewCharArray(jsize length) + { return functions->NewCharArray(this, length); } + jshortArray NewShortArray(jsize length) + { return functions->NewShortArray(this, length); } + jintArray NewIntArray(jsize length) + { return functions->NewIntArray(this, length); } + jlongArray NewLongArray(jsize length) + { return functions->NewLongArray(this, length); } + jfloatArray NewFloatArray(jsize length) + { return functions->NewFloatArray(this, length); } + jdoubleArray NewDoubleArray(jsize length) + { return functions->NewDoubleArray(this, length); } + + jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) + { return functions->GetBooleanArrayElements(this, array, isCopy); } + jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) + { return functions->GetByteArrayElements(this, array, isCopy); } + jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) + { return functions->GetCharArrayElements(this, array, isCopy); } + jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) + { return functions->GetShortArrayElements(this, array, isCopy); } + jint* GetIntArrayElements(jintArray array, jboolean* isCopy) + { return functions->GetIntArrayElements(this, array, isCopy); } + jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) + { return functions->GetLongArrayElements(this, array, isCopy); } + jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) + { return functions->GetFloatArrayElements(this, array, isCopy); } + jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) + { return functions->GetDoubleArrayElements(this, array, isCopy); } + + void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, + jint mode) + { functions->ReleaseBooleanArrayElements(this, array, elems, mode); } + void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, + jint mode) + { functions->ReleaseByteArrayElements(this, array, elems, mode); } + void ReleaseCharArrayElements(jcharArray array, jchar* elems, + jint mode) + { functions->ReleaseCharArrayElements(this, array, elems, mode); } + void ReleaseShortArrayElements(jshortArray array, jshort* elems, + jint mode) + { functions->ReleaseShortArrayElements(this, array, elems, mode); } + void ReleaseIntArrayElements(jintArray array, jint* elems, + jint mode) + { functions->ReleaseIntArrayElements(this, array, elems, mode); } + void ReleaseLongArrayElements(jlongArray array, jlong* elems, + jint mode) + { functions->ReleaseLongArrayElements(this, array, elems, mode); } + void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, + jint mode) + { functions->ReleaseFloatArrayElements(this, array, elems, mode); } + void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, + jint mode) + { functions->ReleaseDoubleArrayElements(this, array, elems, mode); } + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + jboolean* buf) + { functions->GetBooleanArrayRegion(this, array, start, len, buf); } + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, + jbyte* buf) + { functions->GetByteArrayRegion(this, array, start, len, buf); } + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, + jchar* buf) + { functions->GetCharArrayRegion(this, array, start, len, buf); } + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, + jshort* buf) + { functions->GetShortArrayRegion(this, array, start, len, buf); } + void GetIntArrayRegion(jintArray array, jsize start, jsize len, + jint* buf) + { functions->GetIntArrayRegion(this, array, start, len, buf); } + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, + jlong* buf) + { functions->GetLongArrayRegion(this, array, start, len, buf); } + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + jfloat* buf) + { functions->GetFloatArrayRegion(this, array, start, len, buf); } + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + jdouble* buf) + { functions->GetDoubleArrayRegion(this, array, start, len, buf); } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean* buf) + { functions->SetBooleanArrayRegion(this, array, start, len, buf); } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte* buf) + { functions->SetByteArrayRegion(this, array, start, len, buf); } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar* buf) + { functions->SetCharArrayRegion(this, array, start, len, buf); } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort* buf) + { functions->SetShortArrayRegion(this, array, start, len, buf); } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint* buf) + { functions->SetIntArrayRegion(this, array, start, len, buf); } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong* buf) + { functions->SetLongArrayRegion(this, array, start, len, buf); } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat* buf) + { functions->SetFloatArrayRegion(this, array, start, len, buf); } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble* buf) + { functions->SetDoubleArrayRegion(this, array, start, len, buf); } + + jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, + jint nMethods) + { return functions->RegisterNatives(this, clazz, methods, nMethods); } + + jint UnregisterNatives(jclass clazz) + { return functions->UnregisterNatives(this, clazz); } + + jint MonitorEnter(jobject obj) + { return functions->MonitorEnter(this, obj); } + + jint MonitorExit(jobject obj) + { return functions->MonitorExit(this, obj); } + + jint GetJavaVM(JavaVM** vm) + { return functions->GetJavaVM(this, vm); } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf) + { functions->GetStringRegion(this, str, start, len, buf); } + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf) + { return functions->GetStringUTFRegion(this, str, start, len, buf); } + + void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy) + { return functions->GetPrimitiveArrayCritical(this, array, isCopy); } + + void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode) + { functions->ReleasePrimitiveArrayCritical(this, array, carray, mode); } + + const jchar* GetStringCritical(jstring string, jboolean* isCopy) + { return functions->GetStringCritical(this, string, isCopy); } + + void ReleaseStringCritical(jstring string, const jchar* carray) + { functions->ReleaseStringCritical(this, string, carray); } + + jweak NewWeakGlobalRef(jobject obj) + { return functions->NewWeakGlobalRef(this, obj); } + + void DeleteWeakGlobalRef(jweak obj) + { functions->DeleteWeakGlobalRef(this, obj); } + + jboolean ExceptionCheck() + { return functions->ExceptionCheck(this); } + + jobject NewDirectByteBuffer(void* address, jlong capacity) + { return functions->NewDirectByteBuffer(this, address, capacity); } + + void* GetDirectBufferAddress(jobject buf) + { return functions->GetDirectBufferAddress(this, buf); } + + jlong GetDirectBufferCapacity(jobject buf) + { return functions->GetDirectBufferCapacity(this, buf); } + + /* added in JNI 1.6 */ + jobjectRefType GetObjectRefType(jobject obj) + { return functions->GetObjectRefType(this, obj); } +#endif /*__cplusplus*/ +}; + + +/* + * JNI invocation interface. + */ +struct JNIInvokeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + + jint (*DestroyJavaVM)(JavaVM* vm); + jint (*AttachCurrentThread)(JavaVM* vm, JNIEnv** p_env, void* thr_args); + jint (*DetachCurrentThread)(JavaVM* vm); + jint (*GetEnv)(JavaVM* vm, void** p_env, jint version); + jint (*AttachCurrentThreadAsDaemon)(JavaVM* vm, JNIEnv** p_env, void* thr_args); +}; + +/* + * C++ version. + */ +struct _JavaVM { + const struct JNIInvokeInterface* functions; + +#if defined(__cplusplus) + jint DestroyJavaVM() + { return functions->DestroyJavaVM(this); } + jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThread(this, p_env, thr_args); } + jint DetachCurrentThread() + { return functions->DetachCurrentThread(this); } + jint GetEnv(void** env, jint version) + { return functions->GetEnv(this, env, version); } + jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); } +#endif /*__cplusplus*/ +}; + +struct JavaVMAttachArgs { + jint version; /* must be >= JNI_VERSION_1_2 */ + const char* name; /* NULL or name of thread as modified UTF-8 str */ + jobject group; /* global ref of a ThreadGroup object, or NULL */ +}; +typedef struct JavaVMAttachArgs JavaVMAttachArgs; + +/* + * JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no + * longer supported.) + */ +typedef struct JavaVMOption { + const char* optionString; + void* extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; /* use JNI_VERSION_1_2 or later */ + + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +#ifdef __cplusplus +extern "C" { +#endif +/* + * VM initialization functions. + * + * Note these are the only symbols exported for JNI by the VM. + */ + +jint JNI_GetDefaultJavaVMInitArgs(void*); +jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); +jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*); + +#define JNIIMPORT +#define JNIEXPORT __attribute__ ((visibility ("default"))) +#define JNICALL + +/* + * Prototypes for functions exported by loadable shared libs. These are + * called by JNI, not provided by JNI. + */ +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved); +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved); + +#ifdef __cplusplus +} +#endif + + +/* + * Manifest constants. + */ +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#define JNI_OK (0) /* no error */ +#define JNI_ERR (-1) /* generic error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ +#define JNI_ENOMEM (-4) /* Out of memory */ +#define JNI_EEXIST (-5) /* VM already created */ +#define JNI_EINVAL (-6) /* Invalid argument */ + +#define JNI_COMMIT 1 /* copy content, do not free buffer */ +#define JNI_ABORT 2 /* free buffer w/o copying back */ +
diff --git a/pkgs/jni/tool/gen_aux_methods.dart b/pkgs/jni/tool/gen_aux_methods.dart new file mode 100644 index 0000000..0c7ae58 --- /dev/null +++ b/pkgs/jni/tool/gen_aux_methods.dart
@@ -0,0 +1,129 @@ +/// Run from templates directory + +import 'dart:io' as io; +import 'package:path/path.dart'; + +final targetTypes = { + "String": "String", + "Object": "JniObject", + "Boolean": "bool", + "Byte": "int", + "Char": "int", + "Short": "int", + "Int": "int", + "Long": "int", + "Float": "double", + "Double": "double", + "Void": "void" +}; + +final resultConverters = { + "String": (String resultVar) => "return strRes", + "Object": (String resultVar) => + "return JniObject.of(_env, $resultVar, nullptr)", + "Boolean": (String resultVar) => "return $resultVar != 0", +}; + +final invokeResultConverters = { + "String": (String resultVar) => "return strRes", + "Object": (String resultVar) => + "return JniObject.of(env, $resultVar, nullptr)", + "Boolean": (String resultVar) => "return $resultVar != 0", +}; + +void main(List<String> args) { + final script = io.Platform.script; + final scriptDir = dirname(script.toFilePath(windows: io.Platform.isWindows)); + String getTemplate(String name) { + return io.File(join(scriptDir, 'templates', name)).readAsStringSync(); + } + + final methodTemplates = getTemplate('jni_object_methods.dart.tmpl'); + final fieldTemplates = getTemplate('jni_object_fields.dart.tmpl'); + final invokeTemplates = getTemplate('invoke_static_methods.dart.tmpl'); + final retrieveTemplates = getTemplate('retrieve_static_fields.dart.tmpl'); + + final outputDir = join("lib", "src"); + final sInst = StringBuffer(); + final sStatic = StringBuffer(); + final sInvoke = StringBuffer(); + final outPutPaths = { + sInst: join(outputDir, "jni_object_methods_generated.dart"), + sStatic: join(outputDir, "jni_class_methods_generated.dart"), + sInvoke: join(outputDir, "direct_methods_generated.dart") + }; + for (final s in [sInst, sStatic, sInvoke]) { + s.write("// Autogenerated; DO NOT EDIT\n" + "// Generated by running the script in tool/gen_aux_methods.dart\n"); + s.write("// coverage:ignore-file\n"); + } + + sInst.write("part of 'jni_object.dart';\n\n"); + sStatic.write("part of 'jni_class.dart';\n\n"); + sInvoke.write("part of 'jni.dart';\n\n"); + + sInst.write("extension JniObjectCallMethods on JniObject {"); + sStatic.write("extension JniClassCallMethods on JniClass {"); + sInvoke.write("extension JniInvokeMethods on Jni {"); + + for (final t in targetTypes.keys) { + void write(String template) { + final resultConverter = + resultConverters[t] ?? (resultVar) => "return $resultVar"; + final skel = template + .replaceAll("{TYPE}", t == "String" ? "Object" : t) + .replaceAll("{PTYPE}", t) + .replaceAll("{TARGET_TYPE}", targetTypes[t]!) + .replaceAll("{RESULT}", resultConverter("result")) + .replaceAll( + "{STR_REF_DEL}", + t == "String" + ? "final strRes = _env.asDartString(result, " + "deleteOriginal: true);" + : ""); + final inst_ = + skel.replaceAll("{STATIC}", "").replaceAll("{THIS}", "_obj"); + final static_ = + skel.replaceAll("{STATIC}", "Static").replaceAll("{THIS}", "_cls"); + sInst.write(inst_); + sStatic.write(static_); + } + + write(methodTemplates); + if (t != "Void") { + write(fieldTemplates); + } + final invokeResultConverter = + (invokeResultConverters[t] ?? (String r) => "return $r"); + void writeI(String template) { + final replaced = template + .replaceAll("{TYPE}", t == "String" ? "Object" : t) + .replaceAll("{PTYPE}", t) + .replaceAll("{TARGET_TYPE}", targetTypes[t]!) + .replaceAll("{CLS_REF_DEL}", + t == "Object" || t == "String" ? "" : "env.DeleteLocalRef(cls);") + .replaceAll( + "{STR_REF_DEL}", + t == "String" + ? "final strRes = env.asDartString(result, " + "deleteOriginal: true);" + : "") + .replaceAll("{INVOKE_RESULT}", invokeResultConverter("result")); + sInvoke.write(replaced); + } + + writeI(invokeTemplates); + if (t != "Void") { + writeI(retrieveTemplates); + } + } + sInst.write("}"); + sStatic.write("}"); + sInvoke.write("}"); + for (final s in [sInst, sStatic, sInvoke]) { + final outputFile = io.File(outPutPaths[s]!); + outputFile.writeAsStringSync(s.toString(), flush: true); + } + io.stderr.write("Running dart format..\n"); + io.Process.run("dart", ["format", ...outPutPaths.values]); +}
diff --git a/pkgs/jni/tool/templates/invoke_static_methods.dart.tmpl b/pkgs/jni/tool/templates/invoke_static_methods.dart.tmpl new file mode 100644 index 0000000..7372d4c --- /dev/null +++ b/pkgs/jni/tool/templates/invoke_static_methods.dart.tmpl
@@ -0,0 +1,29 @@ + {TARGET_TYPE} invoke{PTYPE}Method(String className, String methodName, String signature, List<dynamic> args) { + return using((Arena arena) { + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final methodNameChars = methodName.toNativeChars(arena); + final signatureChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final methodID = env.GetStaticMethodID(cls, methodNameChars, signatureChars); + if (methodID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final jvArgs = JValueArgs(args, env, arena); + final result = env.CallStatic{TYPE}MethodA(cls, methodID, jvArgs.values); + jvArgs.disposeIn(env); + {CLS_REF_DEL} + {STR_REF_DEL} + env.checkException(); + {INVOKE_RESULT}; + }); + } +
diff --git a/pkgs/jni/tool/templates/jni_object_fields.dart.tmpl b/pkgs/jni/tool/templates/jni_object_fields.dart.tmpl new file mode 100644 index 0000000..e7ea50b --- /dev/null +++ b/pkgs/jni/tool/templates/jni_object_fields.dart.tmpl
@@ -0,0 +1,16 @@ + /// Retrieves the value of the field denoted by [fieldID] + {TARGET_TYPE} get{STATIC}{PTYPE}Field(JFieldID fieldID) { + _checkDeleted(); + final result = _env.Get{STATIC}{TYPE}Field({THIS}, fieldID); + {STR_REF_DEL} + _env.checkException(); + {RESULT}; + } + + /// Retrieve field of given [name] and [signature] + {TARGET_TYPE} get{STATIC}{PTYPE}FieldByName(String name, String signature) { + final fID = get{STATIC}FieldID(name, signature); + final result = get{STATIC}{PTYPE}Field(fID); + return result; + } +
diff --git a/pkgs/jni/tool/templates/jni_object_methods.dart.tmpl b/pkgs/jni/tool/templates/jni_object_methods.dart.tmpl new file mode 100644 index 0000000..462a0d4 --- /dev/null +++ b/pkgs/jni/tool/templates/jni_object_methods.dart.tmpl
@@ -0,0 +1,23 @@ + /// Calls method pointed to by [methodID] with [args] as arguments + {TARGET_TYPE} call{STATIC}{PTYPE}Method(JMethodID methodID, List<dynamic> args) { + _checkDeleted(); + final jvArgs = JValueArgs(args, _env); + final result = _env.Call{STATIC}{TYPE}MethodA({THIS}, methodID, jvArgs.values); + jvArgs.disposeIn(_env); + calloc.free(jvArgs.values); + {STR_REF_DEL} + _env.checkException(); + {RESULT}; + } + + /// Looks up method with [name] and [signature], calls it with [args] as arguments. + /// If calling the same method multiple times, consider using [get{STATIC}MethodID] + /// and [call{STATIC}{PTYPE}Method]. + {TARGET_TYPE} call{STATIC}{PTYPE}MethodByName( + String name, String signature, List<dynamic> args) { + final mID = get{STATIC}MethodID(name, signature); + final result = call{STATIC}{PTYPE}Method(mID, args); + return result; + } + +
diff --git a/pkgs/jni/tool/templates/retrieve_static_fields.dart.tmpl b/pkgs/jni/tool/templates/retrieve_static_fields.dart.tmpl new file mode 100644 index 0000000..b83e94c --- /dev/null +++ b/pkgs/jni/tool/templates/retrieve_static_fields.dart.tmpl
@@ -0,0 +1,28 @@ + {TARGET_TYPE} retrieve{PTYPE}Field(String className, String fieldName, String signature) { + return using((Arena arena) { + final arena = Arena(); + final env = getEnv(); + final classNameChars = className.toNativeChars(arena); + final fieldNameChars = fieldName.toNativeChars(arena); + final signatueChars = signature.toNativeChars(arena); + final cls = _bindings.LoadClass(classNameChars); + if (cls == nullptr) { + env.checkException(); + } + final fieldID = env.GetStaticFieldID(cls, fieldNameChars, signatueChars); + if (fieldID == nullptr) { + try { + env.checkException(); + } catch (e) { + env.DeleteLocalRef(cls); + rethrow; + } + } + final result = env.GetStatic{TYPE}Field(cls, fieldID); + {CLS_REF_DEL} + {STR_REF_DEL} + env.checkException(); + {INVOKE_RESULT}; + }); + } +