Feb 5, 2008 12:00 AM PT

Open source Java projects: Java Native Access

An easier way to access native code

If you've used the Java Native Interface (JNI) to make a platform-specific native library accessible to your Java programs, you know how tedious it can be. Jeff Friesen continues his series on lesser-known open source Java projects by introducing you to Java Native Access -- a project that eliminates the tedium and error associated with JNI and lets you access C libraries programmatically.

In situations where Java does not provide the necessary APIs, it is sometimes necessary to use the Java Native Interface (JNI) to make platform-specific native libraries accessible to Java programs. For instance, I have used JNI to access universal serial bus and TWAIN-based scanner device libraries on the Windows XP platform, and a smartcard library on an older Windows NT platform.

Open source licenses
Like the balloontip project introduced last month, JNA is subject to the GNU Lesser General Public License (LGPL) v2.1. See the Resources section to learn more about this open source license.

In all cases I have followed the same basic (and tedious) procedure: First I created a Java class to load a JNI-friendly library (that accesses the other library) and declare its native methods. After using the JDK's javah tool to create prototypes for the JNI-friendly library's functions from the class's native method declarations, I wrote the library in the C language and compiled the code with my C compiler.

Although there is nothing difficult about these tasks, writing the C code is slow work -- C-based string manipulation and working with pointers can be tricky, for instance. Furthermore, it is easy to make a mistake when using JNI, which can lead to memory leaks and program crashes whose causes are hard to detect.

In this second article in the Open source Java projects series, I'll introduce you to an easier and safer solution: Todd Fast and Timothy Wall's Java Native Access (JNA) project. JNA lets you access native code from Java while avoiding C and the Java Native Interface. I'll start with a quick introduction to JNA and the software required to run the examples in this article. After that, I'll show you how to use JNA to port useful code from three Windows native libraries into your Java programs.

Get started with JNA

The Java Native Access project is hosted on Java.net, where you can download the project's online Javadoc and the software itself. Although the download section identifies five JAR files, you only need to download jna.jar and examples.jar for the purposes of this article.

JNA and Java Web Start
Along with jna.jar and examples.jar, the JNA project download section presents three JAR files that let you work with JNA in a Java Web Start context on Mac OS X, Linux/Unix, and Windows platforms.

The jna.jar file provides the essential JNA software and is required to run all of the examples you'll find here. This JAR file contains several packages of classes, along with JNI-friendly native libraries for the Unix, Linux, Windows, and Mac OS X platforms. Each library is responsible for dispatching native method calls to native libraries.

The examples.jar file provides various examples that illustrate JNA's usefulness. One of them uses JNA to implement an API for specifying transparent windows on the various platforms. In this article's final example, I'll show you how to use this API to fix the transparency problem identified by the VerifyAge2 application in last month's article.

Get local time

If you look under "How To Get Started Using JNA" on the Java Native Access homepage you will see a simple JNA usage example based on the Windows GetSystemTime() API function. The example focuses only on the essentials of JNA, and so it is somewhat incomplete. I'll start this introduction to JNA with a more complete Windows-based example that builds on that one.

This first example is based on the Windows GetLocalTime() API function, which returns the current local time and date. Unlike GetSystemTime(), which returns time/date information expressed in Coordinated Universal Time (UTC), GetLocalTime() returns time/date information expressed according to the current time zone.

Before using JNA to access GetLocalTime() from a Java program, you need to know the name (and possibly location) of this function's Windows Dynamic Link Library (DLL). As it turns out, GetLocalTime() is located in the same DLL as GetSystemTime(), which happens to be kernel32.dll. You also need to know the C-based declaration for GetLocalTime(). The declaration is shown in Listing 1.

Listing 1. The C-based declaration for GetLocalTime()

typedef struct
{
   WORD wYear;
   WORD wMonth;
   WORD wDayOfWeek;
   WORD wDay;
   WORD wHour;
   WORD wMinute;
   WORD wSecond;
   WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;

VOID GetLocalTime(LPSYSTEMTIME lpst);

The C-based declaration reveals the number and types of arguments passed to the function. In this example, there is only one argument -- a pointer to the Windows SYSTEMTIME structure. Furthermore, each structure member's type happens to be a 16-bit unsigned integer. Using this information, you can create an interface that fully describes GetLocalTime(), as shown in Listing 2.

Listing 2. Kernel32.java

// Kernel32.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface Kernel32 extends StdCallLibrary
{
   public static class SYSTEMTIME extends Structure
   {
      public short wYear;
      public short wMonth;
      public short wDayOfWeek;
      public short wDay;
      public short wHour;
      public short wMinute;
      public short wSecond;
      public short wMilliseconds;
   }

   void GetLocalTime (SYSTEMTIME result);
}

The Kernel32 interface

Because JNA requires a library's functions to be declared via an interface, Listing 2 reveals an interface that declares GetLocalTime(). According to convention, I named the interface Kernel32 because GetLocalTime() is located in the Windows kernel32.dll library.

A type-mapping problem
JNA's mapping of a Windows/C language unsigned integer type to a Java signed integer type could be problematic when comparing an API function's returned integer value to another integer value. If you are not careful when making the comparison, the wrong execution path could be taken in a decision statement. This is due to forgetting that any value whose most significant bit is set, is interpreted as positive from the unsigned integer perspective, and negative from the signed integer perspective.

The interface must extend the com.sun.jna.Library interface. Because Windows API functions adhere to the stdcall calling convention, the interface for declaring Windows API functions extends the com.sun.jna.win32.StdCallLibrary interface. This latter interface extends Library and com.sun.jna.win32.StdCall.

Earlier, you learned that GetLocalTime() requires a pointer to a SYSTEMTIME structure as its only argument. Because Java does not support pointers, JNA's alternative is to declare a structure class as a subclass of com.sun.jna.Structure. According to this abstract class's Javadoc, Structure corresponds to a C struct * in a parameter context.

The field members within the SYSTEMTIME class declaration are in the same order as those in the equivalent C struct. Keeping the field order consistent is important. For example, I discovered that swapping the order of wYear and wMonth resulted in the year being placed in wMonth and the month being placed in wYear.

Each field member has Java's short integer type. According to the "Default Type Mappings" section of the JNA homepage, this is the appropriate type to use. However, keep in mind this important difference: the Windows WORD type maps to C's 16-bit unsigned short integer type, whereas Java's short integer type is a 16-bit signed integer.

Access the local time with Kernel32

The GetSystemTime() example on the JNA homepage reveals that you must allocate an instance of the native library using the previously declared interface. You can accomplish this task via the com.sun.jna.Native class's public static Object loadLibrary(String name, Class interfaceClass) method, as shown in Listing 3.

Listing 3. LocalTime.java

// LocalTime.java

import com.sun.jna.*;

public class LocalTime
{
   public static void main (String [] args)
   {
      Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
                                                    Kernel32.class);
      Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
      lib.GetLocalTime (time);
      System.out.println ("Year is "+time.wYear);
      System.out.println ("Month is "+time.wMonth);
      System.out.println ("Day of Week is "+time.wDayOfWeek);
      System.out.println ("Day is "+time.wDay);
      System.out.println ("Hour is "+time.wHour);
      System.out.println ("Minute is "+time.wMinute);
      System.out.println ("Second is "+time.wSecond);
      System.out.println ("Milliseconds are "+time.wMilliseconds);
   }
}

Listing 3 executes Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class) to allocate a Kernel32 instance and load kernel32.dll. A path to this library is not needed because kernel32.dll is a standard Windows DLL. However, if the DLL could not be found, loadLibrary() would throw an UnsatisfiedLinkError.

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME (); creates a SYSTEMTIME structure instance. This instantiation is immediately followed by lib.GetLocalTime (time);, which populates this instance with local time/date values, and several System.out.println() calls that output these values.

Compile and run the application

This part is easy. Assuming that jna.jar, Kernel32.java, and LocalTime.java are located in the current directory, invoke javac -cp jna.jar;. LocalTime.java to compile the application's source code. Assuming a Windows platform, invoke java -cp jna.jar;. LocalTime to run the application. You should see output similar to Listing 4.

Listing 4. Output generated from LocalTime.java

Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156

Accessing joystick device info

Although the previous example introduces JNA, obtaining the local time and date is not actually a good use of this technology, or even JNI. Java's System.currentTimeMillis() method already returns this information, albeit expressed in milliseconds. Accessing a joystick device is a more appropriate use of JNA because Java does not provide an API for game controllers.

Ideally, you would build a platform-independent Java library that uses JNA to access the native Joystick APIs on Linux, Mac OS X, Windows, and Unix platforms. For brevity and convenience, this example will focus exclusively on accessing the Windows Joystick API. I'll also limit the focus to just a small part of the API.

As with GetLocalTime(), your first step is to identify the Joystick API's DLL. The name of this DLL is winmm.dll, which includes the Joystick API with other multimedia APIs -- winmm.dll is located in the same directory as kernel32.dll. You also need to know the C-based declarations for the joystick functions to be used. These function declarations are shown in Listing 5.

Listing 5. C-based declarations for some Joystick API functions

#define MAXPNAMELEN 32

typedef struct
{
   WORD  wMid;                  // manufacturer identifier
   WORD  wPid;                  // product identifier
   TCHAR szPname [MAXPNAMELEN]; // product name
   UINT  wXmin;                 // minimum x position
   UINT  wXmax;                 // maximum x position
   UINT  wYmin;                 // minimum y position
   UINT  wYmax;                 // maximum y position
   UINT  wZmin;                 // minimum z position
   UINT  wZmax;                 // maximum z position
   UINT  wNumButtons;           // number of buttons
   UINT  wPeriodMin;            // smallest supported polling interval when captured
   UINT  wPeriodMax;            // largest supported polling interval when captured
}
JOYCAPS, *LPJOYCAPS;

MMRESULT joyGetDevCaps(UINT IDDevice, LPJOYCAPS lpjc, UINT cbjc);

UINT joyGetNumDevs(VOID);

Functions of the Joystick API

Windows implements the Joystick API via several functions that begin with the joy prefix, and structures that various joy functions use. For example, joyGetNumDevs() returns the maximum number of joystick devices supported by the platform, and joyGetDevCaps() returns an attached joystick's capabilities.

The joyGetDevCaps() function takes three arguments:

  • A device ID ranging from 0 through joyGetNumDevs()-1
  • The address of a JOYCAPS structure that holds returned capability information
  • The size of the JOYCAPS structure in bytes

Although it appears otherwise, this function returns a 32-bit unsigned integer, where zero indicates an attached joystick.

The JOYCAPS structure identifies three types. In addition to the Windows WORD (16-bit unsigned short integer) type, which maps to Java's 16-bit signed short integer type, this structure specifies the Windows-defined UINT (32-bit unsigned integer) type, which maps to Java's 32-bit signed integer type, and the Windows text character type known as TCHAR.

Microsoft introduced TCHAR to let developers smoothly migrate functions with ASCII text arguments to functions with wide character (think Unicode) text arguments. Also, functions with text arguments are implemented as macros that map to ASCII or wide-character functions. For example, joyGetDevCaps() is really a macro that maps to joyGetDevCapsA() and joyGetDevCapsW().

Working with TCHAR

The use of TCHAR and the business of mapping macros to functions only slightly complicate the task of transitioning the C-based declarations to a JNA-based interface -- do you work with the ASCII or wide-character version of a joystick function? Both versions are shown in the interface below.

Listing 6. WinMM.java

// WinMM.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface WinMM extends StdCallLibrary
{
   final static int JOYCAPSA_SIZE = 72;

   public static class JOYCAPSA extends Structure
   {
      public short wMid;
      public short wPid;
      public byte szPname [] = new byte [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsA (int id, JOYCAPSA caps, int size);

   final static int JOYCAPSW_SIZE = 104;

   public static class JOYCAPSW extends Structure
   {
      public short wMid;
      public short wPid;
      public char szPname [] = new char [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsW (int id, JOYCAPSW caps, int size);

   int joyGetNumDevs ();
}

Listing 6 does not introduce new JNA features. Instead, it reinforces the convention of naming the interface after a native library. It also shows how to map the TCHAR array to equivalent byte and char Java arrays. Finally, it reveals structure size constant declarations. Listing 7 shows the usefulness of these constants when invoking joyGetDevCapsA() and joyGetDevCapsW().

Listing 7. JoystickInfo.java

// JoystickInfo.java

import com.sun.jna.*;

public class JoystickInfo
{
   public static void main (String [] args)
   {
      WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);
      int numDev = lib.joyGetNumDevs ();

      System.out.println ("joyGetDevCapsA() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsA (i, caps1, WinMM.JOYCAPSA_SIZE) == 0)
           {
               String pname = new String (caps1.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps1.wMid);
               System.out.println ("  wPid = "+caps1.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps1.wXmin);
               System.out.println ("  wXmax = "+caps1.wXmax);
               System.out.println ("  wYmin = "+caps1.wYmin);
               System.out.println ("  wYmax = "+caps1.wYmax);
               System.out.println ("  wZmin = "+caps1.wZmin);
               System.out.println ("  wZmax = "+caps1.wZmax);
               System.out.println ("  wNumButtons = "+caps1.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps1.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps1.wPeriodMax);
               System.out.println ();
           }

      System.out.println ("joyGetDevCapsW() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsW (i, caps2, WinMM.JOYCAPSW_SIZE) == 0)
           {
               String pname = new String (caps2.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps2.wMid);
               System.out.println ("  wPid = "+caps2.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps2.wXmin);
               System.out.println ("  wXmax = "+caps2.wXmax);
               System.out.println ("  wYmin = "+caps2.wYmin);
               System.out.println ("  wYmax = "+caps2.wYmax);
               System.out.println ("  wZmin = "+caps2.wZmin);
               System.out.println ("  wZmax = "+caps2.wZmax);
               System.out.println ("  wNumButtons = "+caps2.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps2.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps2.wPeriodMax);
               System.out.println ();
           }
   }
}

Although similar to LocalTime, JoystickInfo executes WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class); to allocate a WinMM instance and load winmm.dll. It also executes WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA (); and WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW (); to allocate needed structure instances.

Compile and run the application

Converting a C string to a Java string
The pname = pname.substring (0, pname.indexOf ('\0')); code fragment converts a C string to a Java string. Without this conversion, the \0 string terminator and any following garbage characters would be part of the Java string.

Assuming that jna.jar, WinMM.java, and JoystickInfo.java are located in the current directory, invoke javac -cp jna.jar;. JoystickInfo.java to compile the application's source code. Assuming a Windows platform, invoke java -cp jna.jar;. JoystickInfo to run the application. If no joystick devices are attached, you should see the output shown in Listing 8.

Listing 8. Output of JoystickInfo

joyGetDevCapsA() Demo
---------------------

joyGetDevCapsW() Demo
---------------------

This header-only output is due to each joyGetDevCap() invocation returning a non-zero value, which indicates either a non-attached joystick/game controller or an error. To generate more interesting output, attach one of these devices to your platform and re-run JoystickInfo. For example, I received the output shown below after attaching a Microsoft SideWinder Plug and Play Game Pad device:

Listing 9. Output after running JoystickInfo with a joystick attached

joyGetDevCapsA() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000

joyGetDevCapsW() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000

Transparent windows

The previous article in this series focused on Bernhard Pauler's balloontip project. For that article, I built an application GUI, called VerifyAge, that included a balloon tip. Figure 1 reveals one small problem with the GUI: the balloon tip's undecorated dialog container hides part of the window's frame, preventing access to the frame's Minimize and Maximize buttons, and making the overall GUI look unsightly.

Figure 1. It is not possible to minimize the GUI when the balloon tip is displayed

Although it would have been better to render that part of the undecorated dialog not presenting the balloon tip transparent, Java does not support transparent windows. Fortunately, we can solve this problem using the com.sun.jna.examples.WindowUtils class located in JNA's examples.jar file.

WindowUtils provides utility methods that use JNA to achieve transparent windows on Unix, Linux, Mac OS X, and Windows platforms. For example, public static void setWindowMask(final Window w, Icon mask) lets you choose which part of a window to make transparent based on pixels not defined by its mask argument. This task is demonstrated in Listing 10.

Listing 10. Using JNA to render a window transparent

// Create a mask for this dialog. This mask has the same shape as the
// dialog's rounded balloon tip and ensures that only the balloon tip
// part of the dialog will be visible. All other dialog pixels will
// disappear because they correspond to transparent mask pixels.

// Note: The drawing code is based on the drawing code in
// RoundedBalloonBorder.

Rectangle bounds = getBounds ();
BufferedImage bi = new BufferedImage (bounds.width, bounds.height,
                                      BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics ();
g.fillRoundRect (0, 0, bounds.width, bounds.height-VERT_OFFSET,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
g.drawRoundRect (0, 0, bounds.width-1, bounds.height-VERT_OFFSET-1,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
int [] xPoints = { HORZ_OFFSET, HORZ_OFFSET+VERT_OFFSET, HORZ_OFFSET };
int [] yPoints = { bounds.height-VERT_OFFSET-1, bounds.height-VERT_OFFSET
                   -1, bounds.height-1 };
g.fillPolygon (xPoints, yPoints, 3);
g.drawLine (xPoints [0], yPoints [0], xPoints [2], yPoints [2]);
g.drawLine (xPoints [1], yPoints [1], xPoints [2], yPoints [2]);
g.dispose ();
WindowUtils.setWindowMask (this, new ImageIcon (bi));

The code fragment in Listing 10 is an excerpt from the end of the TipFrame constructor in an enhanced VerifyAge2 application, which is included in this article's code archive. The constructor defines a mask in the shape of the rounded balloon tip, where only drawn pixels are non-transparent.

Assuming that your current directory contains examples.jar, jna.jar, and VerifyAge2.java, invoke javac -cp examples.jar;balloontip.jar VerifyAge2.java to compile the source code. Then invoke java -Dsun.java2d.noddraw=true -cp examples.jar;balloontip.jar;. VerifyAge2 to run the application. Figure 2 reveals transparency.

Figure 2. You can now minimize the GUI when a balloon tip appears

In conclusion

JNA is a project with a long history (stretching back to 1999) but it was first released in November 2006. Since then it has been slowly gaining the attention of developers who need to integrate native C code into Java-based projects. JNA has also made some waves among JRuby programmers because it can be used to work around one of JRuby's pervasive problems, lack of support for POSIX calls. JNA has also been proposed as a solution for extending Ruby with low-level C code.

I enjoyed working with JNA and believe you will find it easier and safer to use than JNI for accessing native code. Needless to say, there is more to JNA than I could cover in a single article. See the Resources section to learn more about this open source Java project. Experiment with it and share your experiences in the discussion forum for this series. I'll be back next month with another lesser known open source project that could benefit your everyday Java development.

Addendum: An alternative to WindowUtils.setWindowMask()

Shortly after writing this article I discovered that Java supports translucent and shaped windows as of the 6u10 build. After reading about this topic in Kirill Grouchnikov's blog, I modified VerifyAge2 to use this new alternative to WindowUtils.setWindowMask(), as follows:

// Create and install a balloon tip shape to ensure that only this part
// of the dialog will be visible.

Rectangle bounds = getBounds ();
GeneralPath gp;
gp = new GeneralPath (new RoundRectangle2D.Double (bounds.x, bounds.y,
                                                   bounds.width,
                                                   bounds.height-
                                                   VERT_OFFSET,
                                                   ARC_WIDTH*2-1,
                                                   ARC_HEIGHT*2-1));
gp.moveTo (HORZ_OFFSET, bounds.height-VERT_OFFSET);
gp.lineTo (HORZ_OFFSET, bounds.height);
gp.lineTo (HORZ_OFFSET+VERT_OFFSET+1, bounds.height-VERT_OFFSET);
AWTUtilities.setWindowShape (this, gp);

This code uses the new AWTUtilities class (located in the com.sun.awt package) and its public void setWindowShape(Window w, Shape s) method to reshape the TipFrame JDialog window into a balloon shape.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca website to discover all of his published Java articles and more.

Learn more about this topic