dcsimg
 

J/Direct

Thursday Mar 1st 2001

J/Direct is the simplest way to call functions in a Win32 DLL. It was designed primarily to interface with the Win32 API, but you can use it to call any other APIs. The ease of use of this feature is counterbalanced by some limitations and reduced performance (compared to RNI). But J/Direct has distinct advantages. First, there is no need to write additional non-Java code, except the code in the DLL you want to call. In other words, you do not need a wrapper or proxy/stub DLL. Second, function arguments are automatically converted to and from standard data types. (If you must pass user-defined data types, J/Direct might not be the way to go.) Third, it’s simple and straightforward, as the example below shows. In just a few lines, this example calls the Win32 API function MessageBox( ) , which pops up a little modal window with a title, a message, an optional icon, and a few buttons.

Bruce Eckel's Thinking in Java Contents | Prev | Next

J/Direct is the simplest way to call functions in a Win32 DLL. It was designed primarily to interface with the Win32 API, but you can use it to call any other APIs. The ease of use of this feature is counterbalanced by some limitations and reduced performance (compared to RNI). But J/Direct has distinct advantages. First, there is no need to write additional non-Java code, except the code in the DLL you want to call. In other words, you do not need a wrapper or proxy/stub DLL. Second, function arguments are automatically converted to and from standard data types. (If you must pass user-defined data types, J/Direct might not be the way to go.) Third, it’s simple and straightforward, as the example below shows. In just a few lines, this example calls the Win32 API function MessageBox( ), which pops up a little modal window with a title, a message, an optional icon, and a few buttons.

public class ShowMsgBox {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError   {
    MessageBox(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
  /** @dll.import("USER32") */
  private static native int 
  MessageBox(int hwndOwner, String text,
    String title, int fuStyle);
}

Amazingly, this code is all you need to call a function in a Win32 DLL using J/Direct. The key is the @dll.import directive before the MessageBox( ) declaration, at the bottom of the example code. It looks like a comment, but it’s not: it tells the compiler that the function below the directive is implemented in the USER32 DLL, and should be called accordingly. All you must do is supply a prototype that matches the function implementation in the DLL and call the function. But instead of typing in the Java version of each Win32 API function that you need, a Microsoft Java package does this for you (I’ll describe this shortly). For this example to work, the function must be exported by name by the DLL, but the @dll.import directive can be used to link by ordinal as well, i.e., you can specify the entry position of the function in the DLL. I’ll cover the features of the @dll.import directive later.

An important issue in the process of linking with non-Java code is the automatic marshaling of the function parameters. As you can see, the Java declaration of MessageBox( ) takes two String arguments, but the original C implementation takes two char pointers. The compiler automatically converts the standard data types for you, following the rules described in a later section.

Finally, you might have noticed the UnsatisfiedLinkError exception in the declaration of main( ). This exception occurs when the linker is unable to resolve the symbol for the non-Java function at run-time. This happens for a number of reasons: the .dll file was not found, it is not a valid DLL, or J/Direct is not supported by your virtual machine. For the DLL to be found, it must be in the Windows or Windows\System directory, in one of the directories listed in your PATH environment variable, or in the directory where the .class file is located. J/Direct is supported in the Microsoft Java compiler version 1.02.4213 or above, and in the Microsoft JVM version 4.79.2164 or above. To get the compiler version number, run JVC from the command line with no parameters. To get the JVM version number, locate the icon for msjava.dll, and using the context menu look at its properties.

The @dll.import directive

The @dll.import directive, your one and only way to J/Direct, is quite flexible. It has a number of modifiers that you can use to customize the way you link to the non-Java code. It can also be applied to some methods within a class or to a whole class, meaning that all of the methods you declare in that class are implemented in the same DLL. Let’s look at these features.

Aliasing and linking by ordinal
For the @dll.import directive to work as shown above, the function in the DLL must be exported by name. However, you might want to use a different name than the original one in the DLL (aliasing), or the function might be exported by number (i.e. by ordinal) instead of by name. The example below declares FinestraDiMessaggio( ) (the Italian equivalent of “MessageBox”) as an alias to MessageBox( ). As you can see, the syntax is pretty simple.

public class Aliasing {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError   {
    FinestraDiMessaggio(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
  /** @dll.import("USER32", 
  entrypoint="MessageBox") */
  private static native int 
  FinestraDiMessaggio(int hwndOwner, String text,
    String title, int fuStyle);
}

The next example shows how to link to a function in a DLL that is not exported by name, but by its position inside of the DLL. The example assumes that there is a DLL named MYMATH somewhere along your path, and that this DLL contains at position 3 a function that takes two integers and gives you back the sum.

public class ByOrdinal {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError {
    int j=3, k=9;
    System.out.println("Result of DLL function:"
      + Add(j,k));
  }
  /** @dll.import("MYMATH", entrypoint = "#3") */
  private static native int Add(int op1,int op2);
}

You can see the only difference is the form of the entrypoint argument.

Applying @dll.import to the entire class
The @dll.import directive can be applied to an entire class, meaning that all of the methods in that class are implemented in the same DLL and with the same linkage attributes. The directive is not inherited by subclasses; for this reason, and since functions in a DLL are by nature static functions, a better design approach is to encapsulate the API functions in a separate class, as shown here:

/** @dll.import("USER32") */
class MyUser32Access {
  public static native int 
  MessageBox(int hwndOwner, String text,
    String title, int fuStyle);
  public native static boolean 
  MessageBeep(int uType);
}
 
public class WholeClass {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError {
    MyUser32Access.MessageBeep(4);
    MyUser32Access.MessageBox(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
}

Since the MessageBeep( ) and MessageBox( ) functions are now declared as static in a different class, you must call them specifying their scope. You might think that you must use the approach above to map all of the Win32 API (functions, constants, and data types) to Java classes. Fortunately, you don’t have to.

The com.ms.win32 package

The Win32 API is fairly big – on the order of a thousand functions, constants, and data types. Of course, you do not want to write the Java equivalent of every single Win32 API function. Microsoft took care of this, distributing a Java package that maps the Win32 API to Java classes using J/Direct. This package, named com.ms.win32, is installed in your classpath during the installation of the Java SDK 2.0 if you select it in the setup options. The package is made up of large number of Java classes that reproduce the constants, data structures, and functions of the Win32 API. The three richest classes are User32.class, Kernel32.class, and Gdi32.class. These contain the core of the Win32 API. To use them, just import them in your Java code. The ShowMsgBox example above can be rewritten using com.ms.win32 as follows (I also took care of the UnsatisfiedLinkError in a more civilized way):

import com.ms.win32.*;
 
public class UseWin32Package {
  public static void main(String args[]) {
    try {
      User32.MessageBeep(
        winm.MB_ICONEXCLAMATION);
      User32.MessageBox(0,
        "Created by the MessageBox() Win32 func",
        "Thinking in Java",
        winm.MB_OKCANCEL |
        winm.MB_ICONEXCLAMATION);
    } catch(UnsatisfiedLinkError e) {
      System.out.println("Can’t link Win32 API");
      System.out.println(e);
    }
  }
}

The package is imported in the first line. The MessageBeep( ) and MessageBox( ) functions can now be called with no other declarations. In MessageBeep( ) you can see that importing the package has also declared the Win32 constants. These constants are defined in a number of Java interfaces, all named winx (x is the first letter of the constant you want to use).

At the time of this writing, the classes in the com.ms.win32 package are still under development, but usable nonetheless.

Marshaling

Marshaling means converting a function argument from its native binary representation into some language-independent format, and then converting this generic representation into a binary format that is appropriate to the called function. In the example above, we called the MessageBox( ) function and passed it a couple of Strings. MessageBox( ) is a C function, and the binary layout of Java Strings is not the same as C strings, but the arguments are nonetheless correctly passed. That’s because J/Direct takes care of converting a Java String into a C string before calling the C code. This happens with all standard Java types. Below is a table of the implicit conversions for simple data types:

Java

C

byte

BYTE or CHAR

short

SHORT or WORD

int

INT, UINT, LONG, ULONG, or DWORD

char

TCHAR

long

__int64

float

Float

double

Double

boolean

BOOL

String

LPCTSTR (Allowed as return value only in ole mode)

byte[]

BYTE *

short[]

WORD *

char[]

TCHAR *

int[]

DWORD *

The list continues, but this gives you the idea. In most cases, you do not need to worry about converting to and from simple data types, but things are different when you must pass arguments of user-defined data types. For example, you might need to pass the address of a structured, user-defined data type, or you might need to pass a pointer to a raw memory area. For these situations, there are special compiler directives to mark a Java class so that it can be passed as a pointer to a structure (the @dll.struct directive). For details on the use of these keywords, please refer to the product documentation.

Writing callback functions

Some Win32 API functions require a function pointer as one of the parameters. The Windows API function may then call the argument function, possibly at a later time when some event occurs. This technique is called a callback function . Examples include window procedures and the callbacks you set up during a print operation (you give the print spooler the address of your callback function so it can update the status and possibly interrupt printing).

Another example is the EnumWindows( ) API function that enumerates all top-level windows currently present in the system. EnumWindows( ) takes a function pointer, then traverses a list maintained internally by Windows. For every window in the list, it calls the callback function, passing the window handle as an argument to the callback.

To do the same thing in Java, you must use the Callback class in the com.ms.dll package. You inherit from Callback and override callback( ). This method will accept only int parameters and will return int or void. The method signature and implementation depends on the Windows API function that’s using this callback.

Now all we need to do is create an instance of this Callback-derived class and pass it as the function pointer argument to the API function. J/Direct will take care of the rest.

The example below calls the EnumWindows( ) Win32 API; the callback( ) method in the EnumWindowsProc class gets the window handle for each top-level window, obtains the caption text, and prints it to the console window.

import com.ms.dll.*;
import com.ms.win32.*;
 
class EnumWindowsProc extends Callback {
  public boolean callback(int hwnd, int lparam) {
    StringBuffer text = new StringBuffer(50);
    User32.GetWindowText(
      hwnd, text, text.capacity()+1);
    if(text.length() != 0)
      System.out.println(text);
    return true;  // to continue enumeration.
  }
}
 
public class ShowCallback {
  public static void main(String args[])
  throws InterruptedException {
    boolean ok = User32.EnumWindows(
      new EnumWindowsProc(), 0);
    if(!ok)
      System.err.println("EnumWindows failed.");
    Thread.currentThread().sleep(3000);
  }
}

The call to sleep( ) allows the windows procedure to complete before main( ) exits.

Other J/Direct features

There are two more J/Direct features you can get using modifiers in the @dll.import directive. The first is simplified access to OLE functions, and the second is the selection of the ANSI versus Unicode version of API functions. Here is a short description of the two.

By convention, all OLE functions return a value of type HRESULT, which is a structured integer value defined by COM. If you program at the COM level and you want something different returned from an OLE function, you must pass it a pointer to a memory area that the function will fill with data. But in Java we don’t have pointers; also, this style is not exactly elegant. With J/Direct, you can easily call OLE functions using the ole modifier in the @dll.import directive. A native method marked as an ole function is automatically translated from a Java-style method signature, which is where you decide the return type, into a COM-style function.

The second feature selects between ANSI and Unicode string handling. Most Win32 API functions that handle strings come in two versions. For example, if you look at the symbols exported by the USER32 DLL, you will not find a MessageBox( ) function, but instead MessageBoxA( ) and MessageBoxW( ) functions, which are the ANSI and Unicode version, respectively. If you do not specify which version you want to call in the @dll.import directive, the JVM will try to figure it out. But this operation takes some time during program execution time that you can save with the ansi, unicode, or auto modifiers.

For a more detailed discussion of these features, consult the Microsoft documentation.

Contents | Prev | Next
Home
Mobile Site | Full Site