Before JNA: JNI
If you’re into the Java world, you’ve probably heard of JNI: Java Native Interface.
It’s used to call the native functions of the system or of any native library.
Some good JNI explanations and examples here: http://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html
Most of developers will never use it because it’s not often necessary to access the system resources, the windows, the volumes etc. That really depends of your business.
Sometimes, you want to use a library that’s not written in Java but in C. It’s very performant and battle-tested, you need to create a bridge. This is where JNI and JNA comes into play.
About resources, Java provides already some high-level API for some system aspects (memory, disks), such as:
- Runtime.getRuntime().maxMemory()
- Runtime.getRuntime().availableProcessors()
- File.listRoots()(0).getFreeSpace()
But it’s pretty limited. Behind the scene, they are declared as native and rely on JNI.
You can use some projects that offers more options, such as oshi (Operating System & Hardware Information). It makes all possible information on the OS and hardware of the machine available (all memory and cpu metrics, network, battery, usb, sensors..).
It’s not using JNI: it’s using JNA!
JNA is JNI’s cousin: created to be simpler to use, and to write only Java code. (Scala in our case :) Note that there is a slight call overhead compared to JNI because of the dynamic bindings.
JNA
Basically, it dynamically links the functions of the native library to some functions declared in a Java/Scala interface/trait. Nothing more.
The difficulty comes with the signature of the functions you want to “import”.
You can easily find their native signatures (Google is our friend), but it’s not always obvious to find how to translate them using the Java/Scala types.
Hopefully, the documentation of JNA is pretty good to understand the subtle cases : Using the library, FAQ.
Let’s review how to use it using Scala and SBT (instead of Java).
How to use it
First, SBT:
libraryDependencies ++= Seq( "net.java.dev.jna" % "jna" % "4.2.2", "net.java.dev.jna" % "jna-platform" % "4.2.2")
The “jna” dependency is the core.
“jna-platform” is optional. It contains a lot of already written interfaces to access some standard libraries on several systems (Windows (kernel32, user32, COM..), Linux (X11), Mac). If you plan to use any system library, check out this package first.
Then, the Scala part.
Use the existing platform bindings
With jna-platform, you can use the existing bindings:
import com.sun.jna.platform.win32.Kernel32 import com.sun.jna.ptr.IntByReference val cn = new Array[Char](256) val success: Boolean = Kernel32.INSTANCE.GetComputerName(cn, new IntByReference(256)) println(if (success) Native.toString(cn) else Kernel32.INSTANCE.GetLastError())
You can feel the native way when calling this function (most native functions follows this style):
- you provide a buffer and its length
- you get a boolean as result to indicate success/failure
- in case of a failure, you call to know the code of the error (such as 111 for )
- in case of a success, the buffer contains the name
That’s very manual but that’s the way. (nowadays, we would return the String and throw an Exception on failure)
For information, the native signature is :
BOOL WINAPI GetComputerName( _Out_ LPTSTR lpBuffer, _Inout_ LPDWORD lpnSize);
A pointer to some buffer to write into and its size (use as input and as output).
Listing the opened windows
Another more complex example to retrieve the list of opened windows :
import com.sun.jna.platform.win32.{User32, WinUser} User32.INSTANCE.EnumWindows(new WinUser.WNDENUMPROC { override def callback(hWnd: HWND, arg: Pointer): Boolean = { val buffer = new Array[Char](256) User32.INSTANCE.GetWindowText(hWnd, buffer, 256) println(s"$hWnd: ${Native.toString(buffer)}") true } }, null)
Output:
native@0xb0274: JavaUpdate SysTray Icon native@0x10342: GDI+ Window native@0x10180: Windows Push Notifications Platform (a lot more)...
The native signature of is :
BOOL WINAPI EnumWindows( _In_ WNDENUMPROC lpEnumFunc, _In_ LPARAM lParam);
- we use User32 because it contains the windows functions of Windows
- a WNDENUMPROC is a pointer to a callback. JNA already has an interface of the same name to be able to create this type in the JVM.
- we call another function of User32 in get the title of each window
Create a custom binding
It’s time to fly with our own wings.
Let’s call a famous function of the Windows API: MessageBox
. You know, the popups? It’s in User32.lib
but JNA did not implemented it. Let’s do it ourselves.
First, we create an interface with the Java/Scala signature of the which is :
int WINAPI MessageBox( _In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText, _In_opt_ LPCTSTR lpCaption, _In_ UINT uType);
The Scala equivalence could be:
import com.sun.jna.Pointer import com.sun.jna.win32.StdCallLibrary trait MyUser32 extends StdCallLibrary { def MessageBox(hWnd: Pointer, lpText: String, lpCaption: String, uType: Int) }
- We use simple Strings and not Array[Char] because they are only used as inputs (_In_).
- The name of the function must be exactly the native’s one (with caps)
Now, we need to instantiate the interface with JNA and call our function:
val u32 = Native.loadLibrary("user32", classOf[MyUser32], W32APIOptions.UNICODE_OPTIONS).asInstanceOf[MyUser32] val MB_YESNO = 0x00000004 val MB_ICONEXCLAMATION = 0x00000030 u32.MessageBox(null, "Hello there!", "Hi", MB_YESNO | MB_ICONEXCLAMATION)
- Always use
W32APIOptions.UNICODE_OPTIONS
or you’ll get into troubles when calling functions (that will automatically convert the input/output of the calls)
It was quite simple right? That’s the purpose of JNA. Just need an interface with the native method declaration, you can call it.
The difficulty could be to write the Java signature, but a tool can help: JNAerator. From the native language, it can generate Java signatures, pretty cool!
More examples of JNA usage on their github’s: https://github.com/java-native-access/jna/tree/master/contrib