[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};
+	});
+  }
+