With the addition of Java Native Access (JNA) to JRuby, systems programmers using JRuby now have greater flexibility in terms of interfacing with underlying operating system.
Some Ruby users are familiar with the ‘Win32API’ library that ships as part of the Ruby standard library. That library lets you interface with the Windows API by defining function pointers from specific DLL’s that you later call. With JRuby’s JNA interface you can now interface with Windows in a similar fashion.
The first thing we need to do is require the ‘java’ library.
With that out of the way, we’ll then want to get an instance of the DLL we need to export functions from. I’ll use Kernel32 for our purposes.
Kernel32 = com.sun.jna.NativeLibrary.getInstance('kernel32')
If you’re familiar with the Windows API, we’ve effectively called the LoadLibrary() function which maps the kernel32 Windows module into our address space, wrapped by our ‘Kernel32′ variable. From here we can define any functions that the kernel32.dll exports. For demonstration purposes I’ll define the GetCurrentDirectoryA() function:
GetCurrentDirectoryA = Kernel32.getFunction('GetCurrentDirectoryA')
This effectively calls the Windows GetProcAddress() function behind the scenes, returning a function pointer address, wrapped in our ‘GetCurrentDirectoryA’ variable.
The Nitty Gritty
Now that we have a function pointer, we want to call it. This is trickier than the standard Win32API library for two reasons. First, you must know the return type of the GetCurrentDirectoryA() function in order to know how to properly invoke it. I know from looking at the documentation that it returns a DWORD, which is a 32 bit number1. Looking at the JNA documentation2 we can see there’s an invokeInt() function. Let’s try it.
buf = 0.chr * 256 num = GetCurrentDirectoryA.invokeInt(buf.length, buf) puts buf.strip
Oops! That won’t work because in Java strings are immutable. We have to convert our Ruby buffer to a ByteBuffer first, then convert it back to a regular Ruby string once we’re ready using the String#from_java_bytes method. Also, the invokeInt() function takes a Java array. We need to explicitly coerce the Ruby array using the .to_java method.3 Armed with this knowledge, let’s try again:
buf = java.nio.ByteBuffer.allocate(256) num = GetCurrentDirectoryA.invokeInt([256, buf].to_java) buf = String.from_java_bytes(buf.array) puts buf.strip
That will return a string like ‘C:\Documents and Settings\some_user’, or whatever your current directory happens to be.
That’s all for now folks. In the future I plan to publish a JRuby gem of the win32-api library. Stay tuned.4
1I originally said invokeLong() here, but it was pointed out that a 32 bit unsigned long is considered an integer in Java.
2The JNA home page is at https://jna.dev.java.net/
3You can find more information at http://www.coffee-bytes.com/node/558
4I have a preliminary version in CVS. You can find the win32-api library at http://www.rubyforge.org/projects/win32utils.