Using J/Direct to Call the Win32 API from Java
Mike Pietraszak
Many programmers have taken a shine to Java, but have been frustrated when
trying to take full advantage of the Win32 API. J/Direct solves this problem,
letting you use Java to full advantage with Windows.
While the pundits and purists debate the merits of lowest-common-denominator
programming, many developers back on planet Earth are busy writing Windows®
-based apps. And with the advent of J/Direct™, you can enjoy the liberating
productivity benefits of the Java language without being weighed down by
libraries that cater to even the least functional of the "pure" platforms.
Now developers who choose to throw off the shackles of purity have a choice—
and that choice includes the best of both the multiplatform and
Windows-specific worlds.
J/Direct is a new feature of the Microsoft® Virtual Machine (VM) that allows
developers to call the entire Win32® API directly. Before J/Direct,
developers who wanted to access the rich functionality of Win32 had two
options. They could either wrap the Win32 API calls in a custom DLL and call
the wrapper using the Raw Native Interface (RNI), or use the COM features of
the VM to access the subset of Win32 APIs exposed through COM interfaces. Now
J/Direct provides a third, more direct approach to accessing DLL-based Win32
APIs in a way that automatically converts native pointers, structures, and
types to their Java equivalents.
The simple program in Figure 1 demonstrates the J/Direct syntax. The program
plays a WAV-format audio file (a format unsupported by the AWT
applet.AudioClip class) using the sndPlaySound API found in the Windows
Multimedia Library (WINMM.DLL). When the code is compiled by the Microsoft
Compiler for Java (JVC.EXE) version 1.02.4213 or greater, the @dll.import
directive is translated into special bytecode attributes in the HelloWindows
class file. The VM for Java then decodes those attributes into an instruction
to load the WINMM.DLL library. The sndPlaySound function can then be called
like any other Java function in the HelloWindows class. So before you get
started, you'll need the VM and the Microsoft Compiler for Java, both of
which are included in the Microsoft SDK for Java 2.0. The SDK can be
downloaded from http://www.microsoft.com/java.
The Best of Both Worlds
The HelloWindows program in Figure 1 will run great, provided that the
program uses the Microsoft VM version 4.79.2252 or greater that comes with
Microsoft Internet Explorer 4.0 or the SDK 2.0. But in a Web-based world,
developers may not have control over the VM their clients will be using to
run Java programs. So it's possible that the program will be run on a VM that
doesn't handle J/Direct calls appropriately. Developers can still use
J/Direct in a way that permits Java-based programs to run successfully in
both Windows-specific and heterogeneous client environments.
Figure 2 is a modified listing for the HelloWindows program in Figure 1.
HelloWindowsEx performs several checks to determine the scenario under which
it is being run: the environment is Windows and J/Direct is available; the
environment is Windows, but the VM-specific class
com.ms.util.SystemVersionManager can't be loaded (so the VM probably isn't
the Microsoft VM); or the environment is Windows and the virtual machine is
the Microsoft VM, but it's not the correct version so there's J/Direct
support. By writing code that recognizes these gradations, you can take
advantage of Windows-specific features and still deploy to platforms that do
not provide J/Direct access to Win32 APIs.
So now you can use J/Direct to access APIs (and you can even rename them).
How do you map the signature of the API you want to call (usually documented
in C) to one that uses Java intrinsic types? Another good question. When
J/Direct accesses a Windows DLL from Java, the Java parameters must be
marshaled from Java types to native C types. For return values, the C types
must be marshaled back to Java types. This conversion is handled
automatically by the VM, according to the table shown in Figure 3. Note that
some conversions are only done for parameters, like Strings and SafeArrays,
and some are only done for return values, like void. But as for the actual
@dll.import declaration in your code, you'll have to do that C to Java (or
Visual Basic® to Java) conversion yourself.
The C signature for the sndPlaySound function is:
BOOL sndPlaySound(
LPCSTR lpszSound,
UINT fuSound
);
So the parameter and return value translation to Java types is as follows:
C Type
Java Type
____________________________________________
BOOL
becomes
boolean
LPCSTR
becomes
String
UNIT
becomes
int
The Java declaration for the API import statement then becomes:
/** @dll.import("winmm") */
static native boolean sndPlaySound(String lpszSound, int fuSound);
Converting Basic to Java
If you are using a reference that lists APIs with Visual Basic types, they
can also be converted easily to Java. Figure 4 shows a table with the
necessary type mappings. The sndPlaySound API would be declared in Visual
Basic as:
Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySound" (ByVal
lpszSoundName As String, ByVal uFlags As Long) As Long
The parameter and return value translation to Java types from Visual Basic is
as follows:
Visual Basic Type
Java Type
____________________________________________
Long (return value)
becomes
int
String
becomes
String
Long
becomes
int
The return value for the C API reference was of type BOOL, and was converted
to Java type boolean. For the Visual Basic API reference, the return value
was type Long, which can be converted to either the Java type boolean or the
Java type int. In this case, either type will work just fine. So the Java
declaration derived from an API reference using Visual Basic types could be
correctly translated as either
/** @dll.import("winmm") */
static native boolean sndPlaySound(String lpszSound, int fuSound);
or
/** @dll.import("winmm") */
static native int sndPlaySound(String lpszSound, int fuSound);
J/Direct also provides a flexible way for developers to map a somewhat
cryptic Win32 API to a more Java-friendly name. The sndPlaySound function,
for example, can be mapped (or "aliased") to playWAV by modifying the
following lines of code in the HelloWindows program:
/** @dll.import("winmm") */
static native boolean sndPlaySound (String lpszSound, int fuSound);
/** @dll.import("winmm", entrypoint="sndPlaySound") */
static native boolean playWAV (String lpszSound, int fuSound);
Because J/Direct calls native APIs, the code must be authenticated and
approved with maximum trust in order to be run from a browser. For J/Direct
code to be run from an applet, it must be digitally signed, indicating full
trust and granting all permissions. Untrusted applet code cannot access
trusted applet classes that use J/Direct. Even after the applet has been
signed, the init, start, stay, and destroy methods of the applet must include
a call to com.ms.security.PolicyEngine.assertPermission. Tools like
CABARC.EXE and SIGNCODE.EXE in the Microsoft SDK for Java 2.0 provide a means
for packaging and digitally signing applets with the high levels of trust
needed to be run from the browser.
Applets with J/Direct
For security reasons, applets that make J/Direct calls must be packaged into
a CAB and authenticated. Figure 5 shows an applet that makes J/Direct calls,
and the following HTML hosts the applet:
<html>
<applet code="HelloIE" width=300 height=50>
<param name="cabbase" value="hello.cab">
</applet>
</html>
Figure 6 is a .bat file that can be used to create the test certificate,
shown in Figure 7, that will be displayed the first time the applet is
loaded. For information on how to apply for an official certificate for
commercial applications, visit
http://www.microsoft.com/workshop/prog/security/authcode/codesign-f.htm.
Figure 7: Test Certificate
You may find the edit-compile-debug cycle for J/Direct applets tricky because
applet classes remain cached while Internet Explorer is running. This means
that even if you recompile a class and redeploy it in a CAB, the old class
will still be in the cache, so it will be run again and your changes will not
be displayed. You can use Ctrl-F5 to force a reload of your classes, or you
can restart Internet Explorer. Next, you must be sure to sign your code with
low trust (signcode.exe -jp low) and request security permissions (with
PolicyEngine.assertPermission). In this example, maximum permissions (SYSTEM)
were requested. Finally, you'll have to turn on test certificates by running
the SETREG.EXE tool.
What about COM and RNI?
J/Direct is a new way for users of the Microsoft VM for Java to take
advantage of Win32 APIs in Windows 95, Windows NT®, and beyond. But what's
so new about Windows access? After all, VM users can already access COM
through a host of tools like the Visual J++™ Type Library Wizard for Java
(JAVATLB.EXE), the Microsoft ActiveX™ Control Importer for Java
(JACTIVEX.EXE), the Visual J++ ActiveX Wizard for Java, and the Java/COM
Registration Utility (JAVAREG.EXE).
But with J/Direct, the Windows APIs are accessed with DLL calls, not via COM.
And although RNI provides a great way to access DLLs, the RNI technology
assumes that you started with a Java class, generated a C header file using
msjavah.exe, and then added functionality to the C DLL. But Win32 DLLs aren't
callable by RNI in this way. Because Win32 API names don't conform to RNI
naming conventions, and because RNI expects data types (like strings) to be
Java-format types (the Win32 API uses C-format types), RNI cannot be used to
access Win32 or third-party DLLs. In other words, a single API cannot follow
both the RNI conventions and the Windows conventions required by existing
Windows-based apps.
J/Direct makes it easy to call DLL functions by automatically marshaling
parameters from Java to native C types. Crossing the Java to C boundary also
means dealing with garbage collection issues. In C, memory must be explicitly
allocated and deallocated in code. But in Java, memory allocation and
deallocation is done automatically by the VM. This difference can be
problematic if a Java-allocated variable gets deallocated by the VM while
still in use on the C side. J/Direct helps when crossing this boundary by not
garbage collecting (deallocating) during an API call, but there are
limitations. If you access a DLL via J/Direct, that DLL cannot access another
DLL that is accessed via RNI. And DLL functions cannot modify standard Java
objects directly—structures built using the @dll.struct directive must be
used.
In my next article, I'll cover the @dll.struct directive, OLE calling
conventions, ANSI versus Unicode issues, callbacks, and the com.ms.dll
package.
From the January 1998 issue of Microsoft Interactive Developer. Get it at
your local newsstand, or better yet, subscribe.
--
* Origin: ★ 交通大學資訊科學系 BBS ★ <bbs.cis.nctu.edu.tw: 140.113.23.3>