Of course, Java programming language functions can call C functions—that is what native methods are for. Can we go the other way? Why would we want to do this anyway? It often happens that a native method needs to
request a service from an object that was passed to it. We’ll first show you how to do it for instance methods, then for static methods.
1. Instance Methods
As an example of calling a Java method from native code, let’s enhance the Printf class and add a method that works similarly to the C function fprintf. That is, it should be able to print a string on an arbitrary PrintWriter object. Here is the definition of the method in Java:
public native static void fprint(PrintWriter out, String s, double x);
We’ll first assemble the string to be printed into a String object str, as in the sprint method that we already implemented. Then, from the C function that implements the native method, we’ll call the print method of the PrintWriter class.
You can call any Java method from C by using the function call
(*env)->CallXrxMethod(env, implicit parameter, methodID, explicit parameters)
Replace Xxx with Void, Int, Object, and so on, depending on the return type of the method. Just as you need a fieldID to access a field of an object, you need a method ID to call a method. To obtain a method ID, call the JNI function GetMethodID and supply the class, the name of the method, and the method signature.
In our example, we want to obtain the ID of the print method of the PrintWriter class. The PrintWriter class has several overloaded methods called print. For that reason, you must also supply a string describing the parameters and the return value of the specific function that you want to use. For example, we want to use void print(java.lang.String). As described in the preceding section, we must now “mangle” the signature into the string “(Ljava/lang/String;)V”.
Here is the complete code to make the method call:
/* get the class of the implicit parameter */
class_PrintWriter = (*env)->GetObjectClass(env, out);
/* get the method ID */
id_print = (*env)->GetMethodID(env, class_PrintWriter, “print”, “(Ljava/lang/String;)V”);
/* call the method */
(*env)->CallVoidMethod(env, out, id_print, str);
Listings 12.14 and 12.15 show the Java code for a test program and the Printf3 class. Listing 12.16 contains the C code for the native fprint method.
2. Static Methods
Calling static methods from native methods is similar to calling instance methods. There are two differences:
- Use the GetStaticMethodID and CallStaticXrxMethod functions
- Supply a class object, not an implicit parameter object, when invoking the method
As an example of this, let’s make the call to the static method
from a native method. The return value of this call is a string that gives the current class path.
First, we have to find the class to use. As we have no object of the class System readily available, we use FindClass rather than GetObjectClass.
jclass class_System = (*env)->FindClass(env, “java/lang/System”);
Next, we need the ID of the static getProperty method. The encoded signature of that method is
because both the parameter and the return value are strings. Hence, we obtain the method ID as follows:
jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, “getProperty”,
Finally, we can make the call. Note that the class object is passed to the CallStaticObjectMethod function.
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty,
The return value of this method is of type jobject. If we want to manipulate it as a string, we must cast it to jstring:
jstring str_ret = (jstring) obj_ret;
A native method can create a new Java object by invoking its constructor. Invoke the constructor by calling the NewObject function.
jobject obj_new = (*env)->NewObject(env, class, methodID, construction parameters);
You can obtain the method ID needed for this call from the GetMethodID function by specifying the method name as “<init>” and the encoded signature of the constructor (with return type void). For example, here is how a native method can create a FileOutputStream object:
const char fileName = “. . .”;
jstring str_fileName = (*env)->NewStringUTF(env, fileName);
jclass class_FileOutputStream = (*env)->FindClass(env, “java/io/FileOutputStream”);
= (*env)->GetMethodID(env, class_FileOutputStream, “<init>”, “(Ljava/lang/String;)V”); jobject obj_stream
= (*env)->NewObject(env, class_FileOutputStream, id_FileOutputStream, str_fileName);
Note that the signature of the constructor takes a parameter of type java.lang .String and has a return type of void.
4. Alternative Method Invocations
Several variants of the JNI functions can be used to call a Java method from native code. These are not as important as the functions that we already discussed, but they are occasionally useful.
The CallNonvirtualXrxMethod functions receive an implicit argument, a method ID, a class object (which must correspond to a superclass of the implicit argument), and explicit arguments. The function calls the version of the method in the specified class, bypassing the normal dynamic dispatch mechanism.
All call functions have versions with suffixes “A” and “V” that receive the explicit parameters in an array or a va_list (as defined in the C header stdarg.h).
Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.