Channels ▼
RSS

JVM Languages

Calling Native Code from Java

Source Code Accompanies This Article. Download It Now.


Java Sourcebook 96: Calling Native Code From Java

Robi is a developer at Corel Corp., where he works on Java-related software projects. He can be reached at robik@corel.com.


One of the benefits of programming in Java is that the Java Virtual Machine (VM) hides platform differences. Applications can be developed once and run anywhere a Java VM is implemented. Unfortunately, that abstraction can get in the way of developing full-featured applications. Although the AWT user-interface package, for instance, provides a reasonable way to write cross-platform GUI code, it lacks some basic services that users take for granted. For example, AWT doesn't offer clipboard support. So even though all major GUIs (Windows, Mac, Motif) have a clipboard, there's no way to access it from Java. While it's relatively trivial to code a Java clipboard, it would be nicer if the real clipboard were available, so data could be exchanged with other applications.

In this article I'll use native code to implement a Java Clipboard class that does just that. Though the implementation of the Clipboard class is for Win32, the basic principles of calling native code are the same on all Java platforms.

The tools and documentation needed to write native methods are provided in the Java Development Kit (JDK) (http:// www.javasoft.com). I used JDK 1.0.2 and Visual C++ 2.2 (4.x should work just as well) in implementing Clipboard. The complete source code and related files are available electronically.

Purists may question using native code, since it is nonportable. Java applets (as opposed to applications) aren't even allowed to load native libraries for security reasons. Still, certain projects demand access to native code. Anyone porting Java to a new platform must use the native-code interface to call the underlying operating system APIs. People writing stand-alone Java applications often need to use existing code libraries until Java versions become available. In some cases, execution speed may be critical enough that only compiled C/C++ code will suffice.

Declaring Native Methods

Java doesn't support functions that exist outside a class--all functions are methods bound to some Java object. So how can Java programs call native C code, where objects and methods don't exist?

The answer is that native code must be wrapped up in a Java class definition. A Java class can be created where the methods are declared in the standard fashion, but implemented in a C (or C++) library. These methods are called "native" methods, and are identified by the native modifier.

A native method is just like a normal Java method except the implementation is absent. As Listing One illustrates, the Clipboard class has four native methods. Notice that the class has a static block of code; this is how the library containing the native code is loaded. This library is a DLL on Win32, a shared library on the Macintosh, and shared object (.so) under Solaris.

Defining and Implementing the Class

Once a class with native methods is defined, it's compiled like any other Java file (I used the JDK 1.0.2 javac compiler) to produce a .class file; see Example 1(a).

To allow access to the Clipboard object definition from native code, a header file and stubs file are needed. The JDK tool javah does this. It reads a .class file and outputs a header with the equivalent C definition; see Example 1(b). (Note that javah takes a class name--not a filename--as its argument. This means that any class javah can find, including the standard java classes or those specified in the CLASSPATH, can be used as an argument.) You can easily generate class definitions for any class in the CLASSPATH; see Example 1(c).

The generated header file, Clipboard.h, shows what the class looks like to C programs; see Listing Two.

The ClassClipboard struct mirrors the definition of the Clipboard class. The lone member variable in the class, the Boolean auto_empty, is defined in the C version as a long.

Javah generates a function prototype for each native method. The function names are derived from the class name and method name; the string Clipboard_ is prepended to the method names to form the function names. If Clipboard were defined as part of a package (say, my.ui.Clipboard) the function names would begin with my_ui_Clipboard_ instead.

Javah also does the work of generating the stubs file. The stubs file is a C file that implements some glue code for the Java VM. Invoking a native method at run time will cause the VM to call a stub function, which, in turn, will call the function implementation. The stubs are generated using the -stubs option, passing the same class name used when generating the header. In this case, the stubs file will be Clipboard.c (javah -stubs Clipboard).

The actual implementation of Clipboard, ClipboardImpl.cpp, is written in C++. Using C++ for the implementation is just as easy as using C. The only requirement is that the class header be surrounded in an extern "C" wrapper. The implementation file implements the functions prototyped in Clipboard.h, calling various Win32 APIs to manipulate the clipboard; see Listing Three.

Building the DLL

The final step in the native implementation is creating a DLL from the header, stub, and implementation files. The DLL I'll create here is Clipboard.dll.

No special work has to be done to export functions in the DLL--the stubs file already exports the required entry points. To build the library, the stubs and implementation files (Clipboard.c and Clipboard.cpp) should be compiled and linked into the same DLL. The Java VM import library (javai.lib, found in the JDK lib directory) should also be linked in: It provides entry points that allow the native code to instantiate and call Java objects.

The Clipboard class explicitly loads the Clipboard DLL in a piece of static code run at class-load time. Once the DLL is loaded, calls to any of the native methods are resolved dynamically by the VM using GetProcAddress(). You should be aware that if a native method is missing from the DLL, no error will occur until Java tries to call it (at which point it will throw a LinkageError exception). Similarly, if the entire DLL is missing or cannot be found, Java will throw an UnsatisfiedLinkError when System.load is called.

The sample code includes an external makefile for Visual C++. Just run nmake and all the necessary files will be built. The makefile requires that

  • The JDK 1.0.2 is properly installed.
  • The ALT_BOOTDIR environment variable is set to the JDK installation directory.
  • The JDK tools are in the PATH.
The virtual-machine headers that Clipboard uses are all part of the JDK's include directory.

Running and Debugging the Clipboard Demo

Once everything is built, you can run the demo Java application with the Java interpreter by typing java NativeDemo.

NativeDemo uses Clipboard to read the contents of the clipboard and prints the text to System.out. Then it calls methods to set and retrieve text from the clipboard.

Clipboard's native code can be debugged by running java.exe in the Visual C++ debugger. Just remember to specify NativeDemo as the program argument. Also, add Clipboard.dll to the list of additional DLLs in the debug-settings property page.

Java Object Handles

If you look at the function prototypes that javah generates for the Clipboard class, you'll notice that all the functions have the same first parameter; see Example 2(a). This parameter is a handle to the object on which the method operates. It is similar to the C++ this pointer, except that it is explicitly declared. Like the this pointer, the handle provides access to the class-member data.

Unlike C++ object pointers, Java object handles cannot be dereferenced with the -> or * operators. Instead, you must use the unhand() macro. This will return a pointer to the object's data. The Clipboard_getText function uses unhand() to check the auto_empty member variable; see Example 2(b).

It may seem tedious to use the unhand() macro to dereference object handles every time member data needs to be accessed, but it is necessary. If you dereference the handle and store the data pointer in a temporary variable, you run the risk of the VM moving the data somewhere else when garbage collection occurs. The VM will prevent garbage collection if your data pointer is declared on the stack, but this feature fails if you store the data pointer in a global. It is just safer to use unhand() all the time.

In the same header, you'll notice that javah created a type HClipboard as a handle to Clipboard objects--it just used the class name to make the type name. For classes that are in packages, javah generates a handle name using the full package and class name. For example, the class java.lang.String is referred to internally with a handle named Hjava_ lang_String. As another example, java.io.InputStream is referred to with Hjava_io_InputStream.

Calling Java Code from C

The Clipboard class not only calls native code, but uses Java objects like Strings and InputStreams. Calling Java objects from C is done with a set of functions exported from javai.dll, the virtual machine DLL; see Example 3(a).

The use of these functions is similar; they all take an execution environment as their first parameter. An execution environment stores the state of the Java stack--a zero is usually substituted for this argument to refer to the current environment.

The function execute_java_constructor instantiates a Java object, either by class name or by using previously loaded class information (pointed to by a ClassClass*). Using preloaded class information is faster than resolving the class by name. Class information can be loaded using FindClass; see Example 3(b). When referring to a class by name, use the fully qualified class name, and replace the periods with forward slashes; for example, the class "java.lang.String" is referenced as "java/ lang/String."

The execute_java_static_method function calls a static method on a class. It requires the class information be found via FindClass first. The method to be called is specified by the method name.

Similarly, execute_java_dynamic_ method calls a nonstatic method on an object. Instead of class info, you provide a handle to the object on which the method operates. Curiously, the VM does not provide a FindMethod call to bind to static or dynamic methods--you always have to specify the method name when calling it.

Method Signatures

When calling a constructor or method, you have to supply a method signature. This is a string that describes the parameters for the method. (The calls take a variable number of parameter arguments.) It resembles printf format strings.

The method signature is a string of the form (<param_type>...)<return_type>. Parameter types are simple, one-character values for intrinsic data types or a class name for objects. If you supply an invalid method signature, an IncompatibleClassChangeError exception will be thrown at run time. You should carefully examine method signatures and their parameters, since the parameters are untyped as with printf--an incorrect format string could cause a crash.

If you look at the stubs file generated in the example Clipboard.c, you'll see that javah inserts comments with the method signature for each of the native methods. If you are ever unsure about how to code signatures, you can always use javah -stubs to generate them directly from a method declaration. You can also use the stubs file to see how intrinsic Java data types correspond to C data types; see Table 1. Figure 1 provides some example method declarations and equivalent signatures.

Method Return Values

Both execute_java_static_method and execute_java_dynamic_method return a long. If the method being called returns an intrinsic type (whose size is less than or equal to a long), you can simply cast the return value to that type; see Example 4(a).

For methods that return objects, you can cast the return value to an object handle of the appropriate type, as in ClipboardImpl.cpp; see Example 4(b).

Working with Strings

The VM provides a number of useful routines to convert strings from Java to C and back. The routines are well documented in javastring.h in the JDK include directory.

Catching and Throwing Exceptions

You can throw Java exceptions from native code using the SignalError function; see Example 5(a). It will instantiate an Exception (or Exception-derived) object, and pass a message string to Exception's constructor. Note that SignalError will not cause a C longjmp or C++ exception--the code will still have to use return, goto, or C++ exception to exit out of the function. ClipboardImpl.cpp uses a utility function ThrowClipboardException that, in turn, calls SignalError.

Catching Java exceptions is a little trickier. After looking through the JDK header files, I turned up two macros in interpreter.h, exceptionOccurred and exceptionClear, that allow you to detect an exception and catch it. First use exceptionOccurred to test if any exception has occurred. Then use exceptionClear to clear the exception state so that it does not continue unwinding the stack. These macros require an execution environment, and do not accept zero as a default. A valid pointer to the current environment can be obtained via the EE() function; see Example 5(b).

Unfortunately, these macros are not mentioned in the JDK online documentation, so they may not be safe to call. Their use is also limited by the fact that you cannot detect the type of exception that occurred.

Synchronization

If native methods are declared as synchronized, no additional work is needed to make them thread safe. The VM will properly synchronize native method calls.

Functions corresponding to the wait, notify, and notifyAll methods are exported from the VM. The macro obj_monitor converts an object handle into a monitor usable by these functions; see Example 6.

Other Native Code Solutions

Both Microsoft and Netscape have alternative methods of accessing native code from Java. Microsoft (which owns the reference implementation of Java on Win32) has created a virtual machine where the run-time layout of Java objects matches that of COM (Component Object Model) objects. This makes Java objects callable as COM objects from C/C++ code, and allows COM objects to be called from Java code. This simplifies the calling protocol between Java and C greatly, and may provide some execution-speed advantages. (Details can be found on Microsoft's web site.) The big disadvantage is that it only works with Microsoft's Java VM, and only on Windows. The original native code interface at least provides source compatibility among platforms. (Microsoft supports the existing interface as well.)

Netscape has recently proposed an enhanced version of the native code interface called JRI, (Java Runtime Interface). It is the same core set of functions, plus additional functionality, including exception handling. Its primary benefit over the Microsoft approach is that it is a platform-independent API. It remains to be seen whether JavaSoft will adopt JRI as a standard Java API.

For most developers, the native method interface, with all its quirks, is the only solution for applications today.

Example 1: (a) Compiling a class with native methods; (b) javah reads a .class file and outputs a header; (c) generating class definitions

(a)   javac Clipboard

(b)   javah Clipboard

(c)   javah java.io.InputStream     
      javah java.lang.String

Example 2: (a) Functions all have the same first parameter; the Clipboard_getText function uses unhand to check the auto_empty member variable.

(a)   extern void Clipboard_clear(struct HClipboard *hclip);

(b)   extern void Clipboard_clear(struct HClipboard *this_ptr)
      {
          ...
          if( unhand(this_ptr)->auto_empty ) ...

Figure 1: Method declarations and equivalent signatures.


<b>Method:</b>   boolean startsWith(String prefix, int toffset);
<b>Signature:</b>    "(Ljava/lang/String;I)Z"

<b>Method:</b>   void run();
<b>Signature:</b>    "()V"

<b>Method:</b>   Object put(Object key, Object value)
<b>Signature:</b>    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"

<b>Method:</b>   void writeChars(char char_array[])
<b>Signature:</b>    "([C)V;"

Example 3: (a) Calling Java objects from C; (b) class info can be loaded using FindClass.

(a)  
HObject *execute_java_constructor(struct execenv *ee, 
  char *classname, ClassClass *c, char *signature, ... );
long execute_java_dynamic_method(struct execenv *ee, 
  HObject *obj, char *method_name, char *signature, ... );
long execute_java_static_method(struct execenv *ee, 
  ClassClass *c,   char *method_name, char *signature, ... );
ClassClass *FindClass(struct execenv *, char *classname, 
  bool_t resolve);

(b)   
ClassClass *      class_info;
Hjava_lang_string   *string_obj;

class_info= FindClass( 0, "java/lang/String", true );
string_obj = (Hjava_lang_string)execute_java_constructor( 0, 0, 
                                            class_info, "()" );

Example 4: (a) Casting the return value; casting the return value to an object handle of the appropriate type.


(a)   
char    a_byte;
a_byte = (char)execute_java_dynamic_method(0, obj_handle, 
            "someMethodReturningByte", "()B" )

(b)   
Hjava_lang_String *clip_string;
long              retval;
retval = execute_java_dynamic_method(0, (Hobject *)this_ptr,
                         "getText", "()Ljava/lang/String;");
clip_string = (Hjava_lang_String)retval;

Example 5: (a) Throwing Java exceptions from native code; (b) catching Java exceptions.


(a) 
SignalError( ExecEnv *, char * exception_classname, char * message);

(b)   
execute_java_dynamic_method(...)
If( exceptionOccured( EE() ) ) {
// some exception happened, so 'catch' it
    exceptionClear(EE());
   // will stop unwinding of stack
}

Example 6: Converting an object handle into a monitor.


SomeClass_someMethod( HSomeClass * this_ptr )
{
    monitorWait( obj_monitor(this_ptr), timeout );
    monitorNotify( obj_monitor(this_ptr) );
    monitorNotifyAll( obj_monitor(this_ptr) );
}

Table 1: How intrinsic Java data types correspond to C data types.

Java Type         C Equivalent                 Signature

long              int64_t (64-bit integer)     J
integer           long                         I
byte              char                         B
char              char                         C
enum              long                         E
float             float                        F
double            double                       D
boolean           long                         Z
void              void                         V
JavaObject        HObject * or derived         L<classname>;
Array of <type>   HarrayOf<type> * or          [<type>
                  HarrayOfObject *

Listing One


import java.io.*;

public class Clipboard
{
    static 
    {
    try {
        System.loadLibrary("Clipboard");
    } catch(UnsatisfiedLinkError e) {
        System.err.println("Could not load Clipboard dll");
    }
    }
    public Clipboard(boolean auto_empty) {
    this.auto_empty = auto_empty;
    }
    public native void clear();
    public native void putText( String clip_text );
    public native String getText();
    public native InputStream getStream();

    boolean auto_empty;
}


Listing Two


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class Clipboard */

#ifndef _Included_Clipboard
#define _Included_Clipboard

typedef struct ClassClipboard {
    /*boolean*/ long auto_empty;
} ClassClipboard;
HandleTo(Clipboard);

#ifdef __cplusplus
extern "C" {
#endif
extern void Clipboard_clear(struct HClipboard *);
struct Hjava_lang_String;
extern void Clipboard_putText(struct HClipboard *,struct Hjava_lang_String *);
extern struct Hjava_lang_String *Clipboard_getText(struct HClipboard *);
struct Hjava_io_InputStream;
extern struct Hjava_io_InputStream *Clipboard_getStream(struct HClipboard *);
#ifdef __cplusplus
}
#endif
#endif


Listing Three


// JDK headers do not include #ifdef __cpluscplus/extern "C" guards
// so we surround headers with extern "C"
extern "C" {
#include "Clipboard.h"
#include "java_io_InputStream.h"
#include "java_io_StringBufferInputStream.h"
}
#include "windows.h"

static void ThrowClipboardException(char * message)
{
    // throws a Java exception of type java.lang.Exception
    // and passes a given message string to the exception class constructor
    SignalError(0, "java/lang/Exception", message );
}

// empties the Clipboard
void Clipboard_clear(struct HClipboard *)
{
    BOOL    emptied;

    emptied = EmptyClipboard();
}
// puts the given text string on the clipboard
void Clipboard_putText(
    struct HClipboard *     this_ptr,
    struct Hjava_lang_String *  text_string )
{
    HANDLE  hglb_clip_text;
    char *  text;
    int     text_len;

    if( !OpenClipboard(NULL) || !EmptyClipboard() ) {
    ThrowClipboardException("opening" );
    return;
    }

    // make a C string out of the Java String
    text = makeCString( text_string );
    text_len = lstrlen(text);

    // allocate a global memory object for the text
    hglb_clip_text = GlobalAlloc( GMEM_DDESHARE, text_len + 1 );
    if( hglb_clip_text == NULL ) {
    // couldn't allocate block, so throw a Java exception
    ThrowClipboardException("allocating");
   return;
    }

    LPSTR   lpszClipBuffer;
    // lock the clip buffer, and copy the string into it
    lpszClipBuffer = (LPSTR)GlobalLock(hglb_clip_text);
    if( lpszClipBuffer == NULL ) {
    // couldn't lock memory so throw Java exception
    ThrowClipboardException("locking");
    }
    lstrcpy( lpszClipBuffer, text );
    GlobalUnlock( hglb_clip_text );

    SetClipboardData(CF_TEXT, hglb_clip_text );
    CloseClipboard();
}
// returns the clipboard text as a string
struct Hjava_lang_String *Clipboard_getText(
    struct HClipboard *     this_ptr)
{
    HANDLE  hglb_clip_text;
    char *  text;
    int     text_len;

    if( !OpenClipboard(NULL) ) {
    ThrowClipboardException("opening" );
    return NULL;
    }

    // get the clipboard data
    hglb_clip_text = GetClipboardData(CF_TEXT);
    if( hglb_clip_text == NULL ) {
    return NULL;
    }

    LPSTR       lpszClipBuffer;
    Hjava_lang_String * clip_string;

    lpszClipBuffer = (LPSTR)GlobalLock(hglb_clip_text);
    if( lpszClipBuffer == NULL ) {
    ThrowClipboardException("locking");
    return NULL;
    }
    clip_string = makeJavaString(lpszClipBuffer, lstrlen(lpszClipBuffer));

    GlobalUnlock( hglb_clip_text);

    // if the auto_empty flag is set clear the clipboard
    if( unhand(this_ptr)->auto_empty ) {
    EmptyClipboard();
    }
    CloseClipboard();

    return clip_string;
}
// returns the clipboard text data in a stream
struct Hjava_io_InputStream *Clipboard_getStream(
    struct HClipboard * this_ptr )
{
    Hjava_io_StringBufferInputStream *  input_stream;
    long                retval;
    Hjava_lang_String *         clip_string;

    // call Clipboard.getString to get clipboard text
    retval = execute_java_dynamic_method( 0, (HObject *)this_ptr, "getText",
    "()Ljava/lang/String;" );
    clip_string = (Hjava_lang_String *)retval;
    // construct a StringBufferInputStream from the string
    input_stream =(Hjava_io_StringBufferInputStream *)execute_java_constructor(
        0, "java/io/StringBufferInputStream", 0, "(Ljava/lang/String;)",
        clip_string );
    // StringBufferInputStream is derived from InputStream,
    // so this cast is perfectly valid
    return (Hjava_io_InputStream *)input_stream;
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video