blob: 885a6c32af59ee16254c6b66cb82fe8517790976 [file] [log] [blame]
// Copyright 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 org.chromium.base.AccessedByNative;
import org.chromium.base.CalledByNative;
import org.chromium.base.CalledByNativeUnchecked;
import org.chromium.base.JNINamespace;
import org.chromium.base.NativeClassQualifiedName;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
// 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 "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.
//
// - 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.
long 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_<Class>_"
// 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 long 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(long 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<jobject> GetNonPODDatatype(JNIEnv* env, jobject obj);
// The jobject parameter refers back to this java side object instance.
// Note that it returns a ScopedJavaLocalRef<jobject> 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(long 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(long 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<InnerStructA> mListInnerStructA = new ArrayList<InnerStructA>();
@CalledByNative
private void addStructA(InnerStructA a) {
// Called by the native side to append another element.
mListInnerStructA.add(a);
}
@CalledByNative
private void iterateAndDoSomething() {
Iterator<InnerStructA> 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<InnerStructB> mListInnerStructB = new ArrayList<InnerStructB>();
void iterateAndDoSomethingWithMap() {
Iterator<InnerStructB> it = mListInnerStructB.iterator();
while (it.hasNext()) {
InnerStructB element = it.next();
// Now, do something with element.
nativeAddStructB(mNativeCPPObject, element);
}
nativeIterateAndDoSomethingWithStructB(mNativeCPPObject);
}
native void nativeAddStructB(long nativeCPPClass, InnerStructB b);
native void nativeIterateAndDoSomethingWithStructB(long nativeCPPClass);
native String nativeReturnAString(long nativeCPPClass);
}