This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND


This article assumes you're familiar with Visual J++
Download the code (2KB)

Going Deep with J/Direct
Mike Pietraszak

What good's a programming language if you can't use the operating system it runs on? J/Direct cures a major complaint that many programmers have about Java.
The first installment of my two-part introduction to J/Direct™ covered the basics: type conversions, aliasing of API names, security, using J/Direct with applets, and J/Direct's relationship to RNI. In this second part, I'll shift the focus to structs, callbacks, pointers, and calling OLE APIs. Because they use J/Direct, the samples in this article must be built with the Microsoft® Visual J++™ Compiler (JVC) version 1.02.4213 or greater, and run on the Microsoft Virtual Machine (VM) version 4.79.2164 or greater. Both are available in the Microsoft SDK for Java, version 2.0, available at http://www.microsoft.com/java.

Structs
      When J/Direct is used to access DLL functions, standard Java objects cannot be modified directly—structures built using the @dll.struct directive must be used. Take the Choose Color dialog of the Windows® Common Dialog API, shown in Figure 1, for an example. If the ChooseColor API is called from Visual Basic, the call takes a parameter of type CHOOSECOLOR.


Declare Function ChooseColor Lib "comdlg32.dll" Alias "ChooseColor" 
(pChoosecolor As CHOOSECOLOR) As Long
The CHOOSECOLOR type is a structure that represents a collection of intrinsic Visual Basic® variable types such as Long and String.

 Type CHOOSECOLOR
     lStructSize As Long
     hwndOwner As Long
     hInstance As Long
     rgbResult As Long
     lpCustColors As Long
     flags As Long
     lCustData As Long
     lpfnHook As Long
     lpTemplateName As String
 End Type
      Using the marshalling table I introduced in my first J/Direct article (MIND, January 1998), the items in this structure can easily be converted to Java types. The one missing piece needed to call this API from Java is the @dll.struct directive. This directive uses a Java class to create a marshallable data structure. The @dll directive should immediately precede the class declaration.

 /** @dll.struct() */
 class CHOOSECOLOR {
     public int lStructSize;
     public int hwndOwner;
     public int hInstance;
     public int rgbResult;
     public int lpCustColors;
     public int flags;
     public int lCustData;
     public int lpfnHook;
     public String lpTemplateName;
 }
      Structs can also be nested, although you'll take a performance hit. Because Java does not support true embedded objects, the VM must translate between each Java reference and its native implementation. So nesting structs means more translations need to be performed. In time-critical applications, it is best to simply make the nested struct elements a part of the host struct and reduce the total number of Java-to-native conversions. Following are nested and manually nested examples for a JRECT struct.

 // Nested
 /** @dll.struct() */
 class JRECT {
     public int width;
     public int height;
     public POINT location;
 }
 /** @dll.struct() */
 class POINT {
     public int x;
     public int y;
 }
 
 
 // Manually nested
 /** @dll.struct() */
 class JRECT {
     public int width;
     public int height;
     public int x;
     public int y;
 }

Pointers
      If the Add to Custom Colors button of the Choose Color dialog in Figure 1 is pressed, the current color is recorded in the Custom Colors area of the dialog, as shown in Figure 2. These colors are stored as an array of 16

Figure 1: Windows Color Selection Common Dialog
Figure 1: Windows Color Selection Common Dialog


integers and may be passed to and from the ChooseColor API. But instead of passing the actual array, a pointer to the array (custColorPtr) is used in the CHOOSECOLOR structure.
      The initial array may be
Figure 2: Custom Colors
Figure 2: Custom Colors
passed to the API by allocating the required memory and copying the default-value array into it. The com.ms.dll.DllLib class provides the allocCoTaskMem and copy methods for this purpose. The allocCoTaskMem function returns an integer that's the address of the allocated memory space.


 // Allocate the necessary memory block (in bytes)
 int custColorPtr = DllLib.allocCoTaskMem(NUM_CUST_COLORS * COLOR_SIZE_IN_BYTES);
 
 // Set the default colors of the array to white
 for (int i = 0; i < NUM_CUST_COLORS; i++)
     cust[i] = 0x00FFFFFF;
 
 // Copy the array to the allocated space
 // Params : (source array, array offset, dest ptr, number of array elements)
 DllLib.copy(cust, 0, custColorPtr, NUM_CUST_COLORS);
      After the API is called, the user modifications to the custom colors in the allocated memory space are copied back to the initial array. The freeCoTaskMem function is called when the memory can be safely returned.

 // Params (source ptr, destination array, array offset, number of array
 // elements)
 DllLib.copy(custColorPtr, cust, 0, NUM_CUST_COLORS);
 
 // Free up allocated memory
 DllLib.freeCoTaskMem(custColorPtr);

Callbacks
      The CHOOSECOLOR structure used by the ChooseColor API also has a member named lpfnHook, which is a pointer to a callback method. These types of pointers are handled very differently than those previously discussed. To use a callback, a class that extends com.ms.dll.Callback must be created, and must include one public nonstatic method named callback. This callback method is invoked by the common dialog whenever events are fired. The Callback class in the com.ms.dll package makes this possible:

 public class Win32ColorDlg extends com.ms.dll.Callback {
     public int callback(int hWnd, int msg, int wParam, int lParam) {
         return 0;
     }
 }
Because callbacks are subject to garbage collection, you're only guaranteed a valid pointer for the duration of one function call. The objects will not be reclaimed by the garbage collector while in use by the native DLL function while the call is in progress. But longer-term use across function calls requires that the Callback class be wrapped inside a 32-bit root handle. The com.ms.dll.Root class can be used to create this handle:

 // Create a root handle.
 // 'this' is an instance of the class that extends com.ms.dll.Callback 
 int hook = Root.alloc(this);
      The actual native address of the root handle is obtained by calling the addrOf method in the com.ms.dll.DllLib class. This method returns the actual native address of the callback as an integer.

 // Assign structure element lpfnHook the address of the root handle.
 cc.lpfnHook = DllLib.addrOf(hook);
      The previous example (and the one in Figure 3) demonstrates a callback by using the main program class to extend com.ms.dll.Callback. But the inner-class structure defined as a part of the JDK 1.1 language modifications could also be used for callbacks.

 public class Win32ColorDlg {
     class InnerCallback extends extends com.ms.dll.Callback
         public int callback(int hWnd, int msg, int wParam, int lParam) {
         // Handle the callback here or call a method in the main
         // class (Win32ColorDlg) to process the msg.
         return 0;
         }
     }
 }
      The hook wrapper would also need to be modified to create a new instance of the inner class to handle the callback.

 // Create a root handle.
 // InnerCallback is the class that extends com.ms.dll.Callback 
 int hook = Root.alloc(new InnerCallback());

ANSI Versus Unicode
      My previous J/Direct article mentioned the sndPlaySound API in the Windows MultiMedia Library (WINMM.DLL):


 /** @dll.import("winmm") */
 static native boolean sndPlaySound(String lpszSound, int fuSound);
Actually, there is no API named sndPlaySound. All Win32 APIs that deal with strings are implemented in two forms: one as ANSI and one as Unicode. Because the sndPlaySound API takes a String parameter to specify the path to the WAV file, there are sndPlaySoundA and sndPlaySoundW implementations.
      When the sndPlaySound API is called, first the lpszSound parameter is converted to a null-terminated ANSI string. Next, the VM attempts to find an API named sndPlaySound. This attempt fails, and the VM then looks for sndPlaySoundA, which it finds. Calling the API without modifiers is the same as calling the API with the ANSI modifier, since ANSI mode is the default:

 /** @dll.import("winmm",ansi) */
 static native boolean sndPlaySound(String lpszSound, int fuSound);
      If the Unicode version of an API like MessageBox is desired, the unicode modifer can be specified in the @dll.import declaration.

 /** @dll.import("USER32",unicode) */
 static native int MessageBox(int hwnd, String text, String title, int style);
This time, the VM converts the String parameters to null-terminated Unicode strings. Next, the VM attempts to find an API named MessageBox. This attempt fails, and the VM then looks for MessageBoxW, which it successfully finds.
      The best way to invoke Win32 APIs, however, is by using the auto modifier:

 /** @dll.import("USER32",auto) */
 static native int MessageBox(int hwnd, String text, String title, int style);
      Using the default ANSI mode guarantees that the call will work, but on fully Unicode-based systems like Windows NT, a performance penalty is incurred when strings are marshalled between Unicode and ANSI. The auto modifier removes this penalty by defaulting to the underlying string support for the system. If the API is called on Windows 95, ANSI is used; if called on Windows NT, Unicode is used.

Calling OLE APIs
      The syntax for accessing OLE APIs in OLE32.DLL and OLEAUT32.DLL differs somewhat from the @dll.import syntax for several reasons. First, OLE functions are always Unicode and not ANSI. Second, almost every OLE call returns an HRESULT value like S_OK or multiple values like S_OK and S_FALSE. Third, if an OLE function returns a value (other than an HRESULT), the value is set by passing a pointer as a parameter (typically the last one) to the function. The function sets the return value at the location specified by the pointer. Fourth, OLE handles strings in a special way by allocating string space using CoTaskMemAlloc. The API caller is expected to free the space itself using CoTaskMemFree.
      The OLE API CoCreateGuid, when called from C, might look something like this:


 // HRESULT CoCreateGuid( GUID *pguid );
 HRESULT hr;
 GUID g;
 hr = CoCreateGuid(&g);
The Java call to this API ignores the HRESULT. Instead, the last parameter is removed from the parameter argument list and is passed back as the return value.

 /** @dll.import("OLE32",ole) */
 static native _Guid CoCreateGuid();
 
 _Guid g = CoCreateGuid();
      The MakeGuid program shown in Figure 4 demonstrates how this API can be used to generate and print a new globally unique identifier.

Error Handling
      Whether you're attempting to debug a failed API call or whether you're simply writing robust code to trap error conditions, you will likely need to utilize the setLastError modifier. This modifier is used when declaring the APIs with the import statement.


 /** @dll.import("user32",setLastError) */
 static native boolean LoadCursorFromFile (String lpFileName);
      The setLastError modifier must be used in place of a call to the traditional Win32 API GetLastErrorCode. This is because the Microsoft Win32 VM may make API calls of its own while executing your API calls. So, if you use GetLastErrorCode, you could accidentally get one of the VM's error codes by mistake. Using the setLastError modifer guarantees that only the errors generated by your API calls will be returned.
      The source code in Figure 5 demonstrates the setLastError syntax. The LoadCursorFromFile API is called with a filename parameter for a file that does not exist. The following error will be displayed when the program is run.

 Error(2) :(0x2) The system cannot find the file specified.
It is worth noting that the setLastError modifier is necessary for error trapping because the VM does not capture API errors by default for performance reasons. It is also worth noting that each Java thread tracks its own errors, so failed API calls on separate threads will return the appropriate error.

Having It All
      Programming in Java used to mean giving up all that Windows has to offer. Multimedia, COM and OLE, email (MAPI), native UI, printing, and access to devices like game controllers and modems all seemed out of reach. Functionality and performance were sacrificed for "purity." Today, J/Direct users sacrifice nothing. They can enjoy the benefits of the Java language and make the most of the Windows platform.

From the April 1998 issue of Microsoft Interactive Developer.