JavaData & JavaManipulating Java Objects in Native Code

Manipulating Java Objects in Native Code

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java object manipulation in native code refers to the way of establishing communication between two languages of different paradigms and use each other’s property in a seamless manner. Classes and objects are the basic building blocks of an object-oriented programming language like Java. With the help of JNI (Java Native Interface), we can declare a native method inside Java classes and define it in native code such as C/C++. Often, when elaborating the code, it needs a reference to Java objects/classes. The catch here is: How can we access Java objects/classes in native C/C++? The article provides a glimpse on how to manipulate Java objects from a native code such as C/C++ using JNI.

Ways to Manipulate

When manipulating a Java object in a native code, we shall keep our discussing limited to the following key points:

  • Creating and invoking Java instances
  • Accessing objects/classes
  • Modifying fields inside Java object
  • Using/Invoking static Java methods

Using a Class Reference

The references defined with the jclass and jobject data typea in native code are used to get hold of the Java class object and Java objects, respectively. Class reference is necessary when we want invoke a native static method declared in a Java class. We may create jclass and jobject as follows in C/C++ code.

jclass jcls=env->FindClass("java/lang/String");

jobject jobj=env->AllocateObject(jcls);

Here, the JNI method FindClass() takes a full qualified package name, java.lan.String, and gets a reference of the class object. Observe that the front slash is used in place of a dot. The AllocateObject() JNI function actually creates the object in memory. Some variations of a class signature referring array object are as follows.

  • To get a reference of the int[] array class object:
    jcls = env->FindClass("[I");
  • To get a reference of the int[][] array class object:
    jcls = env->FindClass("[[I");
  • To get a reference of the String[] array class object. Observe the ; (semi-colon) used in the signature:
    jcls = env->FindClass("[Ljava/lang/String;");

Accessing Fields

Each field defined in a Java class is uniquely identified by an ID. This field ID can be obtained with the help of thhe GetFieldID() JNI function. There is a separate function, called GetStaticFieldID(), to get hold of the static field defined within a Java class. Method signatures are as follows.

  • jfieldID GetFieldID(jclass jcls, const char *name, const char *sig)
  • jfieldID GetStaticFieldID(jclass jcls, const char *name, const char *sig)

They can be invoked as follows:

package org.javajni.demo;

public class DemoClass {
   private int val = 10;
   private static int staticValue=20;
   ...
}

jclass jcls=env->FindClass("org/javajni/demo/DemoClass");
jobject jobj = env->AllocObject(jcls);
  • To get the field ID:
    jfieldID valId = env->GetFieldID(jcls, "val", "I");
    jfieldID staticValId = env->GetStaticFieldID(jcls,
       "staticValue", "I");
    
  • To get the field values:
    jint intVal = env->GetIntField(jobj, valId);
    jint staticIntVal= env->GetStaticIntField(jcls,
       staticValId);
    
  • Manipulating the values:
    intVal = intVal + 10;
    staticIntVal = staticIntVal + 1;
    env->SetIntField(jobj, valId, intVal);
    env->SetStaticIntField(jcls, staticValId,
       staticIntVal);
    

Accessing Methods

In a similar manner, a method ID for a Java method and a method declared as static can be obtained with the help of the GetMethodID() and GetStaticMethodID() JNI functions, respectively. The method signatures are as follows:

  • jmethodID GetMethodID(jclass jcls, const char *name, const char *sig)
  • jmethodID GetStaticMethodID(jclass jcls, const char *name, const char *sig)

The method signature for a specific method can be obtained with the help of the javap command. Following are some examples.

package org.javajni.demo;

public class DemoClass {
   ...

   public void method() {

   }
   public void anotherMethod(int val) {

   }
   public static void staticMethod(){

   }
   public int returnValue(int val){
      return val*10;
   }
}
  • To get the ID for a method:
    jmethodID mID = env->GetMethodID(jcls,
       "method", "()V");
    
  • To get the ID for a static method:
    jmethodID mID = env->GetStaticMethodID(jcls,
       "staticMethod", "()V");
    
  • To get the ID of a method with a return value:
    jmethodID mID = env->GetMethodID(jcls,
       "getLength", "(Ljava/lang/String;)I");
    

Once the method ID is obtained, it can be invoked in the native code, as follows.

  • Calling a void method:
    env->CallVoidMethod(jobj, mID);
  • Calling a void method with a parameter:
    env->CallVoidMethod(obj, mID, 9);
  • Calling a method that returns an integer value and takes a parameter:
    jint val = env->CallIntMethod(jobj, mID, 122);

Creating a Java Object in Native Code

A Java object can be created in a native code in two ways:

  • Either by invoking as specific constructor or
  • Without invoking any constructor

If we do not use any constructor, we need to invoke the AllocObject() JNI function to allocate memory for the Java object. The instance fields of the class will have their default values according to the data type.

For example, in the following code, jclass get holds of the java.lang.String class reference and a string object is created by AllocObject(). The variable jobject references the Java String object that is created without using any constructor, initialized with a default value.

jclass jcls=env->FindClass("java/lang/String");
jobject jobj=env->AllocObject(jcls);
if(jobj==NULL)
   cout<<"object cannot be created";
else
   cout<<"object created";

In cases where we want to use a constructor to create a Java object in native code, we may use following JNI functions:

  • jobject NewObject(jclass jcls, jmethodID mID, arg1, arg2…)
  • jobject NewObjectA(jclass jcls, jmethodID mID, const jvalue *args)
  • jobject NewObjectV(jclass jcls, jmethodID mID, va_list args)

The method ID parameter refers to the specific method (constructor) ID we want to call. There are some special string values, such as “<init>” or “$init$”, in case we want to invoke a constructor. The code next snippet should clarify this point further.

package org.javajni.demo;

public
class DemoClass {
   private int val = 10;

   public DemoClass() {
   }

   public DemoClass(int val) {
      this.val = val;
   }

   public int returnValue() {
      return value;
   }
}

Now, to get a reference of the preceding class in C/C++, we can write it as:

jclass jcls=env->FindClass("org/javajni/demo/DemoClass");

jobject jobj = env->AllocObject(jcls);

At this point, C++ code allocates memory but without invoking the constructor. The field value val has a default value of 0 even though we have initialized with 10. The JNI function signatures for creating objects with the help of a constructor are as follows.

  • jobject NewObject(jclass jcls, jmethodID mID, arg1, arg2…)
  • jobject NewObject(jclass jcls, jmethodID mID, const jvalue *args)
  • jobject NewObject(jclass jcls, jmethodID mID, va_list args)

Now to invoke a specific constructor we may write.

  • Getting method ID for default constructor:
    jmethodID mID1 = env->GetMethodID(jcls,
       "<init>", "()V");
    
  • Create an object using a default constructor:
    jobject jobj1= env->NewObject(jcls, mID1);
  • Getting a method ID for the parametrized constructor:
    jmethodID mID2 = env->GetMethodID(jcls,
       "<init>","(I)V");
    
  • Creating an object of class passing some value to the constructor:
    jobject jobj2 = env->NewObject(jcls, mID2, 123);

Note that AllocObject() and NewObject() are two JNI functions to create a non-arrayed reference of an object. If we want to create an arrayed reference of an object, we must use the NewObjectArray() JNI function.

Conclusion

The article provides a rudimentary sketch on how to manipulate Java objects in native code. Multilingual mapping is not very straightforward. The method signature looks clumsy at times. But, I think on close observation a logical pattern is visible, which makes it easy to use rather than denigrating it as difficult to grasp.

Although here we have limited our idea to only one way communication such as native code using a Java object, other ways around are equally possible. To end with a note of caution, unless you are coding for fun, JNI should be used in an application programming only when it is absolutely necessary.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories