2010年10月6日 星期三

JNI - Invoking Constructors

In the JNI, constructors may be invoked following steps similar to those used for calling instance methods.
    Invoke constructors as following steps:
  1. To obtain the method ID of a constructor, pass "<init>" as the method name and "V" as the return type in the method descriptor.
  2. To invoke the constructor by passing the method ID to JNI functions such as NewObject.
  3. (Example) The following code implements the equivalent functionality of the JNI function NewString, which constructs a java.lang.String object from the Unicode characters stored in a C buffer:
jstring MyNewString(JNIEnv *env, jchar *chars, jint len)
{
    jclass stringClass;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    stringClass = (*env)->FindClass(env, "java/lang/String");
    if (stringClass == NULL) {
        return NULL; /* exception thrown */
    }
    /* Get the method ID for the String(char[]) constructor */
    cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V");
    if (cid == NULL) {
        return NULL; /* exception thrown */
    }

    /* Create a char[] that holds the string characters */
    elemArr = (*env)->NewCharArray(env, len);
    if (elemArr == NULL) {
        return NULL; /* exception thrown */
    }
    (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

    /* Construct a java.lang.String object */
    result = (*env)->NewObject(env, stringClass, cid, elemArr);
 
    /* Free local references */
    (*env)->DeleteLocalRef(env, elemArr);
    (*env)->DeleteLocalRef(env, stringClass);
    return result;
}
  1. FindClass(): returns a reference to the java.lang.String class.
  2. GetMethodID(): returns the method ID for the string constructor, String(char[] chars).
  3. NewCharArray(): to allocate a character array that holds all the string elements.
  4. NewObject(): invokes the constructor specified by the method ID.
  5. DeleteLocalRef(): allows the virtual machine to free the resources used by local references elemArr and stringClass.
It is also possible to invoke constructors using the CallNonvirtualVoidMethod function.
    result = (*env)->NewObject(env, stringClass, cid, elemArr);
  • The single NewObject call above may be replaced by an AllocObject call followed by a CallNonvirtualVoidMethod call:
  • result = (*env)->AllocObject(env, stringClass);
    if (result) {
        (*env)->CallNonvirtualVoidMethod(env, result, stringClass, cid, elemArr);
        /* we need to check for possible exceptions */
        if ((*env)->ExceptionCheck(env)) {
            (*env)->DeleteLocalRef(env, result);
            result = NULL;
        }
    }
    
    AllocObject(): creates an uninitialized object.
    CallNonvirtualVoidMethod(): invokes constructors.
    In most cases, however, you should use NewObject and avoid the more error-prone AllocObject/CallNonvirtualVoidMethod pair.

2010年10月4日 星期一

JNI - Access Instance (object) / Static (class) Method

There are several kinds of methods in the Java programming language.
  • (Object) Instance methods must be invoked on a specific instance of a class.
  • (Class) Static methods may be invoked independent of any instance.
  • Calling Instance Methods
  • (Example) InstanceMethodCall.java: a native method that in turn calls an instance method implemented in the Java programming language.
    1. InstanceMethodCall.java
    2. class InstanceMethodCall {
          private native void nativeMethod();
          private void callback() {
              System.out.println("Output: In Java code");
          }
          public static void main(String args[]) {
              InstanceMethodCall c = new InstanceMethodCall();
              c.nativeMethod();
          }
          static {
              System.loadLibrary("InstanceMethodCall");
          }
      }
      
    3. Generate C header file: InstanceMethodCall.h
    4. /* DO NOT EDIT THIS FILE - it is machine generated */                                                    
      #include <jni.h>
      /* Header for class InstanceMethodCall */
      
      #ifndef _Included_InstanceMethodCall
      #define _Included_InstanceMethodCall
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     InstanceMethodCall
       * Method:    nativeMethod
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod
        (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    5. C implementation of InstanceMethodCall.c
    6. #include <stdio.h>
      #include <jni.h>
      
      JNIEXPORT void JNICALL 
       Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
       {
           jclass cls = (*env)->GetObjectClass(env, obj);
           jmethodID mid = 
               (*env)->GetMethodID(env, cls, "callback", "()V");
           if (mid == NULL) {
               return; /* method not found */
           }
           printf("Output: In C code\n");
           (*env)->CallVoidMethod(env, obj, mid);
       }
      
        Two steps required to call an instance method:
      1. GetMethodID performs a lookup for the method in the given class. The lookup is based on the name and type descriptor of the method. If the method does not exist, GetMethodID returns NULL and causes a NoSuchMethodError to be thrown in the code.
      2. CallVoidMethod invokes an instance method that has the return type void. You pass the object, the method ID, and the actual arguments to CallVoidMethod.
    7. Generate the shared library file - libInstanceMethodCall.so
    8. gcc -shared -I/usr/local/jdk1.6.20_21/include -I/usr/local/jdk1.6.20_21/include/linux InstanceMethodCall.c -o libInstanceMethodCall.so
    9. RUN InstanceMethodCall output:
    10. Output: In C code
      Output: In Java code

  • Calling Static Methods
  • Similarly, we can perform callbacks to static methods from native code by following steps:
    • Obtain the method ID using GetStaticMethodID, as opposed to GetMethodID.
    • Pass the class, method ID, and arguments to one of the family of static method invocation functions: CallStaticVoidMethod, CallStaticBooleanMethod, and so on.
      (Example) StaticMethodCall.java: a slight variation of the earlier InstanceMethodCall example
    1. StaticMethodCall.java
    2. class StaticMethodCall {
          private native void nativeMethod();
          private static void callback() {
              System.out.println("Output: In Java code");
          }
          public static void main(String args[]) {
              StaticMethodCall c = new StaticMethodCall();
              c.nativeMethod();
          }
          static {
              System.loadLibrary("StaticMethodCall");
          }
      }
      
    3. Generate C header file: StaticMethodCall.h
    4. /* DO NOT EDIT THIS FILE - it is machine generated */                           
      #include <jni.h>
      /* Header for class StaticMethodCall */
      
      #ifndef _Included_StaticMethodCall
      #define _Included_StaticMethodCall
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     StaticMethodCall
       * Method:    nativeMethod
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_StaticMethodCall_nativeMethod
        (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    5. C implementation of StaticMethodCall.c
    6. #include <stdio.h>
      #include <jni.h>
                                                                                      
          JNIEXPORT void JNICALL
      Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj)
      {
          jclass cls = (*env)->GetObjectClass(env, obj);
          jmethodID mid =
              (*env)->GetStaticMethodID(env, cls, "callback", "()V");
          if (mid == NULL) {
              return;  /* method not found */
          }
          printf("Output: In C code\n");
          (*env)->CallStaticVoidMethod(env, cls, mid);
      }
      
    7. Generate the shared library file - libStaticMethodCall.so
    8. gcc -shared -I/usr/local/jdk1.6.20_21/include -I/usr/local/jdk1.6.20_21/include/linux StaticMethodCall.c -o libStaticMethodCall.so
    9. RUN StaticmethodCall output:
    10. Output: In C code
      Output: In Java code

  • JNI Method Descriptors
    • similar to how descriptor denotes field types
    • Format:('[' + ARGUMENT_TYPE) + RETURN_TYPE
    • For example:
      native private String getLine(String);
      "(Ljava/lang/String;)Ljava/lang/String;"
      public static void main(String[] args);
      "([Ljava/lang/String;)V"
    • Use the javap tool to print out JNI method descriptors.
    • For example: javap -s -p InstanceMethodCall
    • -s: to output JNI descriptor strings rather than types as they appear in the Java programming language
    • -p: to include information about the private members of the class
    • The output:
      Compiled from "InstanceMethodCall.java"
      class InstanceMethodCall extends java.lang.Object{
      InstanceMethodCall();
        Signature: ()V
      private native void nativeMethod();
        Signature: ()V
      private void callback();
        Signature: ()V
      public static void main(java.lang.String[]);
        Signature: ([Ljava/lang/String;)V
      static {};
        Signature: ()V }

JNI - Access Instance (object) / Static (class) Field

Fields Accessing
  • 這個方法的觀念很簡單,就是:既然java native function是屬於某一個class,理論上它就可以存取這個class裡所有的member fields。因此,這個class中所有的函式,無論在java還是C++,都可以對同一組資料做存取而無需透過參數傳遞。
    The Java programming language supports two kinds of fields.
    1. (Object) Each instance of a class has its own copy of the instance fields of the class.
    2. (Class) All instances of a class share the static fields of the class.
    3. The JNI provides functions that native code can use to get and set instance fields in objects and static fields in classes.
  • Access Object Fileds
  • (Example) InstanceFieldAccess.java: The main method creates an object, sets the instance field, and then calls the native method (InstanceFieldAccess.accessField) to print out the existing value of the instance field and then set the field to a new value.
    1. InstanceFieldAccess.java
    2. class InstanceFieldAccess {
          private String s;
          private native void accessField();
      
          public static void main(String args[]) {
              InstanceFieldAccess c = new InstanceFieldAccess();
              c.s = "abc";
              c.accessField();
              System.out.println("In Java:");
              System.out.println("  c.s = \"" + c.s + "\"");
          }
          static {
              System.loadLibrary("InstanceFieldAccess");
          }
      }
      
    3. Generate C header file: InstanceFieldAccess.h
    4. /* DO NOT EDIT THIS FILE - it is machine generated */                                                        
      #include <jni.h>
      /* Header for class InstanceFieldAccess */
      
      #ifndef _Included_InstanceFieldAccess
      #define _Included_InstanceFieldAccess
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     InstanceFieldAccess
       * Method:    accessField
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_InstanceFieldAccess_accessField
        (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    5. C implementation of InstanceFieldAccess.c
    6. #include <stdio.h>
      #include <jni.h>
      
          JNIEXPORT void JNICALL 
      Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
      {
          jfieldID fid;   /* store the field ID */
          jstring jstr;
          const char *str;
      
          /* Get a reference to obj's class */
          jclass cls = (*env)->GetObjectClass(env, obj);
      
          printf("In C:\n");
      
          /* Look for the instance field s in cls */
          fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
          if (fid == NULL) {
              return; /* failed to find the field */
          }
      
          /* Read the instance field s */
          jstr = (*env)->GetObjectField(env, obj, fid);
          str = (*env)->GetStringUTFChars(env, jstr, NULL);
          if (str == NULL) {
              return; /* out of memory */
          }
          printf("  c.s = \"%s\"\n", str);
          (*env)->ReleaseStringUTFChars(env, jstr, str);
      
          /* Create a new string and overwrite the instance field */
          jstr = (*env)->NewStringUTF(env, "123");
          if (jstr == NULL) {
              return; /* out of memory */
          }
          (*env)->SetObjectField(env, obj, fid, jstr);
      }
      
        To access an instance field, the native method follows a two-step process.
      1. First, it calls GetFieldID to obtain the field ID from the class reference, field name, and field descriptor:
      2. fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
        
      3. Once you have obtained the field ID, you can pass the object reference and the field ID to the appropriate instance field access function (ex. GetObjectField):
      4. jstr = (*env)->GetObjectField(env, obj, fid);
        
        P.S. Strings & Arrays (special objects) : use GetObjectField to access the instance fields.
               Primitive types : use GetIntField and SetFloatField for accessing instance fields.
    7. Generate shared library file - libInstanceFieldAccess.so
    8. gcc -shared -I/usr/local/jdk1.6.20_21/include -I/usr/local/jdk1.6.20_21/include/linux InstanceFieldAccess.c -o libInstanceFieldAccess.so
    9. RUN InstanceFieldAccess output:
    10. In C: c.s = "abc"
      In Java: c.s = "123"
  • Access Static Fields
    1. Accessing static fields is similar to accessing instance fields.
      (Example) StaticFielcdAccess.java: The main method creates an object, initializes the static field, and then calls the native method (StaticFieldAccess.accessField) to print out the existing value of the static field and then set the field to a new value.
    2. StaticFielcdAccess.java
    3. class StaticFieldAccess {
          private static int si;
          private native void accessField();
      
          public static void main(String args[]) {
              StaticFieldAccess c = new StaticFieldAccess();
              StaticFieldAccess.si = 100;
              c.accessField();
              System.out.println("In Java:");
              System.out.println("  StaticFieldAccess.si = " + si);
          }
          static {
              System.loadLibrary("StaticFieldAccess");
          }
      }
      
    4. Generate c header file: StaticFielcdAccess.h
    5. /* DO NOT EDIT THIS FILE - it is machine generated */                           
      #include <jni.h>
      /* Header for class StaticFieldAccess */
      
      #ifndef _Included_StaticFieldAccess
      #define _Included_StaticFieldAccess
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     StaticFieldAccess
       * Method:    accessField
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_StaticFieldAccess_accessField
        (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    6. C implementation of StaticFieldAccess.c
    7. #include <stdio.h>
      #include <jni.h>
      
      JNIEXPORT void JNICALL 
      Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj)
      {
          jfieldID fid;   /* store the field ID */
          jint si;
      
          /* Get a reference to obj's class */
          jclass cls = (*env)->GetObjectClass(env, obj);
      
          printf("In C:\n");
      
          /* Look for the static field si in cls */
          fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
          if (fid == NULL) {
              return; /* field not found */
          }
          /* Access the static field si */
          si = (*env)->GetStaticIntField(env, cls, fid);
          printf("  StaticFieldAccess.si = %d\n", si);
          (*env)->SetStaticIntField(env, cls, fid, 200);
      }
      
    8. Generate shared library file - libStaticFieldAccess.so
    9. gcc -shared -I/usr/local/jdk1.6.20_21/include -I/usr/local/jdk1.6.20_21/include/linux StaticFieldAccess.c -o libStaticFieldAccess.so
    10. RUN StaticFieldAccess output:
    11. In C: StaticFieldAccess.si = 100
      In Java: StaticFieldAccess.si = 200
    There are two differences between how access a static field and how access an instance field:
    • Usage: call GetStaticFieldID for static fields, as opposed to GetFieldID for instance fields. GetStaticFieldID and GetFieldID have the same return type jfieldID.
    • Once you have obtained the static field ID, you pass the class reference, as opposed to an object reference, to the appropriate static field access function (such as GetStaticIntField for static int field).
  • JNI Field Descriptors
    • The specially encoded C string "Ljava/lang/String;" to represent a field type (such as java.lang.String) in the Java programming language. These C strings are called JNI field descriptors.
    • Descriptors Query Tool: "javap" tool (shipped with JDK or Java 2 SDK releases) to generate the field descriptors from class files.
    • javap -s -p CLASS_NAME
      • -s option: print internal type signatures
      • -p option: exposing private members