Search code examples
javawindowsmacosjnamultiplatform

how to isolate JNA bindings that are platform specific?


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

  • either porting the native feature with other JNA binding
  • or simply disable the existing Windows kernel32 binding when running on another platform as it is just convenient but not mandatory helpers.

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?


Solution

  • The answer is really a general software development strategy.

    1. Identify the API that your client code needs

      In this case, it might be MailsSlot.sendMessage(int destID, String msg)

    2. Abstract the implementation details behind that API

      public interface MailSlot { void sendMessage(int destId, String msg); }

    3. 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.