J/Direct
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:
|
|
|
|
|
|
|
INT,
UINT, LONG, ULONG, or DWORD
|
|
|
|
|
|
|
|
|
|
|
|
LPCTSTR
(Allowed as return value only in ole mode)
|
|
|
|
|
|
|
|
|
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.