如何使用 TypeConverter 通过 JNA 将 C 整数映射到 Java 枚举?

How to map C integer to Java enum via JNA using TypeConverter?

我希望 JNA 自动进行转换。现在我正在遵循第二个答案 in a very similar question and JNA's own EnumConverter 实用程序 class 的解决方案。有一个关键区别,我的枚举有一个构造函数参数。

我的代码定义 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());
    }
}

这是直接注册本机 C 库以及我的自定义 TypeMapper 的代码。 C 函数 return 一个 int 我想自动映射到 SentinelStatus 枚举:

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 是一个 enum 像这样:

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;
    }
}

使用这个 JNA 映射和转换器,每当我尝试加载 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)

我已经阅读了文档,对于映射 class 或类型没有任何限制。只有 NativeMapped 接口要求实现者提供 public 无参数构造函数。

是否可以通过这种方式将 C 整数映射到枚举?

更新: 在进一步翻阅 JNA 代码后,我将此字段添加到 SentinelStatus 枚举中:

public final static TypeMapper TYPE_MAPPER = new SentinelTypeMapper();

现在 SentinelLibrary 加载时没有错误。但是所有方法 returning 枚举,return null 错误打印到 stderr:

JNA: unrecognized return type, size 4

很可能您在库定义的上下文之外定义了 Structure(这是存储 TypeMapper 信息的地方)。

您可以在您的 Structure class 上显式设置类型映射器,或者在您的原生 Library [=51] 中定义您的 Structure class =].如果没有为 Structure 显式定义 TypeMapper,它将退回到任何周围 Library class.

提供的选项
public class MyStructure extends Structure {
    public MyStructure() {
        setTypeMapper(new MyTypeMapper());
    }
}

或者,

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

或直接映射:

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

Structure class 找不到显式设置的类型映射器时,它会查找包含 Library 类型的 class,并尝试查找实例化 class 时使用的选项。这些选项在加载本机库时会被缓存,因此可以使用 Library subclass 作为键通过键查找获得这些选项。您需要实现 Library 接口,以便您的直接映射库被识别为本机库 class.

编辑

这是 JNA 类型映射器处理中的错误(请参阅 project issue)。这似乎仅限于直接映射库和 enum 类型映射。

编辑

这里有几个 JNA 错误,如果您想尝试,请 fix is available。此问题与直接映射库上的类型映射器隔离,在该库中,基元被转换为 Java 对象。