Skip to content

Annotations

gershnik edited this page Sep 30, 2019 · 4 revisions

You control JniGen output via two annotations: @ExposedToNative, which tells it which classes to process and @CalledByNative, which controls which class members to expose.

@ExposedToNative

This annotation should be placed on the classes that you need to make accessible from native code. When this annotation is placed on the class (let's call it SomeClass) the following things will happen:

  • A Java type (by default named jSomeClass) will be declared via DEFINE_JAVA_TYPE()

    • If the class is derived from any other @ExposedToNative class the conversions will also be declared via DEFINE_JAVA_CONVERSION()
    • If the class is used in an array SomeClass[] in any exposed method or field the array type (by default named jSomeClassArray) will be declared via DEFINE_ARRAY_JAVA_TYPE()

    To put it simply, the annotation processor will do the 'right thing'. All the necessary object types will be declared for you without you having to worry about it. All these declarations for all types will be put in a single header (by default named type_map.h). You can customize the header name via annotation processor options.

  • If the class has any native methods or any members annotated with @CalledByNative then the process will also generate a class type derived from java_runtime::simple_java_class.

    • The native methods will be declared as static methods of this class for you to implement. Thus, if you forget to implement them or their signature changes on Java side you will get a compile/link time error.
    • The members annotated with @CalledByNative will produce member functions with appropriate signatures as described below.

    Again, the annotation processor will do the 'right thing' generating all the boilerplate code for you. The class declaration will be put into a separate header file (named SomeClass_class.h by default).

    Important note For any element annotated with @CalledByNative all the types in its declaration (return type and argument types) must be either standard Java types (primitive types, Object or String) or themselves annotated with @ExposeToNative. Otherwise you will get an error during processing. What should you do if you have a type from external library you cannot annotate? See Processor Options page for info about how to handle this situation.

  • Finally for all the headers for class types generated above will be included from a file named all_classes.h This header will also define a macro JNIGEN_ALL_GENERATED_CLASSES which is a comma separated list of all the generated class names. This macro can be used as template parameters to java_class_table if you use it.

Customization

@ExposedToNative annotation has arguments that allow customization of the names of types it generates

The default value is used to define a 'stem' for all the generated names. For example if you have

@ExposedToNative("Foo")
class SomeClass {
...
}

then the instance type will become jFoo, the class type Foo_class and the class type will be put in the header named Foo_class.h. The default stem is simply the Java class name for top level classes and Outer_Inner for inner classes.

The typeName parameter can be used to specifically override type name ignoring the stem. For example if you have

@ExposedToNative(typename="jFoo")
class SomeClass {
...
}

then the instance type will become jFoo and the class type SomeClass_class in the header SomeClass_class.h

Similarly the className parameter allows to only override the class name.

@ExposedToNative(className="Foo")
class SomeClass {
...
}

will produce jSomeClass and Foo in the header SomeClass_class.h

Finally header parameter allows you to override the name of the header file generated for the class type.

@ExposedToNative(className="Foo.h")
class SomeClass {
...
}

will produce jSomeClass and SomeClass_class in the header Foo.h

@CalledByNative

You can put this annotation on constructors, methods (static and instance) and fields (static and instance) of a class annotated with @ExposeToNative. When you do so the annotated element will become a method on the class type with an appropriate signature.

  • For constructors the method name will ctor (this can be changed via processor options).
  • For methods the name will be the name of the Java method.
  • For fields the name will be get_<field name> to get the value and (if the field is not final) set_<field name> to set it.

All the boilerplate code: declaring java_methods etc. will be hidden inside the class declaration.

Customization

If you ever need to call an instance @CalledByNative method non-virtually you can set allowNonVirtualCall annotation parameter to true. This will produce an additional templated overload of the method on the C++ side that takes an extra parameter: java_class<T> & that will invoke non-virtual call.

Other uses for JniGen annotations

Preventing code elimination

If you use ProGuard or built-in Android code minifier they will usually strip Java methods called only from C++ as unused. After all these tools cannot 'see inside' C++ code. Your code, then, will mysteriously fail at runtime because classes and methods C++ expects to find are not there. The @ExposeToNative and @CalledByNative annotations provide an easy way to avoid this. Just tell the ProGuard to keep entities annotated with them. Something like

-keepclasseswithmembers class * {
   @smjni.jnigen.CalledByNative <methods>;
}

-keepclasseswithmembers class * {
   @smjni.jnigen.CalledByNative <fields>;
}

-keepclasseswithmembers class * {
    native <methods>;
}

Suppressing IDE/Lint warnings

Similarly IDEs tend to add "this is unused" warnings on things that are only called from native code. You can usually configure these warnings to ignore elements with certain annotations and it is easy to add JniGen annotations to the list. For IntelliJ based IDE the relevant setting is under Inspections/Java/Declaration Redundancy/Unused declaration. Under Options click on 'Entry Points', then 'Annotations' and add JniGen annotations to the list.