[ This exercise | Solution | Course Notes | Exercises page ]


Help: Exercise 2

Task 1

PrintFuncs.java Create the java class that declares the native method printStringNative. Also, implement the Java method printStringJava that will display the string. Be sure to include the native keyword and that the name of the shared library is correct.
class PrintFuncs
{
  public native void printStringNative(String s);

  public void printStringJava(String s)
  {
     System.out.println(s);
  }

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

Task 2

Main.java Create the java class that will test the native method.
class Main 
{
  public static void main(String[] args) 
  {
    PrintFuncs pf = new PrintFuncs();
    pf.printStringJava("This is printed without using native code.");
    pf.printStringNative("This is printed by a callback into Java from C++.");
  }
}

Task 3

Compile PrintFuncs.java and Main.java using javac:

  javac PrintFuncs.java
  javac Main.java
This, of course, produces PrintFuncs.class and Main.class.

Task 4

Using javah, create the C/C++ header file that represents the java PrintFuncs class. Be sure to include the -jni switch so that the resulting header file is a JNI-compatible file. Also, don't include the .class extension:
  javah -jni PrintFuncs
This creates a file called PrintFuncs.h in the same directory as PrintFuncs.class:
 
/* DO NOT EDIT THIS FILE - it is machine generated */
#include < jni.h >
/* Header for class PrintFuncs */

#ifndef _Included_PrintFuncs
#define _Included_PrintFuncs
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     PrintFuncs
 * Method:    printStringNative
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_PrintFuncs_printStringNative
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
It is important that you compile PrintFuncs.java in the previous step so that the .class file is available to javah in this step.

Task 5

The preceding steps are very similar to the steps taken in the first exercise. Now, things get slightly more complicated because we want to call back into Java from C++. First, be sure that you have done the following correctly:

  • #include the header file that was created in the previous step.
  • Make sure that you prepend Java_PrintFuncs_ to the name of the method.
  • Check that the return type and argument types match the declaration in PrintFuncs.h. If they don't match, you won't get any warnings from the compiler, but at run-time, when java calls the method, it will fail.
  • Don't forget the first two parameters to the function. They are required and we will use them in this exercise. If you have problems at run-time, they are likely to be caused by this function signature. The safest thing to do to ensure that you won't have any problems is to copy the declaration of the function from the header file generated by javah.

    PrintFuncsImp.cpp This is the C++ file that implements the native method.

    #include "PrintFuncs.h"    
    
    JNIEXPORT void JNICALL Java_PrintFuncs_printStringNative(JNIEnv *env, jobject obj, jstring s) 
    {
        // Get the class associated with this object (class PrintFuncs)
      jclass cls = env->GetObjectClass(obj);
    
        // The the ID for its 'printStringJ(string)' method
      jmethodID mid = env->GetMethodID(cls, "printStringJava", "(Ljava/lang/String;)V");
    
        // Invoke the method (printStringJava is a void method)
      env->CallVoidMethod(obj, mid, s);
    }
    
    Once the function name and
    signature are correct you will implement the body by making three calls to the API:
    1. Get the object's class by calling GetObjectClass. This will return a reference to the class PrintFuncs that you implemented earlier.
      jclass cls = env->GetObjectClass(obj);
      
    2. Use this class to get the Java method's ID. The name of the method is printStringJava and it takes a single String as a paramter. Note the signature. At runtime, the GetMethodID function will look for a method on the class PrintFuncs named printStringJava that takes a String and returns nothing (a void method).
      jmethodID mid = env->GetMethodID(cls, "printStringJava", "(Ljava/lang/String;)V");
      
    3. Use this ID to invoke the Java method. printStringJava is declared void so you need to call the function named CallVoidMethod. Since the Java method takes a String, you must pass this parameter to the function.
      env->CallVoidMethod(obj, mid, s);
      
  • Task 6

    This is the step where the actual creation of the shared library happens. It is also the point where we must name the file. It is important that the name matches what the PrintFuncs class is expecting when it loads the shared library in its static initialization:

    This code is taken from PrintFuncs.java:

      static 
      {
          // The name of our shared library must match what
          // the class is loading at initialization time:
    
        System.loadLibrary("PrintFuncsImp");
      }
    

    Here is an example of how to make a shared library under Unix:

    # This works on sirius.cs.pdx.edu
    # The -o option allows you to name the shared library. Note that
    # you must provide the 'lib' prefix and '.so' suffix here.
    
    gcc -G -I/pkgs/jdk1.1.1/include -I/pkgs/jdk1.1.1/include/solaris PrintFuncsImp.cpp -o libPrintFuncsImp.so
    
    

    Here is an example of how to make a shared library under Windows NT:

    : This works under NT. Replace the include paths to match your environment
    : and be sure to include Sun's JDK not Microsoft's!
    : Note that you must provide the .dll extension (case insensitive)
    
    cl -Im:\jdk1.1.5\include -Im:\jdk1.1.5\include\win32 -LD PrintFuncsImp.cpp -FePrintFuncsImp.dll
    

    Task 7

    To execute the program, run Main with the java interpreter:
      java Main
    
    The output should look something like this:
      This is printed without using native code.
      This is printed by a callback into Java from C++.
    
    If you receive an error message such as either of these:
      java.lang.UnsatisfiedLinkError: no PrintFuncsImp in shared library path
      java.lang.UnsatisfiedLinkError: printStringNative
    
    refer to the
    common mistakes in the detailed example.


    If you get a SIGSEGV error under Solaris, you'll probably get a message like: (buried in about 30 lines of text)

      lang.NoSuchMethodError *current thread*   
    
    It is probably because of one of these two lines of code:
      jmethodID mid = env->GetMethodID(cls, "printStringJava", "(Ljava/lang/String;)V");
      env->CallVoidMethod(obj, mid, s);
    
    There are three likely reasons for this obscene termination.
    1. You have misspelled the method's name (printStringJava in this exercise.) This method is looked-up at runtime so the compiler has no way of verifying that the method you specified really exists.
    2. The signature is incorrect. This mechanism works much the way that printf does in the C world. The number and types of the arguments are encoded in this string and the compiler has no way of verifying that you really are passing the right parameters.
    3. You are passing the wrong parameters to the method via the CallVoidMethod function. Make sure that the number and types of parameters you are passing exactly match the number and types you specified in GetMethodID.
    Under Windows NT, you will get a "java.exe - Application Error" dialog box saying something useful like:
      The instruction at "0x10011d7e" referenced memory at "0x00000000". The memory 
      could not be "read".
    
      Blah, blah, blah
    
    This is a SIGSEGV under Windows NT.

    Copyright © 1998 Sergio Antoy. All Rights Reserved
    February 99, version 020199