I have a project where I defined a JNA wrapper to Windows kernel32 library, upon which I have made several helpers that are not critical for the project but increase the integration to the platform (namely: system debug logging with OutputDebugString
+ DebugView and Mailslot messaging features).
Here is my JNA defines:
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
public interface JnaKernel32 extends StdCallLibrary {
//StdCall is needed for lib kernel32
@SuppressWarnings("unchecked")
Map ASCII_OPTIONS = new HashMap(){
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.ASCII);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.ASCII);
}
};
@SuppressWarnings("unchecked")
Map UNICODE_OPTIONS = new HashMap(){
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
}
};
Map DEFAULT_OPTIONS = Boolean.getBoolean("w32.ascii") ? ASCII_OPTIONS : UNICODE_OPTIONS;
JnaKernel32 INSTANCE = (JnaKernel32) Native.loadLibrary("kernel32", JnaKernel32.class, DEFAULT_OPTIONS);
//some system defines
//...
}
And the Mailslot definition:
public class Mailslot {
static JnaKernel32 kernel32 = JnaKernel32.INSTANCE;
boolean localMailslot = false;
int lastError = 0;
private int hMailslot = JnaKernel32.INVALID_HANDLE_VALUE;
//...
}
And in some places I have also
static JnaKernel32 kernel32 = JnaKernel32.INSTANCE; //to call OutputDebugString
//...
kernel32.OutputDebugString("some debug message");
My concern is that project could be used also on GNU/Linux or MacOS X but obviously the Native.loadLibrary
fails at runtime if executed on e.g. OSX.
I am considering about
How can I isolate the platform-specific features and calls that are made? I was thinking about moving the JNA part to a runtime loaded plugin perhaps?
The answer is really a general software development strategy.
Identify the API that your client code needs
In this case, it might be MailsSlot.sendMessage(int destID, String msg)
Abstract the implementation details behind that API
public interface MailSlot {
void sendMessage(int destId, String msg);
}
Provide one or more concrete implementations to meet the API contract
public class Win32MailSlot implements MailSlot {
public void sendMessage(int destId, String msg) {
// Do stuff here that's windows-specific
}
}
public class OSXMailSlot implements MailSlot {
public void sendMessage(int destId, String msg) {
// Do stuff here that's windows-specific
}
}
Choose the appropriate implementation at runtime:
MailSlot mslot = Platform.IS_WINDOWS ? new Win32MailSlot() : new OSXMailSlot();
After a few implementations, you may find some duplicated code, which you might then refactor into an abstract base class shared among the platform-specific implementations.
See the JNA platform FileUtils
class for an example of such an strategy.