• This repository has been archived on 20/Nov/2023
  • Stars
    star
    154
  • Rank 242,095 (Top 5 %)
  • Language
    Dart
  • License
    Other
  • Created over 2 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Experimental bindings generator for Java bindings through dart:ffi and JNI.

Build Status Coverage Status

Introduction

Experimental bindings generator for Java bindings through dart:ffi and JNI.

jnigen scans compiled JAR files or Java source code to generate a description of the API, then uses it to generate Dart annd C bindings. The Dart bindings call the C bindings, which in-turn call the Java functions through JNI. Shared functionality and base classes are provided through the support library, package:jni.

The configuration for binding generation is usually provided through YAML.

Three configuration details are needed to generate the bindings. Everything else is optional:

  • Inputs: input can be Java source files (source_path), or compiled classes / JARs (class_path). Some maven / gradle based tooling is also provided to simplify obtaining dependencies.

  • Outputs: Output can be generated in package-structured (one file per class) or single file bindings. Target path to write C and Dart bindings needs to be specified.

  • Classes: Specify which classes or packages you need bindings for. Specifying a package includes all classes inside it recursively.

Check out the examples to see some sample configurations.

C code is always generated into a directory with it's own build configuration. It's built as a separate dynamic library.

Lastly, dart_only bindings mode is also available as a proof-of-concept. It does not need intermediate C bindings, only a dependency on the support library package:jni.

Example

It's possible to generate bindings for JAR libraries, or Java source files.

Here's a simple example Java file, in a Flutter Android app.

package com.example.in_app_java;

import android.app.Activity;
import android.widget.Toast;
import androidx.annotation.Keep;

@Keep
public abstract class AndroidUtils {
  // Hide constructor
  private AndroidUtils() {}

  public static void showToast(Activity mainActivity, CharSequence text, int duration) {
    mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, text, duration).show());
  }
}

This produces the following boilerplate:

Dart Bindings:

/// Some boilerplate is omitted for clarity.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
    ProtectedJniExtensions.initGeneratedLibrary("android_utils");

/// from: com.example.in_app_java.AndroidUtils
class AndroidUtils extends jni.JObject {
  AndroidUtils.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref);

  static final _showToast = jniLookup<
          ffi.NativeFunction<
              jni.JniResult Function(ffi.Pointer<ffi.Void>,
                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("AndroidUtils__showToast")
      .asFunction<
          jni.JniResult Function(
              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();

  /// from: static public void showToast(android.app.Activity mainActivity, java.lang.CharSequence text, int duration)
  static void showToast(
          jni.JObject mainActivity, jni.JObject text, int duration) =>
      _showToast(mainActivity.reference, text.reference, duration).check();
}

C Bindings:

// Some boilerplate is omitted for clarity.

// com.example.in_app_java.AndroidUtils
jclass _c_AndroidUtils = NULL;

jmethodID _m_AndroidUtils__showToast = NULL;
FFI_PLUGIN_EXPORT
JniResult AndroidUtils__showToast(jobject mainActivity,
                                  jobject text,
                                  int32_t duration) {
  load_env();
  load_class_gr(&_c_AndroidUtils, "com/example/in_app_java/AndroidUtils");
  if (_c_AndroidUtils == NULL)
    return (JniResult){.result = {.j = 0}, .exception = check_exception()};
  load_static_method(_c_AndroidUtils, &_m_AndroidUtils__showToast, "showToast",
                     "(Landroid/app/Activity;Ljava/lang/CharSequence;I)V");
  if (_m_AndroidUtils__showToast == NULL)
    return (JniResult){.result = {.j = 0}, .exception = check_exception()};
  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_AndroidUtils,
                                  _m_AndroidUtils__showToast, mainActivity,
                                  text, duration);
  return (JniResult){.result = {.j = 0}, .exception = check_exception()};
}

The YAML configuration used to generate the above code looks like this:

android_sdk_config:
  add_gradle_deps: true

output:
  c:
    library_name: android_utils
    path: src/android_utils/
  dart:
    path: lib/android_utils.dart
    structure: single_file

source_path:
  - 'android/app/src/main/java'
classes:
  - 'com.example.in_app_java.AndroidUtils'

The complete example can be found in jnigen/example/in_app_java, which adds few more classes to demonstrate using classes from gradle JAR and source dependencies.

Supported platforms

Platform Dart Standalone Flutter
Android n/a Supported
Linux Supported Supported
Windows Supported Supported
MacOS Supported Not Yet

On Android, the flutter application runs embedded in Android JVM. On other platforms, a JVM needs to be explicitly spawned using Jni.spawn. package:jni provides the infrastructure for initializing and managing the JNI on both Android and Non-Android platforms.

Java features support

Currently basic features of the Java language are supported in the bindings. Each Java class is mapped to a Dart class. Bindings are generated for methods, constructors and fields. Exceptions thrown in Java are rethrown in Dart with stack trace from Java.

More advanced features such as callbacks are not supported yet. Support for these features is tracked in the issue tracker.

Note on Dart (standalone) target

package:jni is an FFI plugin containing native code, and any bindings generated from jnigen contains native code too.

On Flutter targets, native libraries are built automatically and bundled. On standalone platforms, no such infrastructure exists yet. As a stopgap solution, running dart run jni:setup in a target directory builds all JNI native dependencies of the package into build/jni_libs.

By default jni:setup goes through pubspec configuration and builds all JNI dependencies of the project. It can be overridden to build a custom directory using -s switch, which can be useful when output configuration for C bindings does not follow standard FFI plugin layout.

The build directory has to be passed to Jni.spawn call. It's assumed that all dependencies are built into the same target directory, so that once JNI is initialized, generated bindings can load their respective C libraries automatically.

Requirements

SDK

Flutter SDK is required.

Dart standalone target is supported, but due to some problems with pubspec format, the dart command must be from Flutter SDK and not Dart SDK. See dart-lang/pub#3563.

Java tooling

Along with JDK, maven (mvn command) is required. On windows, it can be installed using a package manager such as chocolatey or scoop.

On windows, append the path of jvm.dll in your JDK installation to PATH.

For example, on Powershell:

$env:Path += ";${env:JAVA_HOME}\bin\server".

If JAVA_HOME not set, find the java.exe executable and set the environment variable in Control Panel. If java is installed through a package manager, there may be a more automatic way to do this. (Eg: scoop reset).

C/C++ tooling

CMake and a standard C toolchain are required to build package:jni and C bindings generated by jnigen.

It's recommended to have clang-format installed for formatting the generated C bindings. On Windows, it's part of LLVM installation. On most Linux distributions it is available as a separate package. On MacOS, it can be installed using Homebrew.

FAQs

I am getting ClassNotFoundError at runtime.

jnigen does not handle getting the classes into application. It has to be done by target-specific mechanism. Such as adding a gradle dependency on Android, or manually providing classpath to Jni.spawn on desktop / standalone targets.

On Android, proguard prunes classes which it deems inaccessible. Since JNI class lookup happens in runtime, this leads to ClassNotFound errors in release mode even if the dependency is included in gradle. in_app_java example discusses two mechanisms to prevent this: using Keep annotation (androidx.annotation.Keep) for the code written in the application itself, and proguard-rules file for external libraries.

Lastly, some libraries such as java.awt do not exist in android. Attempting to use libraries which depend on them can also lead to ClassNotFound errors.

jnigen is not finding classes.

Ensure you are providing correct source and class paths, and they follow standard directory structure. If your class name is com.abc.MyClass, MyClass must be in com/abc/MyClass.java relative to one of the source paths, or com/abc/MyClass.class relative to one of the class paths specified in YAML.

If the classes are in JAR file, make sure to provide path to JAR file itself, and not the containing directory.

jnigen is unable to parse sources.

If the errors are similar to symbol not found, ensure all dependencies of the source are available. If such dependency is compiled, it can be included in class_path.

How are classes mapped into bindings?

Each Java class generates a subclass of JObject class, which wraps a jobject reference in JNI. Nested classes use _ as separator, Example.NestedClass will be mapped to Example_NestedClass.

Does JObject hold a local or global reference? Does it need to be manually deleted?

Each Java object returned into Dart creates a JNI global reference. Reference deletion is taken care of by NativeFinalizer and that's usually sufficient.

It's a good practice to keep the interface between languages sparse. However, if there's a need to create several references (Eg: in a loop), you can use FFI Arena mechanism (using function) and deletedIn method, or manually delete the object using delete method.

Should I use jnigen over Method channels?

This is currently an experimental package. Many features are missing, and it's rough around the edges. You're welcome to try it and give feedback.

YAML Configuration Reference

Keys ending with a colon (:) denote subsections.

The typical invocation with YAML configuration is

dart run jnigen --config jnigen.yaml

Any configuration can be overridden through command line using -D or --override switch. For example -Dlog_level=warning or -Dsummarizer.backend=asm. (Use . to separate subsection and property name).

A * denotes required configuration.

Configuration property Type / Values Description
preamble Text Text to be pasted in the start of each generated file.
source_path List of directory paths Directories to search for source files. Note: source_path for dependencies downloaded using maven_downloads configuration is added automatically without the need to specify here.
class_path List of directory / JAR paths Classpath for API summary generation. This should include any JAR dependencies of the source files in source_path.
classes * List of qualified class / package names List of qualified class / package names. source_path will be scanned assuming the sources follow standard java-ish hierarchy. That is a.b.c either maps to a directory a/b/c or a class file a/b/c.java.
enable_experiment List of experiment names:
  • interface_implementation
List of enabled experiments. These features are still in development and their API might break.
output: (Subsection) This subsection will contain configuration related to output files.
output: >> bindings_type c_based (default) or dart_only Binding generation strategy. Trade-offs are explained at the end of this document.
output: >> c: (Subsection) This subsection specified C output configuration. Required if bindings_type is c_based.
output: >> c: >> path * Directory path Directory to write C bindings. Usually src/ in case of an FFI plugin template.
output: >> c: >> subdir Directory path If specified, C bindings will be written to subdir resolved relative to path. This is useful when bindings are supposed to be under source's license, and written to a subdirectory such as third_party.
output: >> c: >> library_name * Identifier (snake_case) Name for generated C library.
output: >> dart: (Subsection) This subsection specifies Dart output configuration.
output: >> dart: >> structure package_structure / single_file Whether to map resulting dart bindings to file-per-class source layout, or write all bindings to single file.
output: >> dart: >> path * Directory path or File path Path to write Dart bindings. Should end in .dart for single_file configurations, and end in / for package_structure (default) configuration.
maven_downloads: (Subsection) This subsection will contain configuration for automatically downloading Java dependencies (source and JAR) through maven.
maven_downloads: >> source_deps List of maven package coordinates Source packages to download and unpack using maven. The names should be valid maven artifact coordinates. (Eg: org.apache.pdfbox:pdfbox:2.0.26). The downloads do not include transitive dependencies.
maven_downloads: >> source_dir Path Directory in which maven sources are extracted. Defaults to mvn_java. It's not required to list this explicitly in source_path.
maven_downloads: >> jar_only_deps List of maven package coordinates JAR dependencies to download which are not mandatory transitive dependencies of source_deps. Often, it's required to find and include optional dependencies so that entire source is valid for further processing.
maven_downloads: >> jar_dir Path Directory to store downloaded JARs. Defaults to mvn_jar.
log_level Logging level Configure logging level. Defaults to info.
android_sdk_config: (Subsection) Configuration for autodetection of Android dependencies and SDK. Note that this is more experimental than others, and very likely subject to change.
android_sdk_config: >> add_gradle_deps Boolean If true, run a gradle stub during jnigen invocation, and add Android compile classpath to the classpath of jnigen. This requires a release build to have happened before, so that all dependencies are cached appropriately.
android_sdk_config: >> android_example Directory path In case of an Android plugin project, the plugin itself cannot be built and add_gradle_deps is not directly feasible. This property can be set to relative path of package example app (usually example/ so that gradle dependencies can be collected by running a stub in this directory. See notification_plugin example for an example.
summarizer: (Subsection) Configuration specific to summarizer component, which builds API descriptions from Java sources or JAR files.
summarizer: >> backend auto, doclet or asm Specifies the backend to use in API summary generation. doclet uses OpenJDK Doclet API to build summary from sources. asm uses ASM library to build summary from classes in class_path JARs. auto attempts to find the class in sources, and falls back to using ASM.
summarizer: >> extra_args (DEV) List of CLI arguments Extra arguments to pass to summarizer JAR.
exclude: (Subsection) Exclude methods or fields using regex filters. It's generally useful to exclude problematic fields or methods which, with current binding generation, can lead to syntax errors
exclude: >> methods List of methods in classBinaryName#methodName format where classBinaryName is same as qualified name, but $ preceding a nested class instead of .. Example: com.example.MyClass or com.example.MyClass$NestedClass Methods to exclude.
exclude: >> fields List of fields in classBinaryName#fieldName format Fields to exclude.

It's possible to use the programmatic API instead of YAML.

  • Create a tool script. (Eg: tool/generate_jni_bindings.dart)
  • import package:jnigen/jnigen.dart
  • construct a Config object and pass it to generateJniBindings function. The parameters are similar to the ones described above.

Pure dart Bindings

It's possible to generate bindings that do not rely on an intermediate layer of C code. Bindings will still depend on package:jni and its support library written in C. But this approach avoids large C bindings.

To enable pure dart bindings, specify

output:
	bindings_type: dart_only

Any C output configuration will be ignored.

However, pure dart bindings will require additional allocations and check runtimeType of the arguments. This will be the case until Variadic arguments land in Dart FFI.

Android core libraries

These days, Android projects depend heavily on AndroidX and other libraries downloaded via gradle. We have a tracking issue to improve detection of android SDK and dependencies. (#31). Currently we can fetch the JAR dependencies of an android project, by running a gradle stub, if android_sdk_config >> add_gradle_deps is specified.

But core libraries (the android.** namespace) are not downloaded through gradle. The core libraries are shipped as stub JARs with the Android SDK. ($SDK_ROOT/platforms/android-$VERSION/android-stubs-src.jar).

Currently we don't have an automatic mechanism for using these. You can unpack this JAR manually into some directory and provide it as a source path.

However there are 2 caveats to this caveat.

  • SDK stubs after version 28 are incomplete. OpenJDK Doclet API we use to generate API summaries will error on incomplete sources.
  • The API can't process the java.** namespaces in the Android SDK stubs, because it expects a module layout. So if you want to generate bindings for, say, java.lang.Math, you cannot use the Android SDK stubs. OpenJDK sources can be used instead.

The JAR files ($SDK_ROOT/platforms/android-$VERSION/android.jar) can be used instead. But compiled JARs do not include JavaDoc and method parameter names. This JAR is automatically included by Gradle when android_sdk_config >> add_gradle_deps is specified.

Contributing

See the wiki for architecture-related documents.

More Repositories

1

sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
Dart
10,110
star
2

language

Design of the Dart language
TeX
2,652
star
3

dart-pad

An online Dart editor with support for console, web, and Flutter apps
Dart
1,702
star
4

pub

The pub command line tool
Dart
1,040
star
5

http

A composable API for making HTTP requests in Dart.
Dart
1,021
star
6

site-www

Source for Dart website
Dart
944
star
7

shelf

Web server middleware for Dart
Dart
920
star
8

build

A build system for Dart written in Dart
Dart
785
star
9

pub-dev

The pub.dev website
Dart
785
star
10

dart_style

An opinionated formatter/linter for Dart code
Dart
645
star
11

dart-vim-plugin

Syntax highlighting for Dart in Vim
Vim Script
637
star
12

mockito

Mockito-inspired mock library for Dart
Dart
632
star
13

linter

Linter for Dart.
Dart
628
star
14

samples

A collection of Dart code samples by Dart DevRel
Dart
602
star
15

test

A library for writing unit tests in Dart.
Dart
492
star
16

source_gen

Automatic source code generation for Dart
Dart
484
star
17

dartdoc

API documentation tool for Dart.
Dart
473
star
18

markdown

A Dart markdown library
Dart
444
star
19

code_builder

A fluent API for generating valid Dart source code
Dart
428
star
20

web_socket_channel

StreamChannel wrappers for WebSockets.
Dart
418
star
21

leak_tracker

A framework for memory leak tracking for Dart and Flutter applications.
Dart
387
star
22

collection

The collection package for Dart contains a number of separate libraries with utility functions and classes that makes working with collections easier.
Dart
375
star
23

ffigen

FFI binding generator
Dart
364
star
24

logging

A Dart package for debug and error logging.
Dart
332
star
25

async

A Dart package that contains tools to work with asynchronous computations.
Dart
320
star
26

wasm

Utilities for loading and running WASM modules from Dart code
Dart
309
star
27

html

Dart port of html5lib. For parsing HTML/HTML5 with Dart. Works in the client and on the server.
Dart
276
star
28

crypto

A set of cryptographic functions implemented in pure Dart.
Dart
275
star
29

path

A string-based path manipulation library.
Dart
223
star
30

args

A command-line argument parsing library for Dart.
Dart
214
star
31

webdev

A CLI for Dart web development.
Dart
212
star
32

oauth2

An OAuth2 client library for Dart.
Dart
211
star
33

pana

Package ANAlysis for Dart
Dart
205
star
34

setup-dart

A GitHub Action to install and setup a Dart SDK.
Dart
188
star
35

yaml

A Dart YAML parser.
Dart
169
star
36

homebrew-dart

Dart team's official tap for homebrew.
Ruby
153
star
37

native

Dart packages related to FFI and native assets bundling.
Dart
153
star
38

http2

A HTTP/2 implementation for dart.
Dart
153
star
39

sample-pop_pop_win

"Pop, Pop, Win!" is an implementation of Minesweeper in Dart.
Dart
149
star
40

usage

A Google Analytics wrapper for command-line, web, and Flutter apps.
Dart
143
star
41

watcher

A file system watcher library for Dart.
Dart
138
star
42

mime

Dart package for working with MIME type definitions and for processing streams of MIME multipart media types.
Dart
132
star
43

stack_trace

A package for manipulating stack traces and printing them readably.
Dart
128
star
44

web

Lightweight browser API bindings built around JS static interop.
Dart
128
star
45

gcloud

High-level interfaces to Google Cloud Platform APIs
Dart
126
star
46

platform

A generic platform abstraction for Dart
Dart
125
star
47

stream_transform

Dart utility methods to create StreamTransfomer instances to manipulate Streams
Dart
123
star
48

lints

Official Dart lint rules; the core and recommended set of lints suggested by the Dart team.
Dart
116
star
49

coverage

Dart coverage data manipulation and formatting
Dart
103
star
50

sse

Dart Server Sent Events package
Dart
96
star
51

csslib

A library for parsing CSS.
Dart
95
star
52

benchmark_harness

The official benchmark harness for Dart
Dart
94
star
53

appengine

Dart support for App Engine managed VMs
Dart
93
star
54

fake_async

Fake asynchronous events for deterministic testing.
Dart
90
star
55

convert

Conversion utilities
Dart
72
star
56

matcher

A declarative API for specifying expectations.
Dart
71
star
57

json_rpc_2

A Dart implementation of the JSON-RPC 2.0 spec.
Dart
71
star
58

characters

A package for characters represented as unicode extended grapheme clusters
Dart
69
star
59

dart-docker

Docker images for the Dart programming language (https://dart.dev)
Dart
68
star
60

pub_semver

A package for working with Pub/semver-style versions and version constraints
Dart
65
star
61

i18n

A general mono-repo for Dart i18n and l10n packages.
Dart
63
star
62

cli_util

A library to help in building Dart command-line apps
Dart
60
star
63

string_scanner

A class for parsing strings using a sequence of patterns.
Dart
60
star
64

glob

Bash-style filename globbing for Dart.
Dart
56
star
65

pubspec_parse

Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting
Dart
51
star
66

pool

A class for managing a finite pool of resources.
Dart
51
star
67

stream_channel

An abstraction for two-way communication channels.
Dart
50
star
68

io

Utilities for the Dart VM's dart:io.
Dart
49
star
69

macros

A Dart mono-repo for macro development.
Dart
48
star
70

fixnum

Fixed-width integer library for Dart.
Dart
45
star
71

clock

A fakeable wrapper for dart:core clock APIs.
Dart
41
star
72

ecosystem

This repository is home to general Dart Ecosystem tools and packages.
Dart
41
star
73

http_parser

A platform-independent Dart package for parsing and serializing HTTP formats.
Dart
39
star
74

site-shared

Content shared across Dart websites
JavaScript
38
star
75

co19

A Dart language and library conformance test suite
Dart
37
star
76

typed_data

Utility functions and classes that makes working with typed data lists easier in Dart
Dart
34
star
77

source_span

A library for identifying source spans and locations.
Dart
29
star
78

tools

This repository is home to tooling related Dart packages.
Dart
29
star
79

http_multi_server

A dart:io HttpServer wrapper that handles requests from multiple servers.
Dart
28
star
80

yaml_edit

A library for YAML manipulation with comment and whitespace preservation.
Dart
27
star
81

native_synchronization

Low-level synchronization primitives built using dart:ffi.
Dart
26
star
82

os_detect

Dart multi-platform operating system identification
Dart
24
star
83

dartbug.com

The redirect service for Dart issues and bugs.
Dart
23
star
84

bazel_worker

Dart integration for Bazel build system
Dart
23
star
85

boolean_selector

A flexible syntax for boolean expressions.
Dart
23
star
86

dart-syntax-highlight

Tools and documentation for how Dart code is formatted
Dart
22
star
87

api.dart.dev

Dart API docs
Python
19
star
88

grpc_cronet

Flutter dart:grpc implementation that uses the Cronet native library.
Dart
18
star
89

dart_ci

Tools used by Dart's continuous integration (CI) testing that aren't needed by Dart SDK contributors. Mirrored from dart.googlesource.com/dart_ci. Do not land pull requests on Github.
Dart
18
star
90

source_maps

A package to programmatically manipulate source maps.
Dart
16
star
91

term_glyph

Useful glyphs and Windows-safe equivalents
Dart
16
star
92

package_config

Support for working with Package Resolution Configuration files
Dart
16
star
93

test_process

A Dart package for testing subprocesses
Dart
16
star
94

browser_launcher

Provides a standardized way to launch web browsers.
Dart
16
star
95

timing

A Dart package for tracking time spent in child operations
Dart
14
star
96

root_certificates

The set of root certificates trusted by dart:io's default SecurityContext. Taken from Mozilla's NSS library.
C++
11
star
97

test_descriptor

Provides a convenient, easy-to-read API for defining and verifying directory structures in tests
Dart
10
star
98

dartlang_project_templates

Project templates for new repos under the dart-lang organization
Dart
8
star
99

source_map_stack_trace

Convert stack traces generated by dart2js-compiled code into readable native Dart stack traces
Dart
8
star
100

.github

GitHub default community health file for dart-lang repos
7
star