Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Examining Microsoft's J/Direct


Dr. Dobb's Journal January 1998: Examining Microsoft's J/Direct

Making Java native code easy on Windows 95/NT

Andy is a QA engineer at NuMega Technologies, and can be contacted at andyw@ numega.com.


Recently, there has been a movement to write every Java applet or application entirely in Java. There is nothing wrong with a pure Java application; however, this initiative does present a problem -- what do you do with all the old legacy code? Many companies have thousands or even millions of lines of code that cannot be thrown away.

Conventionally, legacy code was reused through some Java native method calling convention such as RNI, JRI, or JNI. Each method is dependent upon the virtual machine (VM) being used. Though these calling conventions did work, they all required a lot of work on the developer's part. Each calling convention had the same basic steps. First, write a Java class that declares the native function. Next, run some tool to generate a C header file that tells the developer the name of the native function and all its arguments. Then, implement the function and export it via the C language naming convention. Finally, compile the C source file into a library. If you were targeting the Windows platform, you also had to create a DEF file and explicitly export each native function.

Microsoft's J/Direct, which ships as part of Internet Explorer 4.0 (and with future versions of Windows 95/NT and Internet Information Server), simplifies the native code calling process almost to the point where a DLL function can be directly called from within a Java applet or application. This technology streamlines the process of accessing native code, with only a slight performance degradation. Performance of J/Direct code is dependent upon the parameters that the native function requires, however. Sometimes the performance is slower, and sometimes it is significantly faster than other native code calling conventions (especially when character arrays or strings are used as parameters).

J/Direct Directives

Commenting code was never a Java language requirement until J/Direct came along. With J/Direct, comments are used as compiler directives that state the name of a DLL to load, define a system data structure, or define a field within a structure. There are three different types of directives; see Table 1. Directives simply tell the compiler how to interpret the following line of code within the Java application.

Each J/Direct directive is a comment specified via a /**@dll.<operation>()*/. All J/Direct directives begin with /**. The double asterisk tells the compiler that the line of code is a special case of a comment. The compiler then looks at the remainder of the line and determines if it is something that can be interpreted.

Example 1 shows a typical example of how J/Direct works with directives. The first line is the directive, which states nothing more than to import KERNEL32.DLL. The second line is a declaration for a function within KERNEL32.DLL. The compiler will look at this construct and generate code to tell the VM to load KERNEL32.DLL, perform any necessary conversion of Java data types to the native language data types, invoke GlobalLock, and finally perform any data type conversions from system data types to Java data types.

Even though the J/Direct call of GlobalLock seems a little complex, it is all done transparently. Previously, you would have had to create code within a native library to perform the function of data type conversion between the native language's data types and Java's. J/Direct performs these operations automatically.

Each native item, be it a function or a structure, must be preceded by a J/Direct directive. This directive is only applicable to the line of code immediately following the directive with only one exception. The exception is that a default library can be specified to support any native function declaration within a class.

Importing Functions

Functions are imported in a straightforward manner. You start by creating the J/Direct directive that specifies the DLL that contains the API you want to expose. Next, you create the declaration for the API. Since nothing in life is ever that easy, there are rules for both the directive and function declaration.

There are several different directives for importing a function. An example of a few are shown (again, see Table 1). The first and simplest is to simply specify the DLL that contains the function declared in the following line of code. This directive is the most common one to use and has the greatest likelihood of working on the first attempt.

The second directive in Table 1 is used when an OLE method needs to be exposed. This directive simply works by specifying the name of the DLL to load and another parameter that is set to OLE in lowercase in Table 1. Why use a special calling convention for OLE methods? This is not a requirement, but more of an added feature. OLE methods typically return an HRESULT type. The HRESULT is set to a value of S_OK whenever the method successfully performed its operation. Any other result code typically represents an error condition where the function failed. The OLE directive tells the compiler to generate code that checks the result from the OLE method. If the OLE method fails, a ComFailedException is thrown.

There are two ways to specify an entry point for a particular function. The first way is to specify the name of the entry point itself. The second way is to specify the ordinal entry point for the function. Example 2 shows both aliasing methods. But why is it necessary to use either of these?

Aliasing makes it possible to call a native function by a different name within the Java application than the name that was exported. One reason to use aliasing is to provide a uniform naming convention within the application. Java class methods always begin with a lowercase character, but Win32 APIs typically start with an uppercase character. Aliasing makes it possible to use the Java naming convention rather than the Win32 convention.

The second reason for aliasing relates to functions exported by ordinal only. Sometimes a function is exported by an ordinal value. This value could be a number like 32. It is illegal to have Java methods named after a number (not to mention strange). It is necessary to convert the ordinal value to a valid method name so that the method can be invoked within the Java application.

Now that we know about the directives for the function, how do we define the function itself? There shouldn't be too many surprises here. First, all native methods must be declared with the native modifier. There is only one significant difference between native method declarations and J/Direct native method declarations -- J/Direct native methods are all static methods.

As with all native method conventions, some conversion between data types is frequently necessary. In J/Direct it is only necessary to convert the function declaration from the system version to the Java version. A complete conversion chart can be found in the J/Direct online documentation in the Java SDK 2.0 from Microsoft. These conversions are trivial and take little time to perform. Remember that the conversions are only used for the function declaration, and that the J/Direct code will handle the actual data type conversion when the method is invoked.

Creating the right import directive to expose a native function seems pretty easy. There are only two tricky bits: The first is remembering to add the static declaration to all J/Direct native methods, and the second is the data type conversion. The data type conversion can be a sticky point, especially with so many special data structures within the Win32 API. How does J/Direct handle these special data structures?

Data Structure Directives

Native system data structures are not much more difficult to use than the native methods. The process is pretty much the same for data structures as it is for native methods; select the right J/Direct directive and declare the structure and all its fields as Java types.

The J/Direct structure directives work differently from the native function's directives in that data structures are not loaded from a DLL. Instead, the data structure is simply redefined. Example 3 shows a simple native C data structure converted to a Java J/Direct structure. Note that the fields were simply recreated in Java with the appropriate access modifier and Java type.

Structures become a little trickier when an array of some sort is used. A special conversion must occur whenever fixed-length array objects are created. Say that a structure had an array 24 bytes long and this structure would be used in a Java application. The structure would first be declared via the normal J/Direct directive. However, the array portion would have an additional directive that would specify the length of the field. There is also a special case where a field may translate in a Java string object; this requires a special conversion. Example 4 shows both conversions.

com.ms.dll

The com.ms.dll package contains four classes in Table 2. The classes provide for all the remaining functionality necessary for Java and native functions to interact.

The Callback class is used to provide an interface to all Win32 callback functions. Applications use this class to define a Java callback function to be used from native code. Creating a new class and extending it from the callback class accomplishes this. Then simply override Callback's callback method. Listing One, for example, shows a callback for the WindowProc message process for a window The important items for now are the class and callback declarations.

The DllLib class provides numerous functions for converting between native memory pointers and Java types. One example of this is the method ptrToString(), which converts a native pointer to a character buffer into a new Java String object.

The Root class allows the application to prevent garbage collection on a given object. This class acts as a sort of holder for a given object. You can allocate, extract, and free the object associated with the Root object.

The final class, Win32Exception, provides error notification, should a Win32 API call fail.

The Windows Clipboard

The Windows Clipboard has a range of functionality that cannot be accessed, for the most part, from Java. One example of this is a clipboard viewer -- an application that can display the data currently in the clipboard. A clipboard viewer is a simple application. The application simply registers itself with the clipboard, stating that the application is a viewer. The clipboard will then send a WM_DRAWCLIPBOARD to the last application that registered itself with the clipboard. This message indicates that the clipboard has changed, causing the application to perform some operation, like displaying the new data. The application must process the message and forward the message to the next viewer in the clipboard viewer chain. A clipboard viewer written in a native system language is trivial, but writing one in Java is a bit tricky -- even with J/Direct.

The application I present here is a simple clipboard viewer. The application does little more than display text objects in the clipboard on the application's frame. (The complete source code and related files are available electronically; see "Resource Center," page 3.)

First, the Java application must be able to receive Windows messages. The problem with this is that Java applications are sent Event objects, which represent messages that are not system dependent. Regular window messages are not sent to Java applications from the VM. However, every window has a message process. A frame is a window. Therefore, it should be an easy process to get the window message process by calling a few Win32 APIs. Listing Two is a special Frame class that is able to determine the window handle (HWND) associated with the frame. The handle is critical in establishing a hook into the message process for the window. The actual hooking of the message process is accomplished by the hook() method. Note that here we use a J/Direct directive that gives a default library for all native methods.

Once the message process is hooked, another Java message processor is used to manage all window messages. A message process is a callback function, so the Java equivalent must be a class derived from com.ms.dll.Callback; see Listing One.

With the ability to receive window messages, the application now needs to register itself with the clipboard. This is done through the Clipboard class in Listing Three. The Clipboard class simply registers the application's frame handle with the system's clipboard. The clipboard will now send messages to the application whenever a new item is placed on the clipboard.

The aforementioned classes provide support for the clipboard viewer, but clearly they don't drive the application. Listing Four is the public class that sets the entire process in motion. The main() method starts by creating an instance of the new Frame class. Then an object of the public class is created and placed within the frame. The application then calls the frame's hook() method, which returns the window handle for the frame. The application then sets the message-process handler to the public class's object. The public class now begins to receive and process window messages through the MessageProc() method. The next step is to simply register the application with the clipboard as a clipboard viewer, adding the application to the clipboard viewer chain. Now the clipboard will send a message any time data on the clipboard changes. When the application terminates, the finalize() method is called, closing all references to the clipboard and removing the application from the clipboard viewer chain.

The interesting thing to note is that in this application there are more native API calls than there are Java method calls. This means that J/Direct can be used to write Windows applications in Java.

Conclusion

One of the biggest problems we often face is how to port an application from one platform or language to another. Often, an application is too big or too complex to be ported over in a timely fashion. J/Direct changes much of that by allowing you to use existing applications' DLLs directly in new Java applications.

DDJ

Listing One

// J/Direct class WindowProc extends Callback
{
    int m_oldWndProc = 0;
    MessageHandler m_msgHndlr = null;
    /** @dll.import("USER32") */
    private static native int CallWindowProc ( int lpPrevWndFunc,
        int hwnd,
        int msg,
        int wParam,
        int lParam );
    public WindowProc ( int wndProc )
    {
       super();
       m_oldWndProc = wndProc;
    }
    public void setMessageHandler ( MessageHandler msgHndlr )  
                                               throws NullPointerException
    {
        if ( msgHndlr == null)
        {
            throw new NullPointerException ( "Bad MessageHandler" );
        }
        m_msgHndlr = msgHndlr;


</p>
    }
    public int callback ( int hwnd, int code,  int wParam, int lParam )
    {
       if ( m_msgHndlr != null )
           m_msgHndlr.MessageProc ( hwnd, code, wParam, lParam );
       return CallWindowProc(m_oldWndProc, hwnd, code, wParam, lParam );
    }
}

Back to Article

Listing Two

// WndFrame allows a developer to hook the Java application's// WindowProc to allow real Window's message processing
/** @dll.import("USER32") */
class WndFrame extends AwtUIFrame
{   
    // Win32 API functions 
    private static native int FindWindow(String lpClassName,
        String lpWindowName);
    private static native int GetWindowLong ( int hwnd,
        int nIndex );
    private static native int SetWindowLong( int hWnd,
        int nIndex,
        Callback NewLong );
    // GetWindowLong and SetWindowLong index
    private final static int GWL_WNDPROC = (-4);
    // class fields
    int m_hwnd = 0;
    String m_windowName = null;
    WindowProc m_wndProc = null;
    WndFrame(String str)
    {
        super ( str );
        m_windowName = str;
    }
    public synchronized int hook() throws DLLException, APIException
    {  
        try
        {
            // Find the handle for the window
            m_hwnd = FindWindow("MSAWT_Comp_Class", m_windowName);
            if ( m_hwnd == 0 )
             throw new APIException("Could not find window:" + m_windowName);
            // get the window proc address
            int wndProc = GetWindowLong ( m_hwnd, GWL_WNDPROC );
            if ( wndProc == 0 )
              throw new APIException ( 
                           "Could not get windowProc for: " + m_windowName );
           m_wndProc = new WindowProc ( wndProc );
            // Set the new window proc with the J/Direct callback
            if ( SetWindowLong ( m_hwnd, GWL_WNDPROC, m_wndProc )== 0)
                throw new APIException ( "Unable to set new WindowProc" );
        } catch ( UnsatisfiedLinkError ule )
        {
            throw new DLLException ( "Unable to load User32.dll" );
        }
        return m_hwnd;
    }
    public synchronized WindowProc getWndProc ( ) throws NullPointerException
    {
        if( m_wndProc == null )
        {
            throw new NullPointerException ( "WindProc not initialized" );
        }
        return m_wndProc;
    }
}       

Back to Article

Listing Three

/** @dll.import("USER32") */class Clipboard extends Object
{
    protected static native boolean OpenClipboard( int hWndNewOwner );
    protected static native int SetClipboardViewer( int hWndNewOwner );
    protected static native boolean ChangeClipboardChain( int hWndRemove,
        int hWndNewNext);
    protected static native boolean CloseClipboard();
    protected static native int GetPriorityClipboardFormat( 
                                             int[] formats, int length );
    protected static native int GetClipboardData( int format );


</p>
    /** @dll.import("KERNEL32" ) */
    protected static native int GlobalLock ( int addr );


</p>
    /** @dll.import("KERNEL32" ) */
    protected static native int GlobalUnlock ( int addr );


</p>
    protected int m_hwnd = 0;
    protected int m_oldHwnd = 0;


</p>
    protected static final int CF_TEXT = 1;
    Clipboard()
    { 
        super();
    }
    public void open ( ) throws DLLException, APIException


</p>
   {
        open ( 0 );
    }
    public void open( int hwnd ) throws DLLException, APIException
    {                       
        try
        {   
            if ( OpenClipboard ( hwnd ) == false)
                throw new APIException( "Could not open clipboard" );
        } catch ( UnsatisfiedLinkError ule )
        {
            throw new DLLException ( "Failed to load User32.dll" );
        }
    }
    public int setViewer ( int hwnd ) throws DLLException
    {
        m_hwnd = hwnd;
        try
        {
            m_oldHwnd = SetClipboardViewer ( hwnd );
            return m_oldHwnd;
        } catch ( UnsatisfiedLinkError ule )
        {
            throw new DLLException ( "Failed to load User32.dll" );
        }
    }
    public void closeViewer ( ) throws DLLException
    {
       try
       {
           ChangeClipboardChain ( m_hwnd, m_oldHwnd);
       } catch ( UnsatisfiedLinkError elu )
       {
           throw new DLLException ( "Failed to load USER32.DLL");
       }
    }
    public void close ( )
    {
        CloseClipboard();
    }
    public String getData() throws APIException
    {
        int [] priority = new int[1];
        priority[0] = CF_TEXT;
        int format = 
            GetPriorityClipboardFormat ( priority, priority.length );
        if ( (format == -1) || ( format == 0 ) )
           throw new APIException ( "No data ready" );
        int results = GetClipboardData( format );
        int locked = GlobalLock( results );
        String str = DllLib.ptrToStringAnsi( locked );
        GlobalUnlock( results );
        return str;
    }
}

Back to Article

Listing Four

public class DirectSample extends AwtUIApplet implements MessageHandler{
    public Clipboard m_cp = null;
    public int m_oldHwnd = 0;
    int m_hwnd = 0;
    UIDrawText m_text = null;
    UIScrollViewer m_view = null;


protected static final int WM_DRAWCLIPBOARD = 0x0308; protected static final int WM_CHANGECBCHAIN = 0x030D;

DirectSample( ) { super(); } public static void main ( String arg[] ) { WndFrame frame = new WndFrame("J/Direct Sample"); frame.setLayout(new UIBorderLayout()); DirectSample ds = new DirectSample(); frame.add( "Center", ds); frame.resize ( new Dimension (320,200 )); ds.init(); frame.show(); try { ds.m_hwnd = frame.hook(); WindowProc wndProc = frame.getWndProc(); wndProc.setMessageHandler ( ds );

ds.m_cp = new Clipboard(); ds.m_oldHwnd = ds.m_cp.setViewer ( ds.m_hwnd );

} catch ( DLLException ioe ) { System.out.println ( "DLL Error: " + ioe.getMessage() ); } catch ( APIException e )

{ System.out.println ( "API error: " + e.getMessage() ); } } public void init ( ) { setLayout( new UIBorderLayout() ); m_text = new UIDrawText ( ); m_view = new UIScrollViewer ( m_text ); add( "Center", m_view ); } /** @dll.import( "USER32" ) */ private static native int SendMessage ( int hwnd, int code, int wParam, int lParam ); public void MessageProc ( int hwnd, int code, int wParam, int lParam) { if ( code == WM_DRAWCLIPBOARD ) { try { m_cp.open ( m_hwnd ); m_text.setText( m_cp.getData() ); m_cp.close(); m_view.invalidate(); } catch ( DLLException de ) { System.out.println ( "DllException: " + de.getMessage() ); } catch ( APIException ae ) { System.out.println ( "APIException: " + ae.getMessage () ); } SendMessage ( m_oldHwnd, code, wParam, lParam); } if ( code == WM_CHANGECBCHAIN ) { SendMessage ( m_oldHwnd, code, wParam, lParam); } } public void finalize ( ) throws Throwable { System.out.println ( "Exiting" ); try { m_cp.closeViewer(); // m_cp.close(); } catch ( DLLException de ) { System.out.println ( "DLLException: " + de.getMessage() ); } super.finalize(); } }

Back to Article


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.