Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Writing Advanced Applications
Chapter 5 Continued: Other Programming Issues

[<<BACK] [CONTENTS] [NEXT>>]

This section presents information on accessing classes, methods, and fields, and covers threading, memory, and Java1 virtual machine issues.


Language issues

So far, the native method examples have covered calling standalone C and C++ functions that either return a result or modify parameters passed into the function. However, C++ like the Java language uses instances of classes. If you create a class in one native method, the reference to this class does not have an equivalent class in the Java language, which makes it difficult to call functions on the C++ class that was first created.

One way to handle this situation is to keep a record of the C++ class reference and pass that back to a proxy or to the calling program. To ensure the C++ class persists across native method calls, use the C++ new operator to create a reference to the C++ object on the stack.

The following code provides a mapping between the Xbase database and Java language code. The Xbase database has a C++ API and uses an initialization class to perform subsequent database operations. When the class object is created, a pointer to this object is returned as a Java language int value. You can use a long or larger value for machines with greater than 32 bits.

public class CallDB {
  public native int initdb();
  public native short opendb(String name, int ptr);
  public native short GetFieldNo(
                        String fieldname, int ptr);

  static {
    System.loadLibrary("dbmaplib");
  }

  public static void main(String args[]) {
    String prefix=null;
    CallDB db=new CallDB();
    int res=db.initdb();
    if(args.length>=1) {
      prefix=args[0];
    }
    System.out.println(db.opendb("MYFILE.DBF", res));
    System.out.println(db.GetFieldNo("LASTNAME", res));
    System.out.println(db.GetFieldNo("FIRSTNAME", res));
   }
}
The return result from the call to the initdb native method, the int value, is passed to subsequent native method calls. The native code included in the dbmaplib.cc library de--references the Java language object passed in as a parameter and retrieves the object pointer. The line xbDbf* Myfile=(xbDbf*)ptr; casts the int pointer value to be a pointer of Xbase type xbDbf.
#include <jni.h>
#include <xbase/xbase.h>
#include "CallDB.h"

JNIEXPORT jint JNICALL Java_CallDB_initdb(
	JNIEnv *env, jobject jobj) {
  xbXBase* x;
  x= new xbXBase();
  xbDbf* Myfile;
  Myfile =new xbDbf(x);
  return ((jint)Myfile);
}

JNIEXPORT jshort JNICALL Java_CallDB_opendb(
	                   JNIEnv *env, jobject jobj, 
	                   jstring dbname, jint ptr) {
  xbDbf* Myfile=(xbDbf*)ptr;
  return((*Myfile).OpenDatabase( "MYFILE.DBF"));
}

JNIEXPORT jshort JNICALL Java_CallDB_GetFieldNo
                           (JNIEnv *env, jobject jobj, 
                           jstring fieldname, 
                           jint ptr) {
  xbDbf* Myfile=(xbDbf*)ptr;
  return((*Myfile).GetFieldNo(
	env->GetStringUTFChars(fieldname,0)));
}

Calling Methods

The section on arrays highlighted some reasons for calling Java language methods from within native code; for example, when you need to free the result you intend to return. Other uses for calling Java native methods from within your native code would be if you need to return more than one result or you just simply want to modify Java language values from within native code.

Calling a Java language method from within native code involves the following three steps:

  1. Retrieve a class reference
  2. Retrieve a method identifier
  3. Call the Methods

Retrieve a Class Reference

The first step is to retrieve a reference to the class that contains the methods you want to access. To retrieve a reference, you can either use the FindClass method or access the jobject or jclass argument to the native method.
Use the FindClass method:

  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
  jclass cls = (*env)->FindClass(env, "ClassName");
  }
Use the jobject argument:

  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
  jclass cls=(*env)->GetObjectClass(env, jobj);
  }
or
Use the jclass argument:

  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jclass jcls){
  jclass cls=jcls;
  }

Retrieve a Method Identifier

Once the class has been obtained, the second step is to call the GetMethodID function to retrieve an identifier for a method you select in the class. The identifier is needed when calling the method of that class instance. Because the Java language supports method overloading, you also need to specify the particular method signature you want to call. To find out what signature your Java language method uses, run the javap command as follows:
  javap -s Class
The method signature used is displayed as a comment after each method declaration as shown here:
bash# javap -s ArrayHandler
Compiled from ArrayHandler.java
public class ArrayHandler extends java.lang.Object {
  java.lang.String arrayResults[];
   /*   [Ljava/lang/String;   */
  static {};
   /*   ()V   */
  public ArrayHandler();
   /*   ()V   */
  public void displayArray();
   /*   ()V   */
  public static void main(java.lang.String[]);
   /*   ([Ljava/lang/String;)V   */
  public native void returnArray();
   /*   ()V   */
  public void sendArrayResults(java.lang.String[]);
   /*   ([Ljava/lang/String;)V   */
}
Use the GetMethodID function to call instance methods in an object instance, or use the GetStaticMethodID function to call static method. Their argument lists are the same.

Call the Methods

Third, the matching instance method is called using a Call<type>Method function. The type value can be Void, Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double.

The parameters to the method can be passed as a comma-separated list, an array of values to the Call<type>MethodA function, or as a va_list. The va_list is a construct often used for variable argument lists in C. CallMethodV is the function used to pass a va_list ().

Static methods are called in a similar way except the method naming includes an additional Static identifier, CallStaticByteMethodA, and the jclass value is used instead of jobject.

The next example returns the object array by calling the sendArrayResults method from the ArrayHandler class.

// ArrayHandler.java
public class ArrayHandler {
  private String arrayResults[];
  int arraySize=-1;

  public native void returnArray();

  static{
    System.loadLibrary("nativelib");
  }
 
  public void sendArrayResults(String results[]) {
    arraySize=results.length;
    arrayResults=new String[arraySize];
    System.arraycopy(results,0,
                     arrayResults,0,arraySize);
  }

  public void displayArray() {
    for (int i=0; i<arraySize; i++) {
      System.out.println("array element 
	"+i+ "= " + arrayResults[i]);
    }
  }

  public static void main(String args[]) {
    String ar[];
    ArrayHandler ah= new ArrayHandler();
    ah.returnArray();
    ah.displayArray();
  }
}
The native C++ code is defined as follows:
#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"

JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){

  jobjectArray ret;
  int i;
  jclass cls;
  jmethodID mid;

  char *message[5]= {"first", 
		"second", 
		"third", 
		"fourth", 
		"fifth"};

  ret=(jobjectArray)env->NewObjectArray(5,
      env->FindClass("java/lang/String"),
      env->NewStringUTF(""));

  for(i=0;i<5;i++) {
    env->SetObjectArrayElement(
	ret,i,env->NewStringUTF(message[i]));
  }

  cls=env->GetObjectClass(jobj);
  mid=env->GetMethodID(cls, 
	"sendArrayResults", 
	"([Ljava/lang/String;)V");
  if (mid == 0) {
    cout <<Can't find method sendArrayResults";
    return;
  }

  env->ExceptionClear();
  env->CallVoidMethod(jobj, mid, ret);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" <<endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  return;
}
To build this on Linux, run the following commands:
  javac ArrayHandler.java
  javah -jni ArrayHandler

  g++  -o libnativelib.so 
	-shared -Wl,-soname,libnative.so
	-I/export/home/jdk1.2/include 
	-I/export/home/jdk1.2/include/linux nativelib.cc  
	-lc
If you want to specify a super class method; for example, to call the parent constructor, you can do so by calling the CallNonvirtual<type>Method functions.

One important point when calling Java language methods or fields from within native code is you need to catch any raised exceptions. The ExceptionClear function clears any pending exceptions while the ExceptionOccured function checks to see if an exception has been raised in the current JNI session.

Accessing Fields

Accessing Java language fields from within native code is similar to calling Java language methods. However, the set or field is retrieved with a field ID, instead of a method ID.

The first thing you need to do is retrieve a field ID. You can use the GetFieldID function, but specify the field name and signature in place of the method name and signature. Once you have the field ID, call a Get<type>Field function to set the field value. The <type> is the same as the native type being returned except the j is dropped and the first letter is capitalized. For example, the <type> value is Int for native type jint, and Byte for native type jbyte.

The Get<type>Field function result is returned as the native type. For example, to retrieve the arraySize field in the ArrayHandler class, call GetIntField as shown in the following example.

The field can be set by calling the env->SetIntField(jobj, fid, arraysize) functions. Static fields can be set by calling SetStaticIntField(jclass, fid, arraysize) and retrieved by calling GetStaticIntField(jobj, fid).

#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"

JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){

    jobjectArray ret;
    int i;
    jint arraysize;
    jclass cls;
    jmethodID mid;
    jfieldID fid;

    char *message[5]= {"first",
                "second",
                "third",
                "fourth",
                "fifth"};

    ret=(jobjectArray)env->NewObjectArray(5,
        env->FindClass("java/lang/String"),
        env->NewStringUTF(""));

    for(i=0;i<5;i++) {
      env->SetObjectArrayElement(
        ret,i,env->NewStringUTF(message[i]));
    }

    cls=env->GetObjectClass(jobj);
    mid=env->GetMethodID(cls,
        "sendArrayResults",
        "([Ljava/lang/String;)V");
    if (mid == 0) {
        cout <<Can't find method sendArrayResults";
        return;
    }

    env->ExceptionClear();
    env->CallVoidMethod(jobj, mid, ret);
    if(env->ExceptionOccurred()) {
       cout << "error occured copying 
                        array back" << endl;
       env->ExceptionDescribe();
       env->ExceptionClear();
    }
    fid=env->GetFieldID(cls, "arraySize",  "I");
    if (fid == 0) {
        cout <<Can't find field arraySize";
        return;
    }
    arraysize=env->GetIntField(jobj, fid);
    if(!env->ExceptionOccurred()) {
       cout<< "size=" << arraysize << endl;
    } else {
       env->ExceptionClear();
    }
    return;
}

Threads and Synchronization

Although the native library is loaded once per class, individual threads in an application written in the Java language use their own interface pointer when calling the native method. If you need to restrict access to a Java language object from within native code, you can either ensure that the Java language methods you call have explicit synchronization or you can use the JNI MonitorEnter and MonitorExit functions.

In the Java langauge, code is protected by a monitor whenever you specify the synchronized keyword. In the Java programming language, the monitor enter and exit routines are normally hidden from the application developer. In JNI, you need to explicitly delineate the entry and exit pointws of thread safe code.

The following example uses a Boolean object to restrict access to the CallVoidMethod function.

  env->ExceptionClear();
  env->MonitorEnter(lock);
  env->CallVoidMethod(jobj, mid, ret);
  env->MonitorExit(lock);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" << endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
You may find that in cases where you want access to a local system resource like a MFC window handle or message queue, it is better to use one Java Thread and access the local threaded native event queue or messaging system from within the native code.

Memory Issues

By default, JNI uses local references when creating objects inside a native method. This means when the method returns, the references are eligible to be garbage collected. If you want an object to persist across native method calls, use a global reference instead. A global reference is created from a local reference by calling NewGlobalReference on the the local reference.

You can explicitly mark a reference for garbage collection by calling DeleteGlobalRef on the reference. You can also create a weak style Global reference that is accessible outside the method, but can be garbage collected. To create one of these references, call NewWeakGlobalRef and DeleteWeakGlobalRef to mark the reference for garbage collection.

You can even explicitly mark a local reference for garbage collection by calling the env->DeleteLocalRef(localobject) method. This is useful if you are using a large amount of temporary data.

  static jobject stringarray=0;

  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){

    jobjectArray ret;
    int i;
    jint arraysize;
    int asize;
    jclass cls, tmpcls;
    jmethodID mid;
    jfieldID fid;

    char *message[5]= {"first", 
		"second", 
		"third", 
		"fourth", 
		"fifth"};

    ret=(jobjectArray)env->NewObjectArray(5,
        env->FindClass("java/lang/String"),
        env->NewStringUTF(""));

  //Make the array available globally
    stringarray=env->NewGlobalRef(ret);

  //Process array
  // ...

  //clear local reference when finished..
    env->DeleteLocalRef(ret);
  }

Invocation

The section on calling methods showed you how to call a method or field in a Java language program using the JNI interface and a class loaded using the FindClass function. With a little more code, you can create a standalone program that invokes a Java virtual machine and includes its own JNI interface pointer that can be used to create instances of Java language classes. In the Java 2 release, the runtime program named java is a small JNI application that does exactly that.

You can create a Java virtual machine with a call to JNI_CreateJavaVM, and shut the created Java virtual machine down with a call to JNI_DestroyJavaVM. A Java vitual machine might also need some additional environment properties. These properties can be passed to the JNI_CreateJavaVM function in a JavaVMInitArgs structure.

The JavaVMInitArgs structure contains a pointer to a JavaVMOption value used to store environment information such as the classpath and Java virtual machine version, or system properties that would normally be passed on the command line to the program.

When the JNI_CreateJavaVM function returns, you can call methods and create instances of classes using the FindClass and NewObject functions the same way you would for embedded native code.


Note: The Java virtual machine invocation used to be only used for native thread Java virtual machines. Some older Java virtual machines have a green threads option that is stable for invocation use. On a Unix platform, you may also need to explicitly link with -lthread or -lpthread.
This next program invokes a Java virtual machine, loads the ArrayHandler class, and retrieves the arraySize field which should contain the value minus one. The Java virtual machine options include the current path in the classpath and turning the Just-In-Time (JIT) compiler off -Djava.compiler=NONE.
#include <jni.h>

void main(int argc, char *argv[], char **envp) {
  JavaVMOption options[2];
  JavaVMInitArgs vm_args;
  JavaVM *jvm;
  JNIEnv *env;
  long result;
  jmethodID mid;
  jfieldID fid;
  jobject jobj;
  jclass cls;
  int i, asize;

  options[0].optionString = ".";
  options[1].optionString = "-Djava.compiler=NONE";

  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_FALSE;

  result = JNI_CreateJavaVM(
             &jvm,(void **)&env, &vm_args);
  if(result == JNI_ERR ) {
    printf("Error invoking the JVM");
    exit (-1);
  }

  cls = (*env)->FindClass(env,"ArrayHandler");
  if( cls == NULL ) {
    printf("can't find class ArrayHandler\n");
    exit (-1);
  }
  (*env)->ExceptionClear(env);
  mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
  jobj=(*env)->NewObject(env, cls, mid);
  fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
  asize=(*env)->GetIntField(env, jobj, fid);

  printf("size of array is %d",asize);
  (*jvm)->DestroyJavaVM(jvm);
}

Attaching Threads

After the Java virtual machine is invoked, there is one local thread running the Java virtual machine. You can create more threads in the local operating system and attach the Java virtual machine to those new threads. You might want to do this if your native application is multi-threaded.

Attach the local thread to the Java virtual machine with a call to AttachCurrentThread. You need to supply pointers to the Java virtual machine instance and JNI environment. In the Java 2 platform, you can also specify in the third parameter the thread name and/or group you want this new thread to live under. It is important to detach any thread that has been previously attached; otherwise, the program will not exit when you call DestroyJavaVM.

#include <jni.h>
#include <pthread.h>

JavaVM *jvm;

void *native_thread(void *arg) {
  JNIEnv *env;
  jclass cls;
  jmethodID mid;
  jfieldID fid;
  jint result;
  jobject jobj;
  JavaVMAttachArgs args;
  jint asize;
  
  args.version= JNI_VERSION_1_2;
  args.name="user";
  args.group=NULL;
  result=(*jvm)->AttachCurrentThread(
	jvm, (void **)&env, &args);

  cls = (*env)->FindClass(env,"ArrayHandler");
  if( cls == NULL ) {
    printf("can't find class ArrayHandler\n");
    exit (-1);
  }
  (*env)->ExceptionClear(env);
  mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
  jobj=(*env)->NewObject(env, cls, mid);
  fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
  asize=(*env)->GetIntField(env, jobj, fid);
  printf("size of array is %d\n",asize);
  (*jvm)->DetachCurrentThread(jvm);
}

void main(int argc, char *argv[], char **envp) {
  JavaVMOption *options;
  JavaVMInitArgs vm_args;
  JNIEnv *env;
  jint result;
  pthread_t tid;
  int thr_id;
  int i;

  options = (void *)malloc(3 * sizeof(JavaVMOption));

  options[0].optionString = "-Djava.class.path=.";
  options[1].optionString = "-Djava.compiler=NONE";

  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_FALSE;

  result = JNI_CreateJavaVM(&jvm,(void **)&env, &vm_args);
  if(result == JNI_ERR ) {
    printf("Error invoking the JVM");
    exit (-1);
  }

  thr_id=pthread_create(&tid, NULL, native_thread, NULL);

// If you don't have join, sleep instead
//sleep(1000);
  pthread_join(tid, NULL);
  (*jvm)->DestroyJavaVM(jvm);
  exit(0);
}

_______
1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

[TOP]


[ This page was updated: 13-Oct-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.