Skip to content

Accessing Methods and Fields

Eugene Gershnik edited this page Apr 19, 2023 · 8 revisions

Unless you are doing something very simple with your JNI code you will need to call Java methods and access Java fields from C++. SimpleJNI wraps raw JNI's jmethodID and jfieldID and the horrible API that uses them in clean type-safe wrappers. These are called method and field objects. There are slightly different types for instance methods, static methods and constructors. Similarly there are different type for instance and static fields. You declare them using variadic templates as follows

java_method<ReturnType, TypeOfThis, /**param types if any**/>
java_static_method<ReturnType, TypeOfClass, /**param types if any**/>
java_constructor<ReturnType, /**param types if any**/>
java_field<FieldType, TypeOfThis>
java_static_field<FieldType, TypeOfClass>

where TypeOfThis/TypeOfClass are the types of the class exposing the method or field. For Java constructor ReturnType is TypeOfThis so it needs to be specified only once.

Java constructors in SimpleJNI combine the functionality of JNI's NewObject and constructor invocation. These are always used together and there is no reason to introduce potential errors by requiring two distinct calls.

For example consider the following Java class' methods and fields

class Something
{
    Something(String arg1, long arg2);
    boolean someMethod(String arg1, long arg2);
    static boolean someStaticMethod(String arg1, long arg2);

    long m_longField;
    static long s_longField;
}

These can be accessed in the following way

jJNIEnv * env = ...;
//An instance of the class object for the Something class. See note 1 below
java_class<jSomething> cls = ...;

//Declare and initialize method and field objects. See notes 2 and 3
java_constructor<jSomething, jstring, jlong> ctor(env, cls);
java_method<jboolean, jSomething, jstring, jlong> someMethod(env, cls, "someMethod"); 
java_static_method<jboolean, jSomething, jstring, jlong> someStaticMethod(env, cls, "someStaticMethod");
java_field<jlong, jSomething> longField(env, cls, "m_longField");
java_static_field<jlong, jSomething> staticLongField(env, cls, "s_longField");

jstring str = ...;

//Now let's call methods and access fields. See note 4

auto obj = ctor(env, str, 42); //See notes 5 and 6
jboolean res = someMethod(env, obj, str, 7);
res = someStaticMethod(env, cls , str, 7);
//non-virtual call. Need to specify class here
res = someMethod.call_non_virtual(env, obj, cls, str, 7); 

jlong val = longField.get(env, obj);
longField.set(env, obj, val + 1);

val = staticLongField.get(env, cls);
staticLongField.set(env, cls, val + 1);

Notes:

  1. java_class<jSomething> represent a Java class object. It is required for initialization of any method or field object. See Representing Java Classes for more details.
  2. If you have made a mistake in the method type declaration (say it really returns a jint rather than jboolean) or mistyped method name the constructor of method/field object will fail with an exception at runtime.
  3. For all methods and fields except Java constructors, the last argument in their constructor calls above is the simple Java name of the method/field. You don't need to know anything about (Ljava.lang.String;J)B or whatever the magic incantation is in this case. There is no need to pass any name to the java_constructor's constructor. The name of the Java constructor method is pre-defined and fixed so you don't need to know it or care.
  4. If you make a mistake and try to pass wrong arguments to method calls they simply won't compile. Your IDE will also likely be able to auto-complete the calls for you.
  5. The return type of any SimpleJNI methods that returns Java objects (like the constructor above) is a smart reference. See Smart References for more details.

Types allowed in method/field declarations

You can only use "JNI compatible" types in declarations of methods and fields. Those are primitive types like jint or jlong, object types like jobject or jSomething you declared or jxXXArray types. Now, things like jint are actually typedefs which on your particular platform might be the same as int. You might get away with just typing int and things will work just fine. However, this will easily break with other types. For example jlong will very likely be not the same as long and jboolean almost certainly not the same as bool. Plus one day you might want to port your code to another platform and you will discover that there even jint is not the same as int.
It is best to get into a habit of always using j-prefixed types in all JNI declarations, whether of Java entities you access or native Java methods you implement.