Skip to main content

Protect Your Legacy Code Investment with JNA

May 20, 2009

{cs.r.title}







The promise of Java Native Access (JNA) is to bridge the worlds of Java and legacy code. Why is this so important? For one, JNA obviates the need to rewrite legacy code where the option of a rewrite exists. Also, JNA means that expensive proprietary bridging solutions are no longer needed. The latter includes arcane artifacts such as proxy arrangements, hard-coding proprietary protocols, and so on. All such solutions tend to be difficult to write, error-prone, and potentially brittle. Another key element of JNA is that it effectively supersedes Java Native Interface (JNI).

Listing 1 illustrates a sneak preview of the type of code I'll look at in this article. In Listing 1, I invoke the GetTickCount() procedure from the Windows kernel32 DLL. GetTickCount() returns the number of milliseconds that have elapsed since the system was started.

public interface CLibrary extends Library {

   CLibrary INSTANCE = (CLibrary)
     Native.loadLibrary((Platform.isWindows() ? "kernel32" : "c"),
                                  CLibrary.class);

   int GetTickCount();
}

public static void main(String[] args) {
  System.out.println("TickCount" + CLibrary.INSTANCE.GetTickCount());
}

Listing 1. A simple JNA example

What's interesting about Listing 1 is the fact that no JNI code is needed. Instead, you can simply call a DLL symbol from Java code. There's no need for mapping and auto-generating JNI header files or any of that difficult stuff. Instead, with JNA, you simply load the required library, map the symbols of interest, and then invoke those symbols.

In short, JNA solutions can save money in any economy. Being able to call directly into legacy code from Java removes any need to use JNI or to rewrite the legacy code. Perhaps the greatest promise of JNA is a unified code environment. However, there are other issues relevant to JNA making an incursion into native code land. One such issue is deciding whether or not Java is a so-called system language.

Java: Not a System Language?

An important criticism of Java in the early days was that it was not a system language. Unlike C or C++, Java lives inside a JVM and cannot access low-level, machine-specific details. Where such access is allowed, it occurs via a high-level API. One of the key merits of isolating Java inside a JVM is safety -- the JVM can crash, but it won't bring the whole system down.

The advent of JNA potentially changes this, because now Java code can access C-style mechanisms. Listing 2 illustrates another example of Java code accessing data via a function in the Windows kernel32 DLL.

Kernel32 lib = Kernel32.INSTANCE;
SYSTEMTIME time = new SYSTEMTIME();
lib.GetSystemTime(time);
System.out.println("Today's integer value is " + time.wDay);

Listing 2. System time from kernel32.dll

Notice in Listing 2 that the Java code gets access to low-level platform data. So JNA means that Java can access system-level capability. However, another important use for JNA is legacy code access where substantial business value exists in the legacy code; for example, complex mathematical functions that have been written in C/C++. Now, rather than using JNI, it's possible to natively invoke the legacy functions with JNA. In other words, JNA can be considered a bridging technology.

JNA: A Bridging Technology

From the examples in Listings 1 and 2, you can see that JNA is an effective Java-to-native-to-Java bridging technology. This makes JNA different from JNI because there's no longer a need to auto-generate a header file or implement special C code. Instead, with JNA you simply map the library symbols you want and then you invoke them.

Let's now look at a more complete example of actually building a DLL and then calling it via JNA code.

An Example of Using JNA

Rather than just use JNA as a kind of one-way technology that simply calls into existing DLLs, it's important to be able to map JNA calls into your own DLLs. So I want to create a really simple DLL and then call into it via JNA code. I created a DLL using Microsoft Visual C++ 2005 Express Edition -- a free download from Microsoft. You can use a more recent version and the same approach should work.

I have to say, it's kind of weird to be talking about Microsoft Visual C++ in a Java.net article! Anyway, Listing 3 illustrates the important section of DLL code, most of which is auto-generated.

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved)
{
    return TRUE;
}

extern "C" __declspec(dllexport) DWORD helloWorld (DWORD divider)
{
    return 77/divider;
}

Listing 3. The DLL code

Don't worry about the details of Listing 3 -- the bulk of it is automatically generated. The important part is the function called helloWorld(). This function doesn't do a whole lot: it is passed an integer parameter and divides it into a fixed value of 77. Clearly, this isn't production standard. Later, I'll use the code arrangement in Listing 3 to simulate an exception scenario of division by zero just to see what happens in JNA.

Let's quickly go through the main points about the helloWorld() function. First of all, the extern C is used to avoid C++ name decoration. This means that the function can be externally invoked as helloWorld() without needing any special characters added to the name. Next, the __declspec(dllexport) tag serves to export the function from the DLL. The rest of the function definition is simply the return value, function name, and parameters. This is followed by the code for the function.

There's just one last thing to note about building the DLL, and this took me a few hours to find -- the calling convention. Make sure this is set to __cdecl. In Visual C++ Express Edition, you set the calling convention at the project configuration level in the C++ Advanced section.

When all of the above steps are complete, you can build the project to produce your DLL. In my case, the DLL is called nativecode.dll. This DLL is included with the source code of this article. Let's now execute the DLL code via JNA.

Invoking the DLL Code from Java

Listing 4 illustrates the code to invoke the DLL function.

public interface CLibrary extends Library {

     CLibrary INSTANCE1 = (CLibrary)
      Native.loadLibrary((Platform.isWindows() ? "nativecode" : "c"),
                           CLibrary.class);

     int helloWorld(int divider);
}

public static void main(String[] args) {
   CLibrary.INSTANCE1.helloWorld(77));
}

Listing 4. Calling the DLL function

In Listing 4, an instance of CLibrary is created. This object allows the specified DLL to be loaded. Following the library loading procedure, the required symbols from the library are mapped -- in the case of Listing 4, there is just one symbol, called helloWorld().

Listing 5 illustrates the program output from the code in Listing 4.

C:\jnacode>java HelloWorld
Value is 1

Listing 5. Invocation of the DLL code

Nothing too amazing in Listing 5 -- the value 77 is passed into the function. The parameter (77) is then divided by 77 inside the function to yield the answer: 1.

When I was trying to figure out the DLL issue related to the calling convention, I wanted to look inside the generated DLL. Fortunately, you can take a peek inside the DLL with a tool called Dependency Walker. To do this, just download a free copy of Dependency Walker, open it, and load the DLL into it. You should see something similar to Figure 1.

Figure 1 The internals of the DLL

Figure 1. The internals of the DLL

Notice the function name in Figure 1 matches the name of the DLL symbol helloWorld(). Just for the record, if you use the standard calling convention when you build a DLL, the function name will look like that illustrated in Figure 2.

Figure 2. Standard calling convention

Notice the way the function name has changed. Now, if you attempt to run the Java program, you'll get the annoying error illustrated in Listing 6.

C:\jnacode>java HelloWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function 'helloWorld': The specified procedure could not be found.

   at com.sun.jna.Function.(Function.java:129)
   at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:250)
      at com.sun.jna.Library$Handler.invoke(Library.java:191)
      at $Proxy0.helloWorld(Unknown Source)
      at HelloWorld.main(HelloWorld.java:31)

Listing 6. Linker error

I included the above error situation because I encountered it myself. So, that's a fully worked example of JNA. How does this relate to JNI -- the predecessor of JNA?

JNI: Not Today, Thanks!

I've often thought that JNI has taken a lot of criticism over the years. In many cases, the first thing a software support organization will ask is: "Are you using JNI?" If the answer is yes, then in many cases no support is forthcoming. On the other hand, in my consultancy work I have seen cases of JNI in use between large Java and C++ code bases. In one such scenario, the Java and C++ code passed all unit and integration tests but the production code crashed every time. In such cases, the Java and C++ programmers tend to run for cover, each blaming the other side.

A better solution to such JNI woes is to run through a set of checklist items for the code on both sides. For example, in the C code:

  • Is an array boundary being exceeded?
  • Is a null pointer being de-referenced?
  • Is dynamic memory being correctly de-allocated?

JNA can potentially end this type of scenario.

What About Library Licensing Issues?

An interesting aspect of JNA is that it opens up the area of direct Java access to DLL code. This also applies to other library technology, such as shared Unix libraries. What does this mean for licensed software components? The use of JNA might conceivably be used to access licensed library code from Java. Another aspect of this issue is that JNA code could allow access to security-restricted library code.

In addition to the above considerations, being able to access legacy code also opens up the possibility of exceptions in the interface between Java and the legacy world. Let's look at this now.

When Things Go Wrong in the Legacy Code Called via JNA

Recall the code from Listing 3, where I divide a parameter into a constant value. What happens if I pass in a zero and cause a divide-by-zero exception?

public interface CLibrary extends Library {

     CLibrary INSTANCE1 = (CLibrary)
      Native.loadLibrary((Platform.isWindows() ? "nativecode" : "c"),
                           CLibrary.class);

     int helloWorld(int divider);
}

public static void main(String[] args) {
   CLibrary.INSTANCE1.helloWorld(77));
   System.out.println("Value: " + CLibrary.INSTANCE1.helloWorld(0));
}

Listing 7. Passing in a zero to cause an exception

When I execute the code in Listing 7, I get the program output illustrated in Listing 8.

C:\jna_article\jnacode>java HelloWorld
Value is 1
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_INT_DIVIDE_BY_ZERO (0xc0000094) at pc=0x009b1365, pid=1768, tid=458
4
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_17-b04 mixed mode)
# Problematic frame:
# C  [nativecode.dll+0x11365]
#
# An error report file with more information is saved as hs_err_pid1768.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp

Listing 8. Trouble in paradise: a divide-by-zero exception

It's not pretty, but we do get some sort of trace output. However, scaling things up into a real-world example, imagine trying to track this down in a large codebase. In summary, JNA is really powerful but as with any power, we must exercise responsibility.

Running the Supplied Code and CLASSPATH Issues

It's not too difficult to get up and running with JNA -- just make sure you follow a few simple guidelines. I've included my custom-built DLL and the compiled Java code as part of the source code .zip file. You should be able to run the code on Windows XP without the need to compile anything.

To run the code, just unzip the source files into a folder such as C:\jnacode. The following step is optional, because the source files include a copy of jna.jar. If you prefer to get your own copy of jna.jar, or if a new version has been released, then download a copy of the file jna.jar. To keep things simple, place the jna.jar file in the above folder. Then, make sure jna.jar and the containing folder are both in the CLASSPATH. After this, you should be able to run the HelloWorld class.

If you're feeling adventurous and/or energetic, you might also like to try building your own DLL, as described earlier. Just follow the steps as described and you should be just fine. Just in case you encounter any problems, have a look at the JNA website: jna.dev.java.net.

Conclusion: How does JNA Stack Up?

I like JNA a lot. It's pretty easy to use. If your library code is built the right way, you shouldn't experience too many problems. JNA is likely to be of tremendous assistance to organizations with large amounts of complex, business-critical legacy code. No more pesky JNI mapping and header file auto-generation.

It is important to remember that it is relatively easy to get into trouble with any new technology, and JNA is no exception. For this reason, if JNA is used in a disciplined manner, there is no reason why it shouldn't overtake JNI and expensive proprietary bridging mechanisms. What form might such discipline take? Well, any calls into JNA resources (i.e., library code) should be fully logged to provide an audit trail. Aspect-oriented techniques suggest themselves for this type of scenario. For example, EJB3 interceptors could be used to ensure effective logging of JNA code.

In addition, any data passed into a native library (via JNA code) should be carefully validated. I reckon the key to JNA success is to proceed slowly.

Resources

References


width="1" height="1" border="0" alt=" " />
Stephen B. Morris is an independent writer/consultant based in Ireland.
Related Topics >> Programming   |   

Comments

What about other direction?

We have an existing JNI project, and this article on JNA looks very interesting. However, this article only talks about calls from Java to native code, whereas most of our interaction is in the opposite direction: native C++ code using JNI to call Java code. Is this possible with JNA?

What about other direction?

Hi there and thanks for the feedback. That's a good question about the call direction. I think JNA is currently only supporting calls from Java. Maybe post a question to the project? How reliable do you find JNI in general? Best wishes Stephen

What about other direction?

If you mean registering a callback function that's implemented in Java to be invoked by native code, then JNA will allow that. If you're talking about instantiating a VM via the JNI Instantiation interface in C and then calling into the VM, not so much.

JNA is built on top of JNI

The article makes it seem like JNA is independent of JNI.. But JNA is just built on top of JNI. In the jna.jar there is a native library with the JNI glue that makes it all possible.

IT certification

I like this post

UnsatisfiedLinkError

Keep getting: Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'nativecode': This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem. I tried: java HelloWorld (as he suggested) java -Djna.library.path=. -cp .;jna.jar HelloWorld ...same effect Tried running in Eclipse, loaded DLL as System.load("c:/tmp/nativecode.dll") in main() ...doesn't work What am I doing wrong?