• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# JNI Zero
2A zero-overhead (or better!) middleware for JNI.
3
4## Overview
5JNI (Java Native Interface) is the mechanism that enables Java code to call
6native functions, and native code to call Java functions.
7
8 * Native code calls into Java using apis from `<jni.h>`, which basically mirror
9   Java's reflection APIs.
10 * Java code calls native functions by declaring body-less functions with the
11  `native` keyword, and then calling them as normal Java functions.
12
13JNI Zero generates boiler-plate code with the goal of making our code:
14 1. easier to write,
15 2. typesafe.
16 3. more optimizable.
17
18JNI Zero uses regular expressions to parse .Java files, so don't do
19anything too fancy. E.g.:
20 * Classes must be either explicitly imported, or are assumed to be in
21the same package. To use `java.lang` classes, add an explicit import.
22 * Inner classes need to be referenced through the outer class. E.g.:
23   `void call(Outer.Inner inner)`
24
25### Exposing Native Methods
26
27There are two ways to have native methods be found by Java:
281) Explicitly register the name -> function pointer mapping using JNI's
29   `RegisterNatives()` function.
302) Export the symbols from the shared library, and let the runtime resolve them
31   on-demand (using `dlsym()`) the first time a native method is called.
32
33(2) Is generally preferred due to a smaller code size and less up-front work, but
34(1) is sometimes required (e.g. when OS bugs prevent `dlsym()` from working).
35Both ways are supported by this tool.
36
37### Exposing Java Methods
38
39Java methods just need to be annotated with `@CalledByNative`. By default the
40generated method stubs on the native side are not namespaced. The generated
41functions can be put into a namespace using `@JNINamespace("your_namespace")`.
42
43## Usage
44
45### Writing Build Rules
461. Find or add a `generate_jni` target with your .java file, then add this
47   `generate_jni` target to your `srcjar_deps` of your `android_library` target:
48
49   ```python
50   generate_jni("abcd_jni") {
51     sources = [ "path/to/java/sources/with/jni/Annotations.java" ]
52   }
53
54   android_library("abcd_java") {
55     ...
56     # Allows the java files to see the generated `${OriginalClassName}Jni`
57     # classes.
58     srcjar_deps = [ ":abcd_jni" ]
59   }
60
61   source_set("abcd") {
62    ...
63    # Allows the cpp files to include the generated `${OriginalClassName}_jni.h`
64    # headers.
65    deps = [ ":abcd_jni" ]
66   }
67   ```
68
69### Calling Java -> Native
70
71- For each JNI method:
72  - C++ stubs are generated that forward to C++ functions that you must write.
73    By default the c++ functions you are expected to implement are not
74    associated with a class.
75  - If the first parameter is a C++ object (e.g.
76    `long native${OriginalClassName}`), then the bindings will not call a static
77    function but instead cast the variable into a cpp `${OriginalClassName}`
78    pointer type and then call a member method with that name on said object.
79
80To add JNI to a class:
81
821. Create a nested-interface annotated with `@NativeMethods` that contains
83   the declaration of the corresponding static methods you wish to have
84   implemented.
852. Call native functions using `${OriginalClassName}Jni.get().${method}`
863. In C++ code, #include the header `${OriginalClassName}_jni.h`. (The path will
87   depend on the location of the `generate_jni` BUILD rule that lists your Java
88   source code.) Only include this header from a single `.cc` file as the
89   header defines functions. That `.cc` must implement your native code by
90   defining non-member functions named `JNI_${OriginalClassName}_${UpperCamelCaseMethod}`
91   for static methods and member functions named `${OriginalClassName}::${UpperCamelCaseMethod}`
92   for non-static methods. Member functions need be declared in the header
93   file as well.
94
95Example:
96#### Java
97```java
98class MyClass {
99  // Cannot be private. Must be package or public.
100  @NativeMethods
101  /* package */ interface Natives {
102    void foo();
103    double bar(int a, int b);
104    // Either the |MyClass| part of the |nativeMyClass| parameter name must
105    // match the native class name exactly, or the method annotation
106    // @NativeClassQualifiedName("MyClass") must be used.
107    //
108    // If the native class is nested, use
109    // @NativeClassQualifiedName("FooClassName::BarClassName") and call the
110    // parameter |nativePointer|.
111    void nonStatic(long nativeMyClass);
112  }
113
114  void callNatives() {
115    // MyClassJni is generated by the generate_jni rule.
116    // Storing MyClassJni.get() in a field defeats some of the desired R8
117    // optimizations, but local variables are fine.
118    Natives jni = MyClassJni.get();
119    jni.foo();
120    jni.bar(1,2);
121    jni.nonStatic(mNativePointer);
122  }
123}
124```
125#### C++
126```c++
127#include "third_party/jni_zero/jni_zero.h"
128#include "<path to BUILD.gn>/<generate_jni target name>/MyClass_jni.h"
129
130class MyClass {
131public:
132  void NonStatic(JNIEnv* env);
133}
134
135// Notice that unlike Java, function names are capitalized in C++.
136// Static function names should follow this format and don't need to be declared.
137void JNI_MyClass_Foo(JNIEnv* env) { ... }
138void JNI_MyClass_Bar(JNIEnv* env, jint a, jint b) { ... }
139
140// Member functions need to be declared.
141void MyClass::NonStatic(JNIEnv* env) { ... }
142```
143
144### Calling Native -> Java
145
146Because the generated header files contain definitions as well as declarations,
147the must not be `#included` by multiple sources. If there are Java functions
148that need to be called by multiple sources, one source should be chosen to
149expose the functions to the others via additional wrapper functions.
150
1511. Annotate some methods with `@CalledByNative`, the generator will now generate
152   stubs in `${OriginalClassName}_jni.h` header to call into those java methods
153   from cpp.
154   * Inner class methods must provide the inner class name explicitly
155     (ex. `@CalledByNative("InnerClassName")`)
156
1572. In C++ code, `#include` the header `${OriginalClassName}_jni.h`. (The path
158   will depend on the location of the `generate_jni` build rule that lists your
159   Java source code). That `.cc` can call the stubs with their generated name
160   `JAVA_${OriginalClassName}_${UpperCamelCaseMethod}`.
161
162Note: For test-only methods, use `@CalledByNativeForTesting` which will ensure
163that it is stripped in our release binaries.
164
165### Automatic Type Conversions using @JniType
166
167Normally, Java types map to C++ types from `<jni.h>` (e.g. `jstring` for
168`java.lang.String`). The first thing most people do is convert the jni spec
169types into standard C++ types.
170
171`@JniType` to the rescue. By annotating a parameter or a return type with
172`@JniType("cpp_type_here")` the generated code will automatically convert from
173the jni type to the type listed inside the annotation. See example:
174
175#### Original Code:
176```java
177class MyClass {
178  @NativeMethods
179  interface Natives {
180    void foo(
181            String string,
182            String[] strings,
183            MyClass obj,
184            MyClass[] objs)
185  }
186}
187```
188
189```c++
190#include "third_party/jni_zero/jni_zero.h"
191#include "<path to BUILD.gn>/<generate_jni target name>/MyClass_jni.h"
192
193void JNI_MyClass_Foo(JNIEnv* env, const JavaParamRef<jstring>&, const JavaParamRef<jobjectArray>&, const JavaParamRef<jobject>&, JavaParamRef<jobjectArray>&) {...}
194```
195
196#### After using `@JniType`
197```java
198class MyClass {
199  @NativeMethods
200  interface Natives {
201    void foo(
202            @JniType("std::string") String convertedString,
203            @JniType("std::vector<std::string>") String[] convertedStrings,
204            @JniType("myModule::CPPClass") MyClass convertedObj,
205            @JniType("std::vector<myModule::CPPClass>") MyClass[] convertedObjects);
206  }
207}
208```
209```c++
210#include "third_party/jni_zero/jni_zero.h"
211#include "<path to BUILD.gn>/<generate_jni target name>/MyClass_jni.h"
212
213void JNI_MyClass_Foo(JNIEnv* env, std::string&, std::vector<std::string>>&, myModule::CPPClass&, std::vector<myModule::CPPClass>&) {...}
214```
215
216#### Implementing Conversion Functions
217
218Conversion functions must be defined for all types that appear in `@JniType`.
219Forgetting to add one will result in errors at link time.
220
221```c++
222// The conversion function primary templates.
223template <typename O>
224O FromJniType(JNIEnv*, const JavaRef<jobject>&);
225template <typename O>
226O FromJniType(JNIEnv*, const JavaRef<jstring>&);
227template <typename O>
228ScopedJavaLocalRef<jobject> ToJniType(JNIEnv*, const O&);
229```
230
231An example conversion function can look like:
232```c++
233#include "third_party/jni_zero/jni_zero.h"
234
235namespace jni_zero {
236template <>
237EXPORT std::string FromJniType<std::string>(
238    JNIEnv* env,
239    const JavaRef<jstring>& input) {
240  // Do the actual conversion to std::string.
241}
242
243template <>
244EXPORT ScopedJavaLocalRef<jstring> ToJniType<std::string>(
245    JNIEnv* env,
246    const std::string& input) {
247  // Do the actual conversion from std::string.
248}
249}  // namespace jni_zero
250```
251
252If a conversion function is missing, you will get a linker error since we
253forward declare the conversion functions before using them.
254
255#### Array Conversion Functions
256
257Array conversion functions look different due to the partial specializations.
258The `ToJniType` direction also takes a `jclass` parameter which is the class of the
259array elements, because java requires it when creating a non-primitive array.
260
261```c++
262template <typename O>
263struct ConvertArray {
264  static O FromJniType(JNIEnv*, const JavaRef<jobjectArray>&);
265  static ScopedJavaLocalRef<jobjectArray> ToJniType(JNIEnv*, const O&, jclass);
266};
267```
268
269JniZero provides implementations for partial specializations to wrap and unwrap
270`std::vector` for object arrays and some primitive arrays.
271
272#### Nullability
273
274All non-primitive default JNI C++ types (e.g. `jstring`, `jobject`) are pointer
275types (i.e. nullable). Some C++ types (e.g. `std::string`) are not pointer types
276and thus cannot be `nullptr`. This means some conversion functions that return
277non-nullable types have to handle the situation where the passed in java type is
278null.
279
280You can get around this by having the conversion be to `std::optional<T>` rather
281than just `T` if `T` is not a nullable type.
282
283
284### Testing Mockable Natives
285
286```java
287/**
288 * Tests for {@link AnimationFrameTimeHistogram}
289 */
290@RunWith(RobolectricTestRunner.class)
291public class AnimationFrameTimeHistogramTest {
292    // Optional: Resets test overrides during tearDown().
293    // Not needed when using Chrome's test runners.
294    @Rule public JniResetterRule jniResetterRule = new JniResetterRule();
295
296    @Mock
297    AnimationFrameTimeHistogram.Natives mNativeMock;
298
299    @Before
300    public void setUp() {
301        MockitoAnnotations.initMocks(this);
302        AnimationFrameTimeHistogramJni.setInstanceForTesting(mNativeMock);
303    }
304
305    @Test
306    public void testNatives() {
307        AnimationFrameTimeHistogram hist = new AnimationFrameTimeHistogram("histName");
308        hist.startRecording();
309        hist.endRecording();
310        verify(mNativeMock).saveHistogram(eq("histName"), any(long[].class), anyInt());
311    }
312}
313```
314
315If a native method is called without setting a mock in a unit test, an
316`UnsupportedOperationException` will be thrown.
317
318### Special case: APK Splits
319Each APK split has their own generated `GEN_JNI`s, which are `<module_name>_GEN_JNI`. In
320order to get your split's JNI to use the `<module_name>` prefix, you must add your
321module name into the argument of the `@NativeMethods` annotation.
322
323So, for example, say your module was named `test_module`. You would annotate
324your `Natives` interface with `@NativeMethods("test_module")`, and this would
325result in `test_module_GEN_JNI`.
326
327
328### Testing for readiness: use `get()`
329
330JNI Generator automatically produces asserts that verify that the Natives interface can be safely
331called. These checks are compiled out of Release builds, making these an excellent way to determine
332whether your code is called safely.
333
334It is not sufficient, however, to use `<Class>Jni.get()` to guarantee native is initialized - it is
335only a debugging tool to ensure that you're using native after native is loaded.
336
337If you expect your code to be called by an external caller, it's often helpful to know _ahead of
338time_ that the context is valid (ie. either native libraries are loaded or mocks are installed).
339In this case it is helpful to call `get()` method, that performs all the Debug checks listed
340above, but does not instantiate a new object for interfacing Native libraries.
341Note that the unused value returned by the `get()` method will be optimized away in release builds
342so there's no harm in ignoring it.
343
344#### Addressing `Jni.get()` exceptions.
345
346When you identify a scenario leading to an exception, relocate (or defer) the appropriate call to
347be made to a place where (or time when) you know the native libraries have been initialized (eg.
348`onStartWithNative`, `onNativeInitialized` etc).
349
350Please avoid calling `LibraryLoader.isInitialized()` / `LibraryLoader.isLoaded()` in new code.
351Using `LibraryLoader` calls makes unit-testing more difficult:
352* this call can not verify whether Mock object is used, making the use of mocks more complicated,
353* using `LibraryLoader.setLibrariesLoadedForNativeTests()` alters the state for subsequently
354executed tests, inaccurately reporting flakiness and failures of these victim tests.
355* Introducing `LibraryLoader.is*()` calls in your code immediately affects all callers, forcing
356the authors of the code up the call stack to override `LibraryLoader` internal state in order to be
357able to unit-test their code.
358
359However, if your code is going to be called both before and after native is initialized, you are
360forced to call `LibraryLoader.isInitialized()` to be able to differentiate. Calling
361`<Class>Jni.get()` only provides assertions, and will fail in debug builds if you call it when
362native isn't ready.
363
364### Java Objects and Garbage Collection
365
366All pointers to Java objects must be registered with JNI in order to prevent
367garbage collection from invalidating them.
368
369For Strings & Arrays - it's common practice to use the `//base/android/jni_*`
370helpers to convert them to `std::vectors` and `std::strings` as soon as
371possible.
372
373For other objects - use smart pointers to store them:
374 * `ScopedJavaLocalRef<>` - When lifetime is the current function's scope.
375 * `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's
376   scope.
377 * `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage
378   collection).
379 * `JavaParamRef<>` - Use to accept any of the above as a parameter to a
380   function without creating a redundant registration.
381
382### Additional Guidelines / Advice
383
384Minimize the surface API between the two sides. Rather than calling multiple
385functions across boundaries, call only one (and then on the other side, call as
386many little functions as required).
387
388If a Java object "owns" a native one, store the pointer via
389`"long mNativeClassName"`. Ensure to eventually call a native method to delete
390the object. For example, have a `close()` that deletes the native object.
391
392The best way to pass "compound" types across in either direction is to
393create an inner class with PODs and a factory function. If possible, mark
394all the fields as "final".
395
396## Build Rules
397
398 * `generate_jni` - Generates a header file with stubs for given `.java` files
399 * `generate_jar_jni` - Generates a header file with stubs for a given `.jar`
400   file
401 * `generate_jni_registration` - Generates a header file with functions to
402   register native-side JNI methods.
403
404Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni)
405for more about the GN templates.
406
407## Changing JNI Zero
408
409 * Python tests live in `test/integration_tests.py`
410 * A working demo app exists as `//third_party/jni_zero/sample:jni_zero_sample_apk`
411