Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

The Java Native Method Interface and Windows


Dr. Dobb's Journal August 1997: The Java Native Method Interface and Windows

Converting data types between Java and C++

Andy, a developer for NuMega Technologies, can be contacted at [email protected].


Java's Virtual Machine (VM) acts as a layer of abstraction between Java bytecode and machine-dependent instructions, enabling operating-system autonomy and platform-independent executables. There are circumstances, however, where the VM may not have support for special implementations, such as those in the java.net and java.sql packages. Luckily, you can use native system function libraries to implement system-dependent functionality outside of the VM.

Prior to the JDK 1.1, native functions were used only to implement classes that required system-dependent functions. One such class was SocketImpl, which had to be custom written for each operating system because of the differences between Windows and UNIX Socket implementations. However, native system functions were little more than a series of stub functions that called the actual system libraries. This was necessary because of calling convention differences between Java and Windows DLLs or UNIX libraries.

The JDK 1.1 implements a native calling convention -- the Java Native Method Interface (JNI) -- that makes it possible for the stub library to do more than just convert types between Java and C++. With JDK 1.1, you can create new instances of Java classes, call Java methods, and interact with the VM from within the native system library. This lets you create complex libraries for developing robust interfaces between Java and special system libraries.

Native functions interact directly with the operating system, bypassing the VM. For example, to create a Windows message box, you create a short Java class that loads a JNI library, which in turn calls MessageBox. Whenever you work with the JNI, you will have to use an intermediate library to make the actual system call.

Data Types

Table 1 lists Java, JNI, and C types. Each time you pass an integer in Java to a JNI function, it is converted into a jint, which is a C long (also known as a 32-bit int). Table 2 lists types that are used to expose Java classes and methods within your library. They will give you the ability to create new instances of classes and interact with both data members and methods.

Another important JNI item is the JNIEnv structure, a C++ structure used by the native library to interact with the Java environment. For example, say you need to create an instance of the Java class Vector. JNIEnv provides the functionality that allows you to create a new instance of the class and expose all of Vector's methods. You can then call Vector's methods from within your library.

Creating Java Objects in C?

Granted, it seems strange to have the functionality to create new Java objects from within C/C++ code. Although cumbersome, this functionality is extremely useful. The process of creating a new class instance begins by calling JNIEnv's FindClass member, which takes the name of a class as a parameter, searches the CLASSPATH, loads the class, and returns a pointer (jclass) to the instance. The class is not a Java object yet; it is simply loaded into memory.

Next, the desired constructor must be found. This is done by calling JNIEnv's GetMethodID with the pointer to the class, the constructor "token" ("<init>"), and the signature for that constructor. GetMethodID returns a pointer (jmethodID) to the instance of the function. To use any member method of a Java class, you must first have a pointer to the method. This sounds odd, but it really isn't. Windows developers should remember that they had to do the same thing with DLLs using GetProcAddress to expose DLL functions before import libraries became common.

Finally, you need to create an instance of the class. This is done by calling JNIEnv's NewObject, which takes the class pointer (jclass), the method pointer (jmethodID), and any additional arguments the constructor may need. If successful, you should get a new pointer to the object (jobject). You can now expose its member functions and begin to use the class. The object's memory is not allocated within your code. Instead, NewObject works its way back into the VM, where the actual memory allocation occurs.

Why would you want to do any of this? Say, for example, you wanted to use a system structure in Java. You would start by creating a Java class for it. Then, inside your library, you load the class, find its constructor, expose its data and method members, populate the object, and return it to the VM. The data from the object can now be used in the Java application.

Signatures

A function's signature is a representation of its return value and argument list. In Java code, you simply call a Java function that is overridden and let the compiler sort out what method you wanted to call. But when you are calling a Java method from within a system library, you are making the call dynamically from the VM. The VM uses the signature to determine the exact function you want to invoke.

A function signature is fairly straightforward. You start with the argument list, which is enclosed in parentheses. You then work backwards, starting with the last parameter, working your way to the return value. Table 3 shows the various signature types. For example, int bar(long a, boolean b, Vector c, byte d), would have a signature of "(BLjava.util.Vector;ZJ)I". As bizarre as this appears, it is one of the few ways to get around the problem of function overriding, and it lets you call any Java method.

Exposing and Using Java Data and Method Members

The GetFieldID and GetMethodID functions give access to Java class data and method members. They work similarly, and use the same argument list, beginning with an instance of the class (jclass), a string pointer of the data or method member's name, and the signature. GetFieldID returns a jfieldID (void *), while GetMethodID returns a jmethodID (void *).

Once you have the pointers to the various fields, you can use the GetField and SetField family of functions to manipulate the data. For example, if you called GetLongField() with the jfieldID for some long data member within a Java object, you would get back its current value. In contrast, if you call SetLongField, with the object instance, the correct jfieldID, and a long value, you will set the field for that Java object.

Calling a function is not much different, though you have a bit more versatility. There are two families of function-calling methods, CallMethod and CallNonvirtualMethod. CallMethod is broken down into three functions: CallMethod, CallMethodA, and CallMethodV. CallMethod takes an instance of the Java object, a jmethodID, and any parameters you wish to pass. CallMethodA uses an array of arguments to pass to the Java method, while CallMethodV takes a va_list of arguments. Each member of the CallMethod family is further broken down based on return type. For example CallObjectMethod(...) should call a Java method that returns a jobject. CallNonvirtualMethod differs from CallMethod in that you specify the jclass and jobject of the method you want to invoke.

This covers nonstatic data and method members, but what about static types and methods? The static GetFieldID is GetStaticFieldID. The remaining functions that allow the manipulation of methods and data members are the same except that you add the word "static" to the JNIEnv function.

Character Pointers

Character pointers are used in many Windows API functions and constitute a key piece of functionality. To make life easier, you can call directly a series of functions through the JNIEnv structure that allow you to create Java String class objects that have the key String class functionality already exposed. You can use these functions to manipulate both Unicode and byte-character strings, and pass them to both Windows API calls and Java methods.

Building a JNI Library

The first step in creating a JNI library is to create a Java class that declares a function native to the operating system. The implementation of this function is written in some other language (preferably C/C++) and is stored within a DLL that follows the JNI calling convention. The class will dynamically load the native function's library when an instance of the class is created.

Once the Java class has been written and compiled, you then run a tool called "JavaH" against the class to create a JNI header file. This header contains the C/C++ declaration for the Java native function. For example, javah -jni foo will yield a new JNI header file called foo.h -- a Windows C DLL header that declares the native function. You will need to use this header to determine what the native function library's functions are declared as. Oddly enough, they differ from the original function declaration in the Java class.

The last step is to take the JNI header and implement the various functions using a mix of JNI functions and Windows API calls. You then compile it and have a new native function library capable of providing any extra support you may require.

LittleWalk

The ToolHelp library provides functionality for viewing processes, modules, and heap information. LittleWalk, the program I present here, simply displays a list of all the running processes on a Windows 95 system -- functionality that Java does not provide from within the VM. (The complete LittleWalk system is available electronically; see "Availability," page 3.)

Listing One s the original Java class for my ToolHelp Java implementation. There are three important steps to note. The first, which loads my TLH.DLL (static { System.loadLibrary(<=lh<=); }), causes the DLL to get loaded into the VM's process space when a new instance of ToolHelp is created. At the bottom of the class you see a native declaration for CreateToolhelp32Snapshot; this is the function we will have to implement in TLH.DLL.

The second step is to create the header file using JavaH. Listing Two is the header and a few calling convention items. First you have JNIEXPORT, which translates to __declspec(dllexport), and JNICALL is __stdcall. Although this is nothing out of the ordinary for a DLL, there is a strange function called Java_ToolHelp_CreateToolhelp32Snapshot. The Java_ToolHelp_ portion is simply the fully qualified path to the native method.

The last step is the implementation of the native function (Listing Three), which is where you generate a list of the processes that will be displayed in the main Java application. There isn't very much to see in this function. However, there are two other functions, CreateProcessEntry32 and AddObject, that are far more interesting.

Listing Four shows CreateProcessEntry. Note the first three actual statements (ignoring the If clauses). Earlier I mentioned how to create a new instance of a class by loading the class, finding the constructor, and creating an instance of the class using the constructor. That is exactly what these statements do. The remainder of the statements populate the various member functions.

Creating a Java class to represent a system structure lets you package the data in the Java class in nearly the same way that you would expect to see the structure in our native operating system. Listing Five presents a Java ProcessEntry32 class that is nearly identical to the Windows ToolHelp API PROCESSENTRY32 type, missing only the dwSize member. This makes it easy to pass data from the native library to our Java class, since its data members have already been exposed.

Listing Six presents the AddObject function. This function takes a Java ProcessEntry32 object and adds it to an existing Java Vector class. Note the process of first getting a pointer to the Vector's AddElement member and calling it using CallVoidMethod.

Listing Four included several references to a SetDWORDField function in Listing Seven. SetDWORDField sets a Java class integer member. The process for setting a field is fairly simple: First you get a pointer to the field (GetFieldID), then assign it a value (SetIntField).

Conclusion

The JNI provides the connection between Java's ability to design and implement large scale applications and C/C++'s ability to create fast, system-dependent code. This makes it possible to write large scale applications in Java, while keeping the system-dependent function implementations native to the operating system.


Listing One

import ProcessEntry32;import java.util.*;


</p>
class ToolHelp extends Object
{
    static { System.loadLibrary("tlh"); } 
    // Load the native system library


</p>
    final private int TH32CS_SNAPPROCESS = 2;


</p>
    private Vector ProcEntries;
    public ToolHelp()
    {
        ProcEntries = new Vector();
        CreateToolhelp32Snapshot( 
        TH32CS_SNAPPROCESS, 0, ProcEntries);
    }
    public Vector getProcList()
    {
        return ProcEntries;
    }
    // native function declaration
    private native boolean CreateToolhelp32Snapshot( 
        int dwFlags, int th32ProcessID, Vector vector);
}

Back to Article

Listing Two

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


</p>
#ifndef _Included_ToolHelp
#define _Included_ToolHelp
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ToolHelp
 * Method:    CreateToolhelp32Snapshot
 * Signature: (IILjava/util/Vector;)Z
 */
JNIEXPORT jboolean JNICALL Java_ToolHelp_CreateToolhelp32Snapshot
  (JNIEnv *, jobject, jint, jint, jobject);


</p>
#ifdef __cplusplus
}
#endif
#endif

Back to Article

Listing Three

JNIEXPORT jboolean JNICALL Java_ToolHelp_CreateToolhelp32Snapshot  (JNIEnv *env, jobject, jint, jint process, jobject vector)
{
    HANDLE handle =  CreateToolhelp32Snapshot(
        TH32CS_SNAPPROCESS, process);
    BOOL first = TRUE;
    jobject ProcEntry32;
    PROCESSENTRY32 ppe;
    do {
        ppe.dwSize = sizeof( PROCESSENTRY32 );
        if(first)
        {
            first = FALSE;
            if ( !Process32First( handle, &ppe))
                return FALSE;
        }
        else
        {
            if ( !Process32Next( handle, &ppe))
                return FALSE;
        }
        ProcEntry32 = CreateProcessEntry32(env, &ppe);
    }
    while(AddObject(env, vector,ProcEntry32 ) );    
    return TRUE;
}

Back to Article

Listing Four

jobject CreateProcessEntry32( JNIEnv * env, LPPROCESSENTRY32 lppe){
    jclass jcProcEntry32 =  env->FindClass("ProcessEntry32");
    if(jcProcEntry32 == NULL)
        return NULL;
    jmethodID jmProcEntry32 = env->GetMethodID(jcProcEntry32,
        "<init>", "()V");
    if(jmProcEntry32 == NULL)
        return NULL;
      jobject joProcEntry32 = env->NewObject(jcProcEntry32,jmProcEntry32,"");
    if(joProcEntry32 == NULL)
        return NULL;
    if (!SetDWORDField(env, lppe->cntUsage, joProcEntry32, "cntUsage"))
        return NULL;
    if (!SetDWORDField(env, lppe->th32ProcessID, 
                              joProcEntry32, "th32ProcessID"))
        return NULL;
    if (!SetDWORDField(env, lppe->th32DefaultHeapID, joProcEntry32, 
                                                        "th32DefaultHeapID"))
        return NULL;
    if (!SetDWORDField(env, lppe->cntThreads, joProcEntry32, "cntThreads"))
        return NULL;
    if (!SetDWORDField(env, lppe->th32ModuleID, joProcEntry32, 
                                                      "th32ModuleID"))
        return NULL;
    if (!SetDWORDField(env, lppe->th32ParentProcessID, joProcEntry32, 
                                                      "th32ParentProcessID"))
        return NULL;
    if (!SetDWORDField(env, lppe->pcPriClassBase, joProcEntry32, 
                                                           "pcPriClassBase"))
        return NULL;
    if (!SetDWORDField(env, lppe->dwFlags, joProcEntry32, "dwFlags"))
        return NULL;
    if (!SetStringField(env, lppe->szExeFile, joProcEntry32, "szExeFile"))
        return NULL;
    return joProcEntry32;
}

Back to Article

Listing Five

class ProcessEntry32 extends Object{
    public int cntUsage;
    public int th32ProcessID;
    public int th32DefaultHeapID;
    public int th32ModuleID;
    public int cntThreads;
    public int th32ParentProcessID;
    public int pcPriClassBase;
    public int dwFlags;
    public String szExeFile;
}

Back to Article

Listing Six

BOOL AddObject (JNIEnv *env, jobject vector, jobject ProcEntry32){
    if(ProcEntry32 == NULL)
        return FALSE;
    jmethodID jmAddElement = env->GetMethodID(
        env->GetObjectClass(vector),
        "addElement", 
        "(Ljava/lang/Object;)V");
    if(jmAddElement == NULL)
        return FALSE;
    env->CallVoidMethod(vector, jmAddElement, ProcEntry32);
    return TRUE;
}

Back to Article

Listing Seven

BOOL SetDWORDField(JNIEnv * env,DWORD field,               jobject procEntry32,char *szField)
{
    jfieldID jfield =  env->GetFieldID( 
        env->GetObjectClass(procEntry32),
        szField, 
        "I");
    if ( !IsValidField(jfield, szField) )
        return FALSE;
    env->SetIntField(procEntry32, jfield, field);
    return TRUE;
}

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


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.