// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.example.jni_generator; import android.graphics.Rect; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.chromium.base.AccessedByNative; import org.chromium.base.CalledByNative; import org.chromium.base.CalledByNativeUnchecked; import org.chromium.base.JNINamespace; import org.chromium.base.NativeClassQualifiedName; // This class serves as a reference test for the bindings generator, and as example documentation // for how to use the jni generator. // The C++ counter-part is sample_for_tests.cc. // jni_generator.gyp has a jni_generator_tests target that will: // * Generate a header file for the JNI bindings based on this file. // * Compile sample_for_tests.cc using the generated header file. // * 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, specially 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 "int 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. // // - Use static factory functions annotated with @CalledByNative rather than calling the // constructors directly. // // - 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. // // This JNINamespace annotation indicates that all native methods should be // generated inside this namespace, including the native class that this // object binds to. @JNINamespace("base::android") class SampleForTests { // Classes can store their C++ pointer counter part as an int that is normally initialized by // calling out a nativeInit() function. int mNativeCPPObject; // You can define methods and attributes on the java class just like any other. // Methods without the @CalledByNative annotation won't be exposed to JNI. public SampleForTests() { } public void startExample() { // Calls native code and holds a pointer to the C++ class. mNativeCPPObject = nativeInit("myParam"); } public void doStuff() { // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must be // done to: // * avoid leaks. // * using finalizers are not allowed to destroy the cpp class. nativeMethod(mNativeCPPObject); } public void finishExample() { // We're done, so let's destroy nativePtr object. nativeDestroy(mNativeCPPObject); } // ----------------------------------------------------------------------------------------------- // The following methods demonstrate exporting Java methods for invocation from C++ code. // Java functions are mapping into C global functions by prefixing the method name with // "Java__" // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. // Exported to C++ as: // Java_Example_javaMethod(JNIEnv* env, jobject obj, jint foo, jint bar) // Typically the C++ code would have obtained the jobject via the Init() call described above. @CalledByNative public int javaMethod(int foo, int bar) { return 0; } // Exported to C++ as Java_Example_staticJavaMethod(JNIEnv* env) // Note no jobject argument, as it is static. @CalledByNative public static boolean staticJavaMethod() { return true; } // No prefix, so this method is package private. It will still be exported. @CalledByNative void packagePrivateJavaMethod() {} // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to // call ClearException() and act as appropriate. // See more details at the "@CalledByNativeUnchecked" annotation. @CalledByNativeUnchecked void methodThatThrowsException() throws Exception {} // The generator is not confused by inline comments: // @CalledByNative void thisShouldNotAppearInTheOutput(); // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); /** * The generator is not confused by block comments: * @CalledByNative void thisShouldNotAppearInTheOutputEither(); * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); */ // String constants that look like comments don't confuse the generator: private String arrgh = "*/*"; //------------------------------------------------------------------------------------------------ // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to // prevent them being eliminated when unreferenced code is stripped. @AccessedByNative private int javaField; //------------------------------------------------------------------------------------------------ // The following methods demonstrate declaring methods to call into C++ from Java. // The generator detects the "native" and "static" keywords, the type and name of the first // parameter, and the "native" prefix to the function name to determine the C++ function // signatures. Besides these constraints the methods can be freely named. // This declares a C++ function which the application code must implement: // static jint Init(JNIEnv* env, jobject obj); // The jobject parameter refers back to this java side object instance. // The implementation must return the pointer to the C++ object cast to jint. // The caller of this method should store it, and supply it as a the nativeCPPClass param to // subsequent native method calls (see the methods below that take an "int native..." as first // param). private native int nativeInit(String param); // This defines a function binding to the associated C++ class member function. The name is // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. native // prefixes stripped). // The |nativeCPPClass| is automatically cast to type CPPClass* in order to obtain the object on // which to invoke the member function. private native void nativeDestroy(int nativeCPPClass); // This declares a C++ function which the application code must implement: // static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); // The jobject parameter refers back to this java side object instance. private native double nativeGetDoubleFunction(); // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than // jobject param, as the function is declared static. private static native float nativeGetFloatFunction(); // This function takes a non-POD datatype. We have a list mapping them to their full classpath in // jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that // function. private native void nativeSetNonPODDatatype(Rect rect); // This declares a C++ function which the application code must implement: // static ScopedJavaLocalRef GetNonPODDatatype(JNIEnv* env, jobject obj); // The jobject parameter refers back to this java side object instance. // Note that it returns a ScopedJavaLocalRef so that you don' have to worry about // deleting the JNI local reference. This is similar with Strings and arrays. private native Object nativeGetNonPODDatatype(); // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type and // call its Method member function. private native int nativeMethod(int nativeCPPClass); // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the // annotation rather than parameter name, which can thus be chosen freely. @NativeClassQualifiedName("CPPClass::InnerClass") private native double nativeMethodOtherP0(int nativePtr); // This "struct" will be created by the native side using |createInnerStructA|, // and used by the java-side somehow. // Note that |@CalledByNative| has to contain the inner class name. static class InnerStructA { private final long mLong; private final int mInt; private final String mString; private InnerStructA(long l, int i, String s) { mLong = l; mInt = i; mString = s; } @CalledByNative("InnerStructA") private static InnerStructA create(long l, int i, String s) { return new InnerStructA(l, i, s); } } private List mListInnerStructA = new ArrayList(); @CalledByNative private void addStructA(InnerStructA a) { // Called by the native side to append another element. mListInnerStructA.add(a); } @CalledByNative private void iterateAndDoSomething() { Iterator it = mListInnerStructA.iterator(); while (it.hasNext()) { InnerStructA element = it.next(); // Now, do something with element. } // Done, clear the list. mListInnerStructA.clear(); } // This "struct" will be created by the java side passed to native, which // will use its getters. // Note that |@CalledByNative| has to contain the inner class name. static class InnerStructB { private final long mKey; private final String mValue; private InnerStructB(long k, String v) { mKey = k; mValue = v; } @CalledByNative("InnerStructB") private long getKey() { return mKey; } @CalledByNative("InnerStructB") private String getValue() { return mValue; } } List mListInnerStructB = new ArrayList(); void iterateAndDoSomethingWithMap() { Iterator it = mListInnerStructB.iterator(); while (it.hasNext()) { InnerStructB element = it.next(); // Now, do something with element. nativeAddStructB(mNativeCPPObject, element); } nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); } native void nativeAddStructB(int nativeCPPClass, InnerStructB b); native void nativeIterateAndDoSomethingWithStructB(int nativeCPPClass); }