ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • The Java Native Interface
    Android 2010. 8. 3. 10:36

    Chapter 7

    The Invocation Interface


    This chapter illustrates how you can embed a Java virtual machine in your native application. A Java virtual machine implementation is typically shipped as a native library. Native applications can link against this library and use the invocation interface to load the Java virtual machine. Indeed, the standard launcher command (java) in JDK or Java 2 SDK releases is no more than a simple C program linked with the Java virtual machine. The launcher parses the command line arguments, loads the virtual machine, and runs Java applications through the invocation interface.

    7.1 Creating the Java Virtual Machine

    To illustrate the invocation interface, let's look at a C program that loads a Java virtual machine and calls the Prog.main method defined as follows:

     public class Prog {
    public static void main(String[] args) {
    System.out.println("Hello World " + args[0]);
    }
    }

    The following C program, invoke.c, loads a Java virtual machine and invokes Prog.main.

     #include <jni.h>

    #define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */
    #define USER_CLASSPATH "." /* where Prog.class is */

    main() {
    JNIEnv *env;
    JavaVM *jvm;
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jclass stringClass;
    jobjectArray args;

    #ifdef JNI_VERSION_1_2
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString =
    "-Djava.class.path=" USER_CLASSPATH;
    vm_args.version = 0x00010002;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    #else
    JDK1_1InitArgs vm_args;
    char classpath[1024];
    vm_args.version = 0x00010001;
    JNI_GetDefaultJavaVMInitArgs(&vm_args);
    /* Append USER_CLASSPATH to the default system class path */
    sprintf(classpath, "%s%c%s",
    vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
    #endif /* JNI_VERSION_1_2 */

    if (res < 0) {
    fprintf(stderr, "Can't create Java VM\n");
    exit(1);
    }
    cls = (*env)->FindClass(env, "Prog");
    if (cls == NULL) {
    goto destroy;
    }

    mid = (*env)->GetStaticMethodID(env, cls, "main",
    "([Ljava/lang/String;)V");
    if (mid == NULL) {
    goto destroy;
    }
    jstr = (*env)->NewStringUTF(env, " from C!");
    if (jstr == NULL) {
    goto destroy;
    }
    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
    if (args == NULL) {
    goto destroy;
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    destroy:
    if ((*env)->ExceptionOccurred(env)) {
    (*env)->ExceptionDescribe(env);
    }
    (*jvm)->DestroyJavaVM(jvm);
    }

    The code conditionally compiles an initialization structure JDK1_1InitArgs that is specific to the virtual machine implementation in JDK release 1.1. Java 2 SDK release 1.2 still supports JDK1_1InitArgs, although it introduces a general-purpose virtual machine initialization structure called JavaVMInitArgs. The constant JNI_VERSION_1_2 is defined in Java 2 SDK release 1.2, but not in JDK release 1.1.

    When it targets the 1.1 release, the C code begins with a call to JNI_GetDefaultJavaVMInitArgs to obtain the default virtual machine settings. JNI_GetDefaultJavaVMInitArgs returns such values as the heap size, stack size, default class path, and so on, in the vm_args parameter. We then append the directory in which Prog.class resides to vm_args.classpath.

    When it targets the 1.2 release, the C code creates a JavaVMInitArgs structure. The virtual machine initialization arguments are stored in a JavaVMOption array. You can set both common options (e.g., -Djava.class.path=.) and implementation-specific options (e.g., -Xmx64m) that directly correspond to java command line options. Setting ignoreUnrecognized field to JNI_TRUE instructs the virtual machine to ignore unrecognized implementation-specific options.

    After setting up the virtual machine initialization structure, the C program calls JNI_CreateJavaVM to load and initialize the Java virtual machine. The JNI_CreateJavaVM function fills in two return values:

    • An interface pointer, jvm, to the newly created Java virtual machine.
    • The JNIEnv interface pointer env for the current thread. Recall that native code accesses JNI functions through the env interface pointer.

    When the JNI_CreateJavaVM function returns successfully, the current native thread has bootstrapped itself into the Java virtual machine. At this point it is running just like a native method. Thus it can, among other things, issue JNI calls to invoke the Prog.main method.

    Eventually the program calls the DestroyJavaVM function to unload the Java virtual machine. (Unfortunately, you cannot unload the Java virtual machine implementation in JDK release 1.1 or Java 2 SDK release 1.2. DestroyJavaVM always returns an error code in these releases.)

    Running the above program produces:

     Hello World from C!

    7.2 Linking Native Applications with the Java Virtual Machine

    The invocation interface requires you to link programs such as invoke.c with a Java virtual machine implementation. How you link with the Java virtual machine depends on whether the native application is intended to be deployed with only a particular virtual machine implementation or it is designed to work with a variety of virtual machine implementations from different vendors.

    7.2.1 Linking with a Known Java Virtual Machine

    You may decide that your native application will be deployed only with a particular virtual machine implementation. In this case you can link the native application with the native library that implements the virtual machine. For example, with the JDK 1.1 release for Solaris, you can use the following command to compile and link invoke.c:

     cc -I<jni.h dir> -L<libjava.so dir> -lthread -ljava invoke.c

    The -lthread option indicates that we use the Java virtual machine implementation with native thread support (§8.1.5). The -ljava option specifies that libjava.so is the Solaris shared library that implements the Java virtual machine.

    On Win32 with the Microsoft Visual C++ compiler, the command line to compile and link the same program with JDK 1.1 release is:

     cl -I<jni.h dir> -MD invoke.c -link <javai.lib dir>\javai.lib

    Of course, you need to supply the correct include and library directories that correspond to the JDK installation on your machine. The -MD option ensures that your native application is linked with the Win32 multithreaded C library, the same C library used by the Java virtual machine implementation in JDK 1.1 and Java 2 SDK 1.2 releases. The cl command consults the javai.lib file, shipped with JDK release 1.1 on Win32, for linkage information about invocation interface functions such as JNI_CreateJavaVM implemented in the virtual machine. The actual JDK 1.1 virtual machine implementation used at run time is contained in a separate dynamic link library file called javai.dll. In contrast, the same Solaris shared library (.so file) is used both at link time and at run time.

    With Java 2 SDK release 1.2, virtual machine library names have changed to libjvm.so on Solaris and to jvm.lib and jvm.dll on Win32. In general, different vendors may name their virtual machine implementations differently.

    Once compilation and linking are complete you can run the resulting executable from the command line. You may get an error that the system cannot find either a shared library or a dynamic link library. On Solaris, if the error message indicates that the system cannot find the shared library libjava.so (or libjvm.so in Java 2 SDK release 1.2), then you need to add the directory containing the virtual machine library to your LD_LIBRARY_PATH variable. On a Win32 system, the error message may indicate that it cannot find the dynamic link library javai.dll (or jvm.dll in Java 2 SDK release 1.2). If this is the case, add the directory containing the DLL to your PATH environment variable.

    7.2.2 Linking with Unknown Java Virtual Machines

    You cannot link the native application with one specific library that implements a virtual machine if the application is intended to work with virtual machine implementations from different vendors. Because the JNI does not specify the name of the native library that implements a Java virtual machine, you should be prepared to work with Java virtual machine implementations that are shipped under different names. For example, on Win32 the virtual machine is shipped as javai.dll in JDK release 1.1 and as jvm.dll in Java 2 SDK release 1.2.

    The solution is to use run-time dynamic linking to load the particular virtual machine library needed by the application. The name of the virtual machine library can then be easily configured in an application-specific way. For example, the following Win32 code finds the function entry point for JNI_CreateJavaVM given the path of a virtual machine library:

     /* Win32 version */
    void *JNU_FindCreateJavaVM(char *vmlibpath)
    {
    HINSTANCE hVM = LoadLibrary(vmlibpath);
    if (hVM == NULL) {
    return NULL;
    }
    return GetProcAddress(hVM, "JNI_CreateJavaVM");
    }

    LoadLibrary and GetProcAddress are the API functions for dynamic linking on Win32. Although LoadLibrary can accept either the name (such as "jvm") or the path (such as "C:\\jdk1.2\\jre\\bin\\classic\\jvm.dll") of the native library that implements the Java virtual machine, it is preferable that you pass the absolute path of the native library to JNU_FindCreateJavaVM. Relying on LoadLibrary to search for jvm.dll makes your application susceptible to configuration changes, such as additions to the PATH environment variable.

    The Solaris version is similar:

     /* Solaris version */
    void *JNU_FindCreateJavaVM(char *vmlibpath)
    {
    void *libVM = dlopen(vmlibpath, RTLD_LAZY);
    if (libVM == NULL) {
    return NULL;
    }
    return dlsym(libVM, "JNI_CreateJavaVM");
    }

    The dlopen and dlsym functions support dynamically linking shared libraries on Solaris.

    7.3 Attaching Native Threads

    Suppose that you have a multithreaded application such as a web server written in C. As HTTP requests come in, the server creates a number of native threads to handle the HTTP requests concurrently. We would like to embed a Java virtual machine in this server so that multiple threads can perform operations in the Java virtual machine at the same time, as illustrated in Figure 7.1.

    Figure 7.1   Embedding the Java virtual machine in a web server

    Server-spawned native methods may have a shorter life span than the Java virtual machine. Therefore, we need a way to attach a native thread to a Java virtual machine that is already running, perform JNI calls in the attached native thread, and then detach the native thread from the virtual machine without disrupting other attached threads.

    The following example, attach.c, illustrates how to attach native threads to a virtual machine using the invocation interface. This program is written using the Win32 thread API. Similar versions can be written for Solaris and other operating systems.

     /* Note: This program only works on Win32 */
    #include <windows.h>
    #include <jni.h>
    JavaVM *jvm; /* The virtual machine instance */

    #define PATH_SEPARATOR ';'
    #define USER_CLASSPATH "." /* where Prog.class is */

    void thread_fun(void *arg)
    {
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jclass stringClass;
    jobjectArray args;
    JNIEnv *env;
    char buf[100];
    int threadNum = (int)arg;
    /* Pass NULL as the third argument */
    #ifdef JNI_VERSION_1_2
    res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
    #else
    res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
    #endif
    if (res < 0) {
    fprintf(stderr, "Attach failed\n");
    return;
    }
    cls = (*env)->FindClass(env, "Prog");
    if (cls == NULL) {
    goto detach;
    }
    mid = (*env)->GetStaticMethodID(env, cls, "main",
    "([Ljava/lang/String;)V");
    if (mid == NULL) {
    goto detach;
    }
    sprintf(buf, " from Thread %d", threadNum);
    jstr = (*env)->NewStringUTF(env, buf);
    if (jstr == NULL) {
    goto detach;
    }
    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
    if (args == NULL) {
    goto detach;
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    detach:
    if ((*env)->ExceptionOccurred(env)) {
    (*env)->ExceptionDescribe(env);
    }
    (*jvm)->DetachCurrentThread(jvm);
    }

    main() {
    JNIEnv *env;
    int i;
    jint res;

    #ifdef JNI_VERSION_1_2
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString =
    "-Djava.class.path=" USER_CLASSPATH;
    vm_args.version = 0x00010002;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = TRUE;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    #else
    JDK1_1InitArgs vm_args;
    char classpath[1024];
    vm_args.version = 0x00010001;
    JNI_GetDefaultJavaVMInitArgs(&vm_args);
    /* Append USER_CLASSPATH to the default system class path */
    sprintf(classpath, "%s%c%s",
    vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
    #endif /* JNI_VERSION_1_2 */

    if (res < 0) {
    fprintf(stderr, "Can't create Java VM\n");
    exit(1);
    }
    for (i = 0; i < 5; i++)
    /* We pass the thread number to every thread */
    _beginthread(thread_fun, 0, (void *)i);
    Sleep(1000); /* wait for threads to start */
    (*jvm)->DestroyJavaVM(jvm);
    }

    The attach.c program is a variation of invoke.c. Rather than calling Prog.main in the main thread, the native code starts five threads. Once it has spawned the threads it waits for them to start and then calls DestroyJavaVM. Each spawned thread attaches itself to the Java virtual machine, invokes the Prog.main method, and finally detaches itself from the virtual machine before it terminates. DestroyJavaVM returns after all five threads terminate. We ignore the return value of DestroyJavaVM for now because this function is not fully implemented in JDK release 1.1 and Java 2 SDK release 1.2.

    JNI_AttachCurrentThread takes NULL as its third argument. Java 2 SDK release 1.2 introduces the JNI_ThreadAttachArgs structure. It allows you to specify additional arguments, such as the thread group to which you would like to attach. The details of the JNI_ThreadAttachArgs structure is described as part of the specification for JNI_AttachCurrentThread in Section 13.2.

    When the program executes the function DetachCurrentThread it frees all local references belonging to the current thread.

    Running the program produces the following output:

     Hello World from thread 1
    Hello World from thread 0
    Hello World from thread 4
    Hello World from thread 2
    Hello World from thread 3

    The exact order of output will likely vary depending on random factors in thread scheduling.

    출처

    http://java.sun.com/docs/books/jni/html/invoke.html#11202

    'Android' 카테고리의 다른 글

    repo sync problems – Android Eclair  (0) 2010.11.26
    Activity Life Circle (생명주기)  (1) 2010.08.06
    _IO , _IOR , _IOW , _IORW  (0) 2010.07.21
    안드로이드 디렉토리 구조  (0) 2010.07.19
    Native Library 따라가보기  (0) 2010.07.15
Designed by Tistory.