Skip to content

Commit

Permalink
Add README.md for //base/android/jni_generator
Browse files Browse the repository at this point in the history
Bug: 683256
Change-Id: I3735b47e983028b08b379dfc716eea78e3b977fd
Reviewed-on: https://chromium-review.googlesource.com/985641
Commit-Queue: agrieve <agrieve@chromium.org>
Reviewed-by: Richard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547046}
  • Loading branch information
agrieve authored and Commit Bot committed Mar 30, 2018
1 parent 41daf53 commit 79c58da
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 58 deletions.
118 changes: 118 additions & 0 deletions base/android/jni_generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Overview
JNI (Java Native Interface) is the mechanism that enables Java code to call
native functions, and native code to call Java functions.

* Native code calls into Java using apis from `<jni.h>`, which basically mirror
Java's reflection APIs.
* Java code calls native functions by declaring body-less functions with the
`native` keyword, and then calling them as normal Java functions.

`jni_generator` generates boiler-plate code with the goal of making our code:
1. easier to write, and
2. typesafe.

`jni_generator` uses regular expressions to parse .Java files, so don't do
anything too fancy. E.g.:
* Classes must be either explicitly imported, or are assumed to be in
the same package. To use `java.lang` classes, add an explicit import.
* Inner classes need to be referenced through the outer class. E.g.:
`void call(Outer.Inner inner)`

The presense of any JNI within a class will result in ProGuard obfuscation for
the class to be disabled.

### Exposing Native Methods

**Without Crazy Linker:**
* Java->Native calls are exported from the shared library and lazily resolved
by the runtime (via `dlsym()`).

**With Crazy Linker:**
* Java->Native calls are explicitly registered with JNI on the native side.
Explicit registration is necessary because crazy linker provides its own
`dlsym()`, but JNI is hardcoded to use the system's `dlsym()`.
* The logic to explicitly register stubs is generated by
`jni_registration_generator.py`.
* This script finds all native methods by scanning all source `.java` files
of an APK. Inefficient, but very convenient.
* Since `dlsym()` is not used in this case, we use a linker script to avoid
the cost of exporting symbols from the shared library (refer to
`//build/config/android:hide_all_but_jni_onload`).
* `jni_registration_generator.py` exposes two registrations methods:
* `RegisterNonMainDexNatives` - Registers native functions needed by multiple
process types (e.g. Rendereres, GPU process).
* `RegisterMainDexNatives` - Registers native functions needed only by the
browser process.

### Exposing Java Methods

Java methods just need to be annotated with `@CalledByNative`. The generated
functions can be put into a namespace using `@JNINamespace("your_namespace")`.

## Usage

Because the generator does not generate any source files, generated headers must
not be `#included` by multiple sources. If there are Java functions that need to
be called by multiple sources, one source should be chosen to expose the
functions to the others via additional wrapper functions.

### Calling Java -> Native

* Methods marked as `native` will have stubs generated for them that forward
calls to C++ function (that you must write).
* If the first parameter is a C++ object (e.g. `long mNativePointer`), then the
bindings will automatically generate the appropriate cast and call into C++
code (JNI itself is only C).

### Calling Native -> Java

* Methods annotated with `@CalledByNative` will have stubs generated for them.
* Just call the generated stubs defined in generated `.h` files.

### Java Objects and Garbage Collection

All pointers to Java objects must be registered with JNI in order to prevent
garbage collection from invalidating them.

For Strings & Arrays - it's common practice to use the `//base/android/jni_*`
helpers to convert them to `std::vectors` and `std::strings` as soon as
possible.

For other objects - use smart pointers to store them:
* `ScopedJavaLocalRef<>` - When lifetime is the current function's scope.
* `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's
scope.
* `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage
collection).
* `JavaParamRef<>` - Use to accept any of the above as a parameter to a
function without creating a redundant registration.

### Additional Guidelines / Advice

Minimize the surface API between the two sides. Rather than calling multiple
functions across boundaries, call only one (and then on the other side, call as
many little functions as required).

If a Java object "owns" a native one, store the pointer via
`"long mNativeClassName"`. Ensure to eventually call a native method to delete
the object. For example, have a `close()` that deletes the native object.

The best way to pass "compound" types across in either direction is to
create an inner class with PODs and a factory function. If possible, make mark
all the fields as "final".

## Build Rules

* `generate_jni` - Generates a header file with stubs for given `.java` files
* `generate_jar_jni` - Generates a header file with stubs for a given `.jar`
file
* `generate_jni_registration` - Generates a header file with functions to
register native-side JNI methods (required only when using crazy linker).

Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni)
for more about the GN templates.

## Changing `jni_generator`

* Python unit tests live in `jni_generator_tests.py`
* A working demo app exists as `//base/android/jni_generator:sample_jni_apk`
Original file line number Diff line number Diff line change
Expand Up @@ -30,64 +30,6 @@
// * link a native executable to prove the generated header + cc file are self-contained.
// All comments are informational only, and are ignored by the jni generator.
//
// Binding C/C++ with Java is not trivial, especially when ownership and object lifetime
// semantics needs to be managed across boundaries.
// Following a few guidelines will make the code simpler and less buggy:
//
// - Never write any JNI "by hand". Rely on the bindings generator to have a thin
// layer of type-safety.
//
// - Treat the types from the other side as "opaque" as possible. Do not inspect any
// object directly, but rather, rely on well-defined getters / setters.
//
// - Minimize the surface API between the two sides, and rather than calling multiple
// functions across boundaries, call only one (and then, internally in the other side,
// call as many little functions as required).
//
// - If a Java object "owns" a native object, stash the pointer in a "long mNativeClassName".
// Note that it needs to have a "destruction path", i.e., it must eventually call a method
// to delete the native object (for example, the java object has a "close()" method that
// in turn deletes the native object). Avoid relying on finalizers: those run in a different
// thread and makes the native lifetime management more difficult.
//
// - For native object "owning" java objects:
// - If there's a strong 1:1 to relationship between native and java, the best way is to
// stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the
// java object can be GC'd once the native object is destroyed but note that this global strong
// ref implies a new GC root, so be sure it will not leak and it must never rely on being
// triggered (transitively) from a java side GC.
// - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether
// that reference is still valid before de-referencing it. Note that you will need another
// java-side object to be holding a strong reference to this java object while it is in use, to
// avoid unpredictable GC of the object before native side has finished with it.
//
// - The best way to pass "compound" datatypes across in either direction is to create an inner
// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the
// fields as "final"). See examples with "InnerStructB" below.
//
// - It's simpler to create thin wrappers with a well defined JNI interface than to
// expose a lot of internal details. This is specially significant for system classes where it's
// simpler to wrap factory methods and a few getters / setters than expose the entire class.
//
// - Iterate over containers where they are originally owned, then create inner structs or
// directly call methods on the other side. It's much simpler than trying to amalgamate
// java and stl containers.
//
// An important note about qualified class name resolution:
// The generator doesn't compile the class and have little context about the
// classes being passed through the JNI layers. It adds a few simple rules:
//
// - all classes are either explicitly imported, or they are assumed to be in
// the same package.
//
// - Inner class needs to be done through an import and usage of the
// outer class, so that the generator knows how to qualify it:
// import foo.bar.Zoo;
// void call(Zoo.Inner);
//
// - implicitly imported classes aren't supported, so in order to pass
// things like Runnable, please import java.lang.Runnable;
//
// This JNINamespace annotation indicates that all native methods should be
// generated inside this namespace, including the native class that this
// object binds to.
Expand Down

0 comments on commit 79c58da

Please sign in to comment.