1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.example.jni_generator; 6 7 import android.graphics.Rect; 8 9 import org.chromium.base.AccessedByNative; 10 import org.chromium.base.CalledByNative; 11 import org.chromium.base.CalledByNativeUnchecked; 12 import org.chromium.base.JNINamespace; 13 import org.chromium.base.NativeClassQualifiedName; 14 15 import java.util.ArrayList; 16 import java.util.Iterator; 17 import java.util.List; 18 19 // This class serves as a reference test for the bindings generator, and as example documentation 20 // for how to use the jni generator. 21 // The C++ counter-part is sample_for_tests.cc. 22 // jni_generator.gyp has a jni_generator_tests target that will: 23 // * Generate a header file for the JNI bindings based on this file. 24 // * Compile sample_for_tests.cc using the generated header file. 25 // * link a native executable to prove the generated header + cc file are self-contained. 26 // All comments are informational only, and are ignored by the jni generator. 27 // 28 // Binding C/C++ with Java is not trivial, specially when ownership and object lifetime 29 // semantics needs to be managed across boundaries. 30 // Following a few guidelines will make the code simpler and less buggy: 31 // 32 // - Never write any JNI "by hand". Rely on the bindings generator to have a thin 33 // layer of type-safety. 34 // 35 // - Treat the types from the other side as "opaque" as possible. Do not inspect any 36 // object directly, but rather, rely on well-defined getters / setters. 37 // 38 // - Minimize the surface API between the two sides, and rather than calling multiple 39 // functions across boundaries, call only one (and then, internally in the other side, 40 // call as many little functions as required). 41 // 42 // - If a Java object "owns" a native object, stash the pointer in a "long mNativeClassName". 43 // Note that it needs to have a "destruction path", i.e., it must eventually call a method 44 // to delete the native object (for example, the java object has a "close()" method that 45 // in turn deletes the native object). Avoid relying on finalizers: those run in a different 46 // thread and makes the native lifetime management more difficult. 47 // 48 // - For native object "owning" java objects: 49 // - If there's a strong 1:1 to relationship between native and java, the best way is to 50 // stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the 51 // java object can be GC'd once the native object is destroyed but note that this global strong 52 // ref implies a new GC root, so be sure it will not leak and it must never rely on being 53 // triggered (transitively) from a java side GC. 54 // - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether 55 // that reference is still valid before de-referencing it. Note that you will need another 56 // java-side object to be holding a strong reference to this java object while it is in use, to 57 // avoid unpredictable GC of the object before native side has finished with it. 58 // 59 // - The best way to pass "compound" datatypes across in either direction is to create an inner 60 // class with PODs and a factory function. If possible, make it immutable (i.e., mark all the 61 // fields as "final"). See examples with "InnerStructB" below. 62 // 63 // - It's simpler to create thin wrappers with a well defined JNI interface than to 64 // expose a lot of internal details. This is specially significant for system classes where it's 65 // simpler to wrap factory methods and a few getters / setters than expose the entire class. 66 // 67 // - Use static factory functions annotated with @CalledByNative rather than calling the 68 // constructors directly. 69 // 70 // - Iterate over containers where they are originally owned, then create inner structs or 71 // directly call methods on the other side. It's much simpler than trying to amalgamate 72 // java and stl containers. 73 // 74 // An important note about qualified class name resolution: 75 // The generator doesn't compile the class and have little context about the 76 // classes being passed through the JNI layers. It adds a few simple rules: 77 // 78 // - all classes are either explicitly imported, or they are assumed to be in 79 // the same package. 80 // 81 // - Inner class needs to be done through an import and usage of the 82 // outer class, so that the generator knows how to qualify it: 83 // import foo.bar.Zoo; 84 // void call(Zoo.Inner); 85 // 86 // - implicitly imported classes aren't supported, so in order to pass 87 // things like Runnable, please import java.lang.Runnable; 88 // 89 // This JNINamespace annotation indicates that all native methods should be 90 // generated inside this namespace, including the native class that this 91 // object binds to. 92 @JNINamespace("base::android") 93 class SampleForTests { 94 // Classes can store their C++ pointer counter part as an int that is normally initialized by 95 // calling out a nativeInit() function. 96 long mNativeCPPObject; 97 98 // You can define methods and attributes on the java class just like any other. 99 // Methods without the @CalledByNative annotation won't be exposed to JNI. SampleForTests()100 public SampleForTests() { 101 } 102 startExample()103 public void startExample() { 104 // Calls native code and holds a pointer to the C++ class. 105 mNativeCPPObject = nativeInit("myParam"); 106 } 107 doStuff()108 public void doStuff() { 109 // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must be 110 // done to: 111 // * avoid leaks. 112 // * using finalizers are not allowed to destroy the cpp class. 113 nativeMethod(mNativeCPPObject); 114 } 115 finishExample()116 public void finishExample() { 117 // We're done, so let's destroy nativePtr object. 118 nativeDestroy(mNativeCPPObject); 119 } 120 121 // ----------------------------------------------------------------------------------------------- 122 // The following methods demonstrate exporting Java methods for invocation from C++ code. 123 // Java functions are mapping into C global functions by prefixing the method name with 124 // "Java_<Class>_" 125 // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. 126 127 // Exported to C++ as: 128 // Java_Example_javaMethod(JNIEnv* env, jobject obj, jint foo, jint bar) 129 // Typically the C++ code would have obtained the jobject via the Init() call described above. 130 @CalledByNative javaMethod(int foo, int bar)131 public int javaMethod(int foo, 132 int bar) { 133 return 0; 134 } 135 136 // Exported to C++ as Java_Example_staticJavaMethod(JNIEnv* env) 137 // Note no jobject argument, as it is static. 138 @CalledByNative staticJavaMethod()139 public static boolean staticJavaMethod() { 140 return true; 141 } 142 143 // No prefix, so this method is package private. It will still be exported. 144 @CalledByNative packagePrivateJavaMethod()145 void packagePrivateJavaMethod() {} 146 147 // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that 148 // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to 149 // call ClearException() and act as appropriate. 150 // See more details at the "@CalledByNativeUnchecked" annotation. 151 @CalledByNativeUnchecked methodThatThrowsException()152 void methodThatThrowsException() throws Exception {} 153 154 // The generator is not confused by inline comments: 155 // @CalledByNative void thisShouldNotAppearInTheOutput(); 156 // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); 157 158 /** 159 * The generator is not confused by block comments: 160 * @CalledByNative void thisShouldNotAppearInTheOutputEither(); 161 * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); 162 */ 163 164 // String constants that look like comments don't confuse the generator: 165 private String arrgh = "*/*"; 166 167 //------------------------------------------------------------------------------------------------ 168 // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to 169 // prevent them being eliminated when unreferenced code is stripped. 170 @AccessedByNative 171 private int javaField; 172 173 //------------------------------------------------------------------------------------------------ 174 // The following methods demonstrate declaring methods to call into C++ from Java. 175 // The generator detects the "native" and "static" keywords, the type and name of the first 176 // parameter, and the "native" prefix to the function name to determine the C++ function 177 // signatures. Besides these constraints the methods can be freely named. 178 179 // This declares a C++ function which the application code must implement: 180 // static jint Init(JNIEnv* env, jobject obj); 181 // The jobject parameter refers back to this java side object instance. 182 // The implementation must return the pointer to the C++ object cast to jint. 183 // The caller of this method should store it, and supply it as a the nativeCPPClass param to 184 // subsequent native method calls (see the methods below that take an "int native..." as first 185 // param). nativeInit(String param)186 private native long nativeInit(String param); 187 188 // This defines a function binding to the associated C++ class member function. The name is 189 // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. native 190 // prefixes stripped). 191 // The |nativeCPPClass| is automatically cast to type CPPClass* in order to obtain the object on 192 // which to invoke the member function. nativeDestroy(long nativeCPPClass)193 private native void nativeDestroy(long nativeCPPClass); 194 195 // This declares a C++ function which the application code must implement: 196 // static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); 197 // The jobject parameter refers back to this java side object instance. nativeGetDoubleFunction()198 private native double nativeGetDoubleFunction(); 199 200 // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than 201 // jobject param, as the function is declared static. nativeGetFloatFunction()202 private static native float nativeGetFloatFunction(); 203 204 // This function takes a non-POD datatype. We have a list mapping them to their full classpath in 205 // jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that 206 // function. nativeSetNonPODDatatype(Rect rect)207 private native void nativeSetNonPODDatatype(Rect rect); 208 209 // This declares a C++ function which the application code must implement: 210 // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject obj); 211 // The jobject parameter refers back to this java side object instance. 212 // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about 213 // deleting the JNI local reference. This is similar with Strings and arrays. nativeGetNonPODDatatype()214 private native Object nativeGetNonPODDatatype(); 215 216 // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type and 217 // call its Method member function. nativeMethod(long nativeCPPClass)218 private native int nativeMethod(long nativeCPPClass); 219 220 // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the 221 // annotation rather than parameter name, which can thus be chosen freely. 222 @NativeClassQualifiedName("CPPClass::InnerClass") nativeMethodOtherP0(long nativePtr)223 private native double nativeMethodOtherP0(long nativePtr); 224 225 // This "struct" will be created by the native side using |createInnerStructA|, 226 // and used by the java-side somehow. 227 // Note that |@CalledByNative| has to contain the inner class name. 228 static class InnerStructA { 229 private final long mLong; 230 private final int mInt; 231 private final String mString; 232 InnerStructA(long l, int i, String s)233 private InnerStructA(long l, int i, String s) { 234 mLong = l; 235 mInt = i; 236 mString = s; 237 } 238 239 @CalledByNative("InnerStructA") create(long l, int i, String s)240 private static InnerStructA create(long l, int i, String s) { 241 return new InnerStructA(l, i, s); 242 } 243 } 244 245 private List<InnerStructA> mListInnerStructA = new ArrayList<InnerStructA>(); 246 247 @CalledByNative addStructA(InnerStructA a)248 private void addStructA(InnerStructA a) { 249 // Called by the native side to append another element. 250 mListInnerStructA.add(a); 251 } 252 253 @CalledByNative iterateAndDoSomething()254 private void iterateAndDoSomething() { 255 Iterator<InnerStructA> it = mListInnerStructA.iterator(); 256 while (it.hasNext()) { 257 InnerStructA element = it.next(); 258 // Now, do something with element. 259 } 260 // Done, clear the list. 261 mListInnerStructA.clear(); 262 } 263 264 // This "struct" will be created by the java side passed to native, which 265 // will use its getters. 266 // Note that |@CalledByNative| has to contain the inner class name. 267 static class InnerStructB { 268 private final long mKey; 269 private final String mValue; 270 InnerStructB(long k, String v)271 private InnerStructB(long k, String v) { 272 mKey = k; 273 mValue = v; 274 } 275 276 @CalledByNative("InnerStructB") getKey()277 private long getKey() { 278 return mKey; 279 } 280 281 @CalledByNative("InnerStructB") getValue()282 private String getValue() { 283 return mValue; 284 } 285 } 286 287 List<InnerStructB> mListInnerStructB = new ArrayList<InnerStructB>(); 288 iterateAndDoSomethingWithMap()289 void iterateAndDoSomethingWithMap() { 290 Iterator<InnerStructB> it = mListInnerStructB.iterator(); 291 while (it.hasNext()) { 292 InnerStructB element = it.next(); 293 // Now, do something with element. 294 nativeAddStructB(mNativeCPPObject, element); 295 } 296 nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); 297 } 298 nativeAddStructB(long nativeCPPClass, InnerStructB b)299 native void nativeAddStructB(long nativeCPPClass, InnerStructB b); nativeIterateAndDoSomethingWithStructB(long nativeCPPClass)300 native void nativeIterateAndDoSomethingWithStructB(long nativeCPPClass); nativeReturnAString(long nativeCPPClass)301 native String nativeReturnAString(long nativeCPPClass); 302 } 303