Introducing J/Direct

A Look at Microsoft's J/Direct Jumpstart Package

By Aris Buinevicius

One of the big promises of Java is based on providing a cross-platform set of class libraries that will run seamlessly across any platform that provides a JVM (Java Virtual Machine). While Java has now been widely available and widely discussed over the past two years, many developers have run into some of the weaknesses of Java and need to "cut to the chase" to get the advantages of native operating system functionality.

As far back as JDK 1.0.2, Java enabled developers to link to platform-specific libraries through native calls. While native methods are useful for simpler libraries, native calls force a developer to have specific knowledge about how the native library works, and they don't easily hook into DLL functionality. Microsoft's J/Direct technology allows Java developers to directly call the core Windows DLLs without the complexity required with native calls. And J/Direct is provided as part of the Microsoft JVM and the Java SDK 2.0 release.

Who Needs J/Direct?

Java has received a lot of press in the last two years, including legitimate predictions as well as unsubstantiated hype. The current state of Java shows that developers appreciate many of the benefits received by the Java language, but many of the implementation details are still immature — such as performance, library depth, the AWT-based user interface, byte code reverse engineering, and more.

JVMs operate by interpreting Java byte code at run time, as opposed to running a standard executable. While this approach has some advantages, the major disadvantage is slow performance. Some technologies, such as the Just In Time compiler (JIT), help speed up the interpreted code being executed, but they still can't approach the speed of well-written code running as a binary executable. For some tasks, JIT performance comes close to native executable speed; however, for other processor-intensive tasks (such as those required by scientific number-crunching libraries, intense graphical applications, etc.), a developer can see greater performance by accessing native libraries through their Java code.

Because C/C++ was widely adopted beginning in the mid 1980s, quite a bit of code has been written by enterprises and has been continually updated and tweaked to evolve from the same base of source code. For many corporations, it's impractical to assume this code base can be completely rewritten in Java. Who wants to rewrite line after line of code that has been operating reliably for years? For such "legacy" code bases, J/Direct can provide an easy solution that avoids rewriting large bodies of code.

One strength and weakness of the byte code that composes a Java .class file (the files that are interpreted when a Java application runs) is that the file format is well documented and hence extremely easy to decompile. Many third-party utilities exist to reassemble Java byte code into easy-to-read source code. Some techniques exist to help protect the trade secrets developers place into their Java code (such as code obfuscators); but none thus far has proven to be 100-percent reliable. Using J/Direct with a native DLL provides a more secure approach to deploying applications. While even binary libraries can be deconstructed into assembly code, determining trade secrets from assembly is a more daunting task than reverse engineering Java source code.

The Sun AWT (Abstract Windowing Toolkit) is the library from which graphical user interface (GUI) applications are constructed. The AWT has been plagued with inconsistencies, cross-platform incompatibilities, and other problems, since its inception. The AWT can be the Achilles heel of commercial-quality Java applications, especially when compared to the applications that Windows users have become familiar. Using J/Direct (and many of the samples provided in the J/Direct Jumpstart package show this), Java developers can bypass the AWT to use a traditional Windows SDK-like interface to develop their GUIs.

If you want to develop applications that take advantage of the Windows operating system and the flexibility of Java as a language, J/Direct offers many advantages.

What's Required?

J/Direct is part of the Microsoft JVM that is shipped with Internet Explorer, and developers have used the Microsoft SDK 2.0 release to access the functionality of J/Direct. With Visual J++ 6.0, J/Direct is integrated more tightly into the IDE, and Visual J++ 6.0 provides some additional tools (e.g. the J/Direct CallBuilder) to make developing with J/Direct easier.

There are several ways to deploy J/Direct applications. The first is through standard Java deployment (i.e. through a Java applet or application) to a user group you have control over, i.e. if you call non-Windows DLLs, they must be available to your users. You can also create a standard executable. For example, you can use a tool such as the jexegen.exe, which is currently provided as part of the SDK 2.0.1 release.

How Does It Work?

To invoke a DLL method, or use a structure that was originally defined as part of a DLL, you must first use the J/Direct directives to specify the names of the DLL pieces you will be using.

Declaring methods. J/Direct directives for DLL methods are set up through Java comment blocks that specify the name of the DLL you are calling, followed by a definition of the method, and the types of that method. For example, you can call the USER32 DLL to declare the Win32 MessageBox method as shown here:

/** @dll.import("USER32") */
private static native int MessageBox(
  int hwndOwner, String text, String title, int fuStyle);

After this declaration is made inside a class, the MessageBox method can be called anywhere within the class to display a standard Windows MessageBox, just as a regular Windows application would.

Declaring structures. The J/Direct structure directive is used to specify the mapping between a DLL structure and a Java class. It can also specify attributes (such as whether ANSI or UNICODE strings will be used), and the packing size of the structure. The syntax for declaring the mapping is:

/** @dll.struct(<Linktype=ansi, unicode, or auto>,
                <pack=1, 2, 4, or 8>) */

followed by class declaration. The Linktype allows you to specify whether the Java string and char types in your class declaration will be treated as ANSI or UNICODE. A value of auto for the argument will automatically determine which is appropriate; if not specified, the default value is ansi. The pack argument is a directive to indicate how much byte padding is provided to structures; the potential values are 1, 2, 4, or 8. The default value is 8 if the pack directive isn't used. Both the Linktype and the pack arguments are optional for the structure directive. An example of a structure directive for the CHOOSEFONT structure is shown in Figure 1.

/**
 * @dll.struct(auto, pack=2)
 */
public class CHOOSEFONT {
  public int lStructSize = com.ms.dll.DllLib.sizeOf(this);
  public int hwndOwner;
  public int hDC;
  public int lpLogFont;
  public int iPointSize;
  public int Flags;
  public int rgbColors;
  public int lCustData;
  public int lpfnHook;
  public String lpTemplateName;
  public int hInstance;
  public int lpszStyle;
  public short nFontType;
  private short _align_;
  public int nSizeMin;
  public int nSizeMax;
}

Figure 1:. An example of a structure directive for the CHOOSEFONT structure (part of the Jumpstart package)

Fixed size array mapping. In cases where you need to specify a fixed array size that is part of a structure, you can use the structure mapping directive just before the array declaration. An example of this is shown for the LOGFONT structure in Figure 2. To specify a fixed-sized string, you can use the syntax:

type=TCHAR[n]

If you need to use a fixed-size array, you can use the syntax:

type=FIXEDARRAY,size=n

where n represents the number of characters, and the number of bytes respectively.

/** @dll.struct(auto) */
public class LOGFONT {
  public int lfHeight;
  public int lfWidth;
  public int lfEscapement;
  public int lfOrientation;
  public int lfWeight;
  public byte lfItalic;
  public byte lfUnderline;
  public byte lfStrikeOut;
  public byte lfCharSet;
  public byte lfOutPrecision;
  public byte lfClipPrecision;
  public byte lfQuality;
  public byte lfPitchAndFamily;

  /** @dll.structmap([type=TCHAR[32]]) */
  public String lfFaceName;
}

Figure 2: .Using the structure mapping directive just before the array declaration.

Marshalling. Data marshalling refers to the process that maps native data types to Java data types. For example, a signed 16-bit integer maps to a Java short type, and a DWORD is represented by a Java integer type. For the most part, many basic data types map over in a straightforward manner, but some explanations are needed about more complex types.

When passing a string to a DLL method, you must be aware of whether the DLL method will be treating the string as a read-only string, or whether the method has access to modify the contents of the string passed in. In situations where the method won't be changing the string, you should pass a normal string as an argument. When the method can modify the contents of the string, you should use a StringBuffer as an argument, and make sure the StringBuffer has enough capacity declared so the DLL method won't exceed its storage limit.

Because pointers aren't part of the Java language, and pointers can be used as DLL method arguments, you should pass a one-element array as an argument that can hold a pointer value.

Many standard Windows DLL methods provide polymorphic parameters that are expected to have different meanings based on the values of the other parameters in the method. J/Direct offers two ways around this dilemma; you can either declare the final parameter as a generic Java Object type, or you can provide a separate declaration (or overloading) of each potential method combination.

A Basic Example

For the most basic instances of calling a DLL method, a J/Direct application requires little overhead and can be simple to invoke. The application in Figure 3 is a simple Java program provided in the SDK 2.0.1 release (in \samples\jdirect\simple) which will start, invoke a MessageBox window, and exit. This application shows you how easy it can be to make calls directly into the Windows USER32 DLL.

/* Copyright (C) 1997 Microsoft Corporation.
 * All rights reserved.
 * This example calls the MessageBox api using
 * the new @dll.import directive.
 */
public class Simple {
  public static void main(String args[]) {
    try {
      MessageBox(
        0, "MessageBox successfully called.", "Java", 0);
    } 
    catch (UnsatisfiedLinkError ule) {
      System.err.println("Caught exception: " + ule);
      System.err.println(
        "Probably wrong version of Java compiler.");
    }
  }


  /** @dll.import("USER32") */
  static native int MessageBox(
    int hwndOwner, String text, String title, int style);
}

Figure 3: A simple Java program provided in the SDK2.0.1 release.

You can see that the @dll.import statement (in the comment block towards the end of the class) provides the "glue" between Java and the DLL by specifying in which DLL the proceeding method will be found, and the Java data types that map to the DLL method. Furthermore, you can see in the try block how easy it is to call the method; it's similar to a forward declaration in C.

You can also shield your Java developers from the gory implementations of the J/Direct calls by packaging the DLL wrappers in a separate Java class that can be used transparently just as any other Java class.

ANSI and UNICODE

For many DLL methods — you might be wondering — how does J/Direct choose between calling an ANSI-based method, or a UNICODE-based method? By default, J/Direct will choose the ANSI version. If you want to invoke the UNICODE version, you should specify the UNICODE tag when you declare the method with the @dll.import directive.

A basic example of a method provided in both ANSI and UNICODE formats in the J/Direct documentation is the MessageBox method. The sequence of determining which method will be called for the UNICODE version is roughly:

First, your String types are converted to null-terminated UNICODE strings.

Second, a literal method is searched for in the DLL for MessageBox.

Third, since the method isn't found, a "W" is appended to the method, and the MessageBoxW method is searched for, found, and invoked.

Security Issues

With direct access to standard DLL methods, you can bet that J/Direct poses some serious security issues in terms of its deployment. J/Direct applications shouldn't be used for Internet applications (as standard Java applets might be). Instead, they should be developed for a target audience on an intranet, where you know the environment in which they will be operating.

J/Direct Jumpstart and GUI Development

In December of 1997, Microsoft released the J/Direct Jumpstart package, which provides several examples of how to create GUI applications using J/Direct. One of the serious flaws in JDK 1.0.2 and JDK 1.1 was the AWT implementation; it was full of miscellaneous bugs (several Scrollbar problems were still in the JDK 1.1.4).

By examining some of the samples provided in the Jumpstart package, you can see how to code Java GUI applications for a Windows audience and completely bypass the AWT. In fact, you can use the standard Windows messaging system — with which many Windows SDK developers are already familiar — in a Java context.

Jumpstart provides a full set of wrapper classes around some standard Windows SDK classes. This gives you the ability to instantiate Windows GUI objects and controls similar to how you would with the SDK itself. Several examples are provided that should be familiar to Windows developers, including Scribble and MultiPad. The wrapper classes are part of a package in Jumpstart called misc, and are not officially supported by Microsoft. However, just as its name implies, Jumpstart gives you a head start to defining many of the common classes because you don't have to provide the wrappers yourself.

Once Again, Hello World!

As is traditional when developing your first application in any computer language, you try a "Hello World" type of application. Jumpstart provides just such an example (see Figure 4). The Window class is a wrapper around a standard window object. The basic flow of this application is to simply extend the Window class and override the onPaint routine to display the "Hello World" message.

// Sample source code from the J/Direct Jumpstart Toolkit.
// Copyright (C) Microsoft Corp 1997. All rights reserved.
//
// This source code is intended only as an informational
// supplement to Win32 programming with the J/Direct
// technology in the Microsoft Win32 JVM.
//
// These samples show direct, low-overhead access to 
// commonly-used Win32 programming interfaces. Information
// on how to use J/Direct can be found in the Microsoft 
// SDK for Java.

import misc.*;

/**
 * Hello World is a minimum application that displays a 
 * line of text in its client area. It shows the minimum
 * code required to write a Win32 GUI application using
 * J/Direct.
 */
public class HelloWorld extends Window {

  public static void main( String[] args ) {
    HelloWorld  me = new HelloWorld();
    Application app = new Application();

    try {
      // By setting mainFrame( true ), we're saying that 
      // this window is the main window for the 
      // application. Thus, when the window is closed, a
      // WM_QUIT message should be sent automatically to
      // terminate the message loop and exit the 
      // application.
      me.mainFrame( true );

      // Create and show the main window.
      me.create();
      me.show();

      // Run the application's message pump.
      app.run();
    }
    catch( Exception e ) {
      e.printStackTrace();
    }
  }

  /*  . . . . . . . . . . . . . . . . . . . . . . . . . */

  public HelloWorld() {
    super( "J/Direct Hello World Application" );
  }

  /*  . . . . . . . . . . . . . . . . . . . . . . . . . */

  public void onPaint( PAINTSTRUCT ps, DeviceContext dc ) {
    String message = "Hello, world!";
    RECT rc = getClientRect();
    SIZE sz = dc.getTextSize( message );

    dc.textOut((rc.right - sz.cx) / 2, 
               (rc.bottom - sz.cy) / 2, message );
  }
}

Figure 4: A "Hello World" application from Jumpstart.

Part of the misc package of Jumpstart includes PAINTSTRUCT and DeviceContext classes, each of which provides the same members and methods of the Windows PaintStruct and DeviceContext classes. The only real difference is that your invocation is performed using Java syntax; the flow of Windows messages is the same as in a Windows application — as opposed to a Java application using AWT. The Window class also provides a standard message method called windowProc, through which a subclass of Window can override the method for explicit message handling.

This is a simple example which illustrates how much potential J/Direct (and Jumpstart) provide in terms of developing Windows applications while taking advantage of Java as a language.

Visual J++ Integration

The syntax of many of the J/Direct @dll directives can be picky in terms of extra spaces not being allowed, etc. To help promote J/Direct integration into your Java development efforts, VJ++ contains a useful tool called J/Direct CallBuilder, which can run in the IDE. Using CallBuilder, you are provided with a large list of methods, structures, and constants that can then be automatically inserted into your source code. This is useful since you don't have to continually look up method references on your MSDN CDs.

At the time of this writing, a default file (WIN32.txt) was provided with most of the Win32 pieces. Hypothetically, if you had your own DLL that you used often, you could construct a reference file similar to WIN32.txt with all the methods, structures, and constants that were part of your own DLL.

Conclusion

We've only touched briefly on what is possible using J/Direct as a gateway to your Java development efforts. Basically, the entire Windows operating system is at your disposal, yet you can take advantage of all the language constructs that Java provides.

Putting any political issues aside, J/Direct provides a powerful interface tool to Java developers who are developing strictly for the Windows platform.

Aris Buinevicius was one of the first employees of Bristol Technologies, where he helped develop cross-platform porting solutions for developers moving from Windows to UNIX; he later co-founded Stingray Software. Aris has been programming for more than 15 years, with the last two concentrating on Java.