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 332) Is generally preferred due to a smaller code size and less up-front work, but 341) 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 2861. Add the `JniMocker` rule to your test. 2872. Call `JniMocker#mock` in a `setUp()` method for each interface you want to 288 stub out. 289 290`JniMocker` will reset the stubs during `tearDown()`. 291 292```java 293/** 294 * Tests for {@link AnimationFrameTimeHistogram} 295 */ 296@RunWith(BaseRobolectricTestRunner.class) 297@Config(manifest = Config.NONE) 298public class AnimationFrameTimeHistogramTest { 299 @Rule 300 public JniMocker mocker = new JniMocker(); 301 302 @Mock 303 AnimationFrameTimeHistogram.Natives mNativeMock; 304 305 @Before 306 public void setUp() { 307 MockitoAnnotations.initMocks(this); 308 mocker.mock(AnimationFrameTimeHistogramJni.TEST_HOOKS, mNativeMock); 309 } 310 311 @Test 312 public void testNatives() { 313 AnimationFrameTimeHistogram hist = new AnimationFrameTimeHistogram("histName"); 314 hist.startRecording(); 315 hist.endRecording(); 316 verify(mNativeMock).saveHistogram(eq("histName"), any(long[].class), anyInt()); 317 } 318} 319``` 320 321If a native method is called without setting a mock in a unit test, an 322`UnsupportedOperationException` will be thrown. 323 324### Special case: DFMs 325DFMs have their own generated `GEN_JNI`s, which are `<module_name>_GEN_JNI`. In 326order to get your DFM's JNI to use the `<module_name>` prefix, you must add your 327module name into the argument of the `@NativeMethods` annotation. 328 329So, for example, say your module was named `test_module`. You would annotate 330your `Natives` interface with `@NativeMethods("test_module")`, and this would 331result in `test_module_GEN_JNI`. 332 333 334### Testing for readiness: use `get()` 335 336JNI Generator automatically produces asserts that verify that the Natives interface can be safely 337called. These checks are compiled out of Release builds, making these an excellent way to determine 338whether your code is called safely. 339 340It is not sufficient, however, to use `<Class>Jni.get()` to guarantee native is initialized - it is 341only a debugging tool to ensure that you're using native after native is loaded. 342 343If you expect your code to be called by an external caller, it's often helpful to know _ahead of 344time_ that the context is valid (ie. either native libraries are loaded or mocks are installed). 345In this case it is helpful to call `get()` method, that performs all the Debug checks listed 346above, but does not instantiate a new object for interfacing Native libraries. 347Note that the unused value returned by the `get()` method will be optimized away in release builds 348so there's no harm in ignoring it. 349 350#### Addressing `Jni.get()` exceptions. 351 352When you identify a scenario leading to an exception, relocate (or defer) the appropriate call to 353be made to a place where (or time when) you know the native libraries have been initialized (eg. 354`onStartWithNative`, `onNativeInitialized` etc). 355 356Please avoid calling `LibraryLoader.isInitialized()` / `LibraryLoader.isLoaded()` in new code. 357Using `LibraryLoader` calls makes unit-testing more difficult: 358* this call can not verify whether Mock object is used, making the use of mocks more complicated, 359* using `LibraryLoader.setLibrariesLoadedForNativeTests()` alters the state for subsequently 360executed tests, inaccurately reporting flakiness and failures of these victim tests. 361* Introducing `LibraryLoader.is*()` calls in your code immediately affects all callers, forcing 362the authors of the code up the call stack to override `LibraryLoader` internal state in order to be 363able to unit-test their code. 364 365However, if your code is going to be called both before and after native is initialized, you are 366forced to call `LibraryLoader.isInitialized()` to be able to differentiate. Calling 367`<Class>Jni.get()` only provides assertions, and will fail in debug builds if you call it when 368native isn't ready. 369 370### Java Objects and Garbage Collection 371 372All pointers to Java objects must be registered with JNI in order to prevent 373garbage collection from invalidating them. 374 375For Strings & Arrays - it's common practice to use the `//base/android/jni_*` 376helpers to convert them to `std::vectors` and `std::strings` as soon as 377possible. 378 379For other objects - use smart pointers to store them: 380 * `ScopedJavaLocalRef<>` - When lifetime is the current function's scope. 381 * `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's 382 scope. 383 * `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage 384 collection). 385 * `JavaParamRef<>` - Use to accept any of the above as a parameter to a 386 function without creating a redundant registration. 387 388### Additional Guidelines / Advice 389 390Minimize the surface API between the two sides. Rather than calling multiple 391functions across boundaries, call only one (and then on the other side, call as 392many little functions as required). 393 394If a Java object "owns" a native one, store the pointer via 395`"long mNativeClassName"`. Ensure to eventually call a native method to delete 396the object. For example, have a `close()` that deletes the native object. 397 398The best way to pass "compound" types across in either direction is to 399create an inner class with PODs and a factory function. If possible, mark 400all the fields as "final". 401 402## Build Rules 403 404 * `generate_jni` - Generates a header file with stubs for given `.java` files 405 * `generate_jar_jni` - Generates a header file with stubs for a given `.jar` 406 file 407 * `generate_jni_registration` - Generates a header file with functions to 408 register native-side JNI methods. 409 410Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni) 411for more about the GN templates. 412 413## Changing JNI Zero 414 415 * Python tests live in `test/integration_tests.py` 416 * A working demo app exists as `//third_party/jni_zero/sample:jni_zero_sample_apk` 417