4

I would like to have the conversion made by JNA automatically. Right now I'm following the solution from the second answer in a very similar question and JNA's own EnumConverter utility class. There's one crucial difference, my enum has a constructor argument.

My code defining the TypeConverter:

public class SentinelStatusConverter implements TypeConverter {
    @Override
    public SentinelStatus fromNative(Object nativeValue, FromNativeContext context) {
        Integer code = (Integer) nativeValue;
        return SentinelStatus.fromCode(code);
    }

    @Override
    public Integer toNative(Object value, ToNativeContext context) {
        SentinelStatus status = (SentinelStatus) value;
        return Integer.valueOf(status.getCode());
    }

    @Override
    public Class<Integer> nativeType() {
        return Integer.class;
    }
}

public class SentinelTypeMapper extends DefaultTypeMapper {
    public SentinelTypeMapper() {
        addTypeConverter(SentinelStatus.class, new SentinelStatusConverter());
    }
}

Here's code directly registering the native C library along with my custom TypeMapper. C functions return an int which I want to map automatically into a SentinelStatus enum:

public class SentinelLibrary {
    static {
        Map<String, Object> options = new HashMap<String, Object>();
        options.put(Library.OPTION_TYPE_MAPPER, new SentinelTypeMapper());
        Native.register(NativeLibrary.getInstance("libnamelib", options));
    }

    public static native SentinelStatus hasp_get_sessioninfo(
        NativeLong sessionHandle,
        String query,
        PointerByReference info);
}

SentinelStatus is an enum like so:

public enum SentinelStatus {
    HASP_STATUS_OK(0),
    HASP_SOME_ERROR(13),
    ...
    HASP_NOT_IMPL(1831);

    private final int code;

    SentinelStatus(final int code) { this.code = code; }

    public int getCode() { return this.code; }

    public static SentinelStatus fromCode(final int code) {
        for (SentinelStatus status : EnumSet.allOf(SentinelStatus.class)) {
            if (code == status.getCode()) {
                return status;
            }
        }
        return SentinelStatus.HASP_NOT_IMPL;
    }
}

With this JNA mapping and converter I get an error whenever I try to load the SentinelLibrary class:

java.lang.ExceptionInInitializerError
...
Caused by: java.lang.IllegalArgumentException: Unsupported Structure field type class package.name.SentinelStatus
at com.sun.jna.Structure$FFIType.get(Structure.java:1851)
at com.sun.jna.Structure$FFIType.get(Structure.java:1806)
at com.sun.jna.Native.register(Native.java:1438)
at com.sun.jna.Native.register(Native.java:1165)
at package.name.SentinelLibrary.<clinit>(line with Native.register() call)

I've read the documentation and there weren't any restriction as to the mapped class or type. Only the NativeMapped interface required the implementer to provide a public no-argument constructor.

Is it possible to map a C integer to an enum this way?

UPDATE: After further rummaging through JNA code, I've added this field to the SentinelStatus enum:

public final static TypeMapper TYPE_MAPPER = new SentinelTypeMapper();

Now SentinelLibrary gets loaded without errors. But all methods returning the enum, return null with error printed to stderr:

JNA: unrecognized return type, size 4
Community
  • 1
  • 1
Tomasz Cudziło
  • 2,462
  • 1
  • 17
  • 20

1 Answers1

2

Most likely you are defining your Structure outside the context of your library definition (which is where the TypeMapper information is stored).

You can either explicitly set the type mapper on your Structure class, or define your Structure class within your native Library class. If there is no TypeMapper explicitly defined for a Structure, it will fall back to the options provided by any surrounding Library class.

public class MyStructure extends Structure {
    public MyStructure() {
        setTypeMapper(new MyTypeMapper());
    }
}

Alternatively,

public interface MyLibrary extends Library {
    public class MyStructure extends Structure {
    }
}

Or with direct mapping:

public class MyLibrary implements Library {
    { Native.register(...); }
    public class MyStructure extends Structure { }
}

When a Structure class fails to find an explicitly-set type mapper, it looks for enclosing class of type Library, and attempts to look up the options used when instantiating that class. These options get cached when the native library is loaded, so the are available via key lookup using the Library subclass as key. You'll need to implement the Library interface so that your direct-mapped library is recognized as a native library class.

EDIT

This is a bug in JNA's type mapper handling (see project issue). This seems to be restricted to direct-mapped libraries and enum type mapping.

EDIT

There are several JNA bugs at work here, a fix is available if you'd like to try it. This issue is isolated to type mappers on direct-mapped libraries where a primitive is being converted to a Java object.

technomage
  • 9,861
  • 2
  • 26
  • 40
  • Thanks for the swift answer. It explains why my `SentinelStatus` may not be recognised when `register`ing the library. But the problem still exists. `SentinelStatus` which I want to map to is an `enum`. I can't really make it a subclass of a `Structure`. – Tomasz Cudziło Jul 15 '15 at 13:35
  • `SentinelStatus` is being used as a field within a `Structure`, according to your error. It's the `Structure` container that needs to be able to find the type mapper. – technomage Jul 15 '15 at 15:22
  • Its also possible that the library caching happens after the `Structure` lookup happens, in which case you'd need to follow the first example and set the type mapper on the structure instance explicitly. It'd help if you included more of your library definition, particularly those bits that include the `Structure` usage. – technomage Jul 15 '15 at 15:24
  • `SentinelStatus` enum is actually only used as a return type of `native` methods from `SentinelLibrary` (as can be seen at the end of the first code block). I'm not using any `Structure` subclasses anywhere. – Tomasz Cudziło Jul 15 '15 at 15:26
  • Then there's a bug in the JNA error reporting, apparently. – technomage Jul 15 '15 at 15:27