Search

Thursday 17 January 2013

Java JNA Win32: when default libraries are not enough

I've been working on implementing Win32 interaction functionality using JNA library and encountered the situation when the library became incomplete. The methods of com.sun.jna.platform.win32.User32 interface which is supplied by the platform library contained only subset of the entire functions of user32.dll library. Actually, I've found that while trying to call GetMenu function. It appeared that there's no such function in the Java class supplied with the JNA library. What to do with that? How to make a Java call for functions from existing DLL? Well, JNA provides all necessary interfaces for that. The default class didn't contain some functions as it would become very big while the functions are rarely used. So, actually, we should do 2 steps:

  1. Creating Java interface for dll functions
  2. Add missing data types
So, let's see how it's done with Java.

Creating Java interface for dll functions

First of all we should create interface which extends com.sun.jna.Library interface or some of it's extensions. For instance:

import com.sun.jna.Native;
import com.sun.jna.Library;

public interface User32Ext extends Library {

}
In my case I just wanted to extend existing import com.sun.jna.platform.win32.User32 interface. So, the interface looks like:
import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;

public interface User32Ext extends User32 {

}
Once it's done we should create the INSTANCE field which should contain the actual callable instance with current interface so we can use it while calling the API. So, the code is updated like:
import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.win32.W32APIOptions;

public interface User32Ext extends User32 {

 User32Ext INSTANCE = (User32Ext) Native.loadLibrary("user32.dll",
   User32Ext.class, W32APIOptions.DEFAULT_OPTIONS);
}
Note that the DLL is loaded during instance initialization. After that we can add methods we want to use from the loaded DLL. Changes look like:
import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.win32.W32APIOptions;

public interface User32Ext extends User32 {

 User32Ext INSTANCE = (User32Ext) Native.loadLibrary("user32.dll",
   User32Ext.class, W32APIOptions.DEFAULT_OPTIONS);

 public HMENU GetMenu(HWND hWnd);
}
That's actually it! Now, I can write the code which uses the above interface and it's method:
User32Ext user32 = User32Ext.INSTANCE;
user32.GetMenu(hwnd);
It's not necessary to define all the functions from the DLL. The only ones which are defined will be in use.

Add missing data types

Unfortunately, in some cases we have to use some structures which aren't defined in the JNA library. E.g. we were encouraged with the recent success in calling menu functionality so we decided to add GetMenuInfo function which suddenly accepts the pointer to the MENUINFO structure. And this structure wasn't defined anywhere. For this purpose I defined the WinDefExt interface which extends the com.sun.jna.platform.win32.WinDef interface and defined missing type in the following form:

import java.util.Arrays;
import java.util.List;

import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT.HANDLE;

public interface WinDefExt extends WinDef {
 public class MENUINFO extends Structure {
  public int cbSize = size();
  public DWORD fMask;
  public DWORD dwStyle;
  public DWORD cyMax;
  public HBRUSH hbrBack;
  public DWORD dwContextHelpID;
  public ULONG_PTR dwMenuData;

  protected List getFieldOrder() {
   return Arrays.asList(new String[] { "cbSize", "fMask", "dwStyle",
     "cyMax", "hbrBack", "dwContextHelpID", "dwMenuData" });
  }
 }
}
One more thing to be mentioned here is that we also define the sequence of the structure fields. It's necessary for proper data processing from native code to Java.

Well, sometimes types contain some fields of other complex types which also require definition. In our example it's the HBRUSH type which is undefined here. Generally, we should simply repeat the procedure for missing type. E.g. for HBRUSH the updates would be:

import java.util.Arrays;
import java.util.List;

import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT.HANDLE;

public interface WinDefExt extends WinDef {
 
 public static class HBRUSH extends HANDLE {
  public HBRUSH() {
   super();
  }
  
  public HBRUSH(Pointer p) {
   super(p);
  }
 }
 
 public class MENUINFO extends Structure {
  public int cbSize = size();
  public DWORD fMask;
  public DWORD dwStyle;
  public DWORD cyMax;
  public HBRUSH hbrBack;
  public DWORD dwContextHelpID;
  public ULONG_PTR dwMenuData;

  protected List getFieldOrder() {
   return Arrays.asList(new String[] { "cbSize", "fMask", "dwStyle",
     "cyMax", "hbrBack", "dwContextHelpID", "dwMenuData" });
  }
 }
}
This procedure can be repeated as many times as it's needed. Anyway at the end all the complex types are actually the combination of primitive types. So, it should end at some point.

Summary

During the work with JNA library I'll have to use a lot of other functionality which isn't defined in the default libraries. So, I always should be able to extend existing libraries. At the same time it's not convenient to write my own wrapper DLL for that. I've found simpler way for this and I described that in this post.

No comments:

Post a Comment