JNA进阶应用,自定义native类型

问题的提出

经常有一些C/C++库,会自定义一些在不同平台上 长度不同的native类型,例如我之前遇到的8字节的enum问题,MvPixelType枚举类型,在windows平台是4字节整型,在Linux平台意外变成了8字节长整型,这种时候JNA默认给枚举分配的4字节就不合适了,需要通过某种方式指导JNA根据平台类型读写相应长度的内存

选择哪种方式解决

目前有2种方式

替换JNA默认的TypeMapper

介绍TypeMapper前,需要先介绍TypeConverter。所谓TypeConverter,就是这样一个converter,它既能将一种Java数据类型,转换成对应的native数据类型,又能将该上述的native数据类型,转换成相应的Java数据类型。

TypeConverter接口继承了2个接口(Java类不允许多继承,但接口允许),分别是FromNativeConverter(从native内存读取出java对象)和ToNativeConverter(将Java对象写入native内存),其源码见下:

public interface TypeConverter extends FromNativeConverter, ToNativeConverter {}

public interface FromNativeConverter {

    /** Convert the given native object into its Java representation using

     * the given context.

     */

    Object fromNative(Object nativeValue, FromNativeContext context);

    /** Indicate the native type used by this converter. */

    Class<?> nativeType();

}
public interface ToNativeConverter {

    /**

     * Convert a Java type to an appropriate native type. The new type

     * must be one of the following classes:

     * <ul>

     * <li>{@link Pointer}

     * <li>Boolean

     * <li>Byte

     * <li>Short

     * <li>Character

     * <li>Integer

     * <li>{@link NativeLong}

     * <li>Long

     * <li>Float

     * <li>Double

     * <li>{@link Structure}

     * <li>String

     * <li>{@link WString}

     * <li>{@link java.nio.Buffer} (unsupported in direct mode)

     * <li>primitive array (unsupported in direct mode)

     * </ul>

     */

    Object toNative(Object value, ToNativeContext context);

    /** Indicate the type expected from {@link #toNative}. */

    Class<?> nativeType();

}

TypeMapper,就是覆盖所有native类型的TypeConverter的集合,它内部维护着2张表,分别是FromNativeConverter列表和ToNativeConverter列表,维护两张表的好处是某些应用场景只有读取或只有写入,这样就能屏蔽掉无关内容,防止误操作。当然,对于既有读又有写的场景,就会将TypeConverter对象在上述两张表里分别注册一次。

/** Provides converters for conversion to and from native types. */
public interface TypeMapper {
    /** Return the {@link FromNativeConverter} appropriate for the given Java class.
     * @param javaType Java class representation of the native type.
     * @return Converter from the native-compatible type.
     */
    FromNativeConverter getFromNativeConverter(Class<?> javaType);

    /** Return the {@link ToNativeConverter} appropriate for the given Java class.
     * @param javaType Java class representation of the native type.
     * @return Converter to the native-compatible type.
     */
    ToNativeConverter getToNativeConverter(Class<?> javaType);
}

注册、查询等具体操作见JNA默认实现的DefaultTypeMapper

public class DefaultTypeMapper implements TypeMapper {
    private static class Entry {
        public Class<?> type;
        public Object converter;
        public Entry(Class<?> type, Object converter) {
            this.type = type;
            this.converter = converter;
        }
    }

    private List<Entry> toNativeConverters = new ArrayList<Entry>();
    private List<Entry> fromNativeConverters = new ArrayList<Entry>();


    /** Add a {@link ToNativeConverter} to define the conversion into a native
     * type from arguments of the given Java type.  Converters are
     * checked for in the order added.
     * @param cls Java class requiring conversion
     * @param converter {@link ToNativeConverter} to transform an object of
     * the given Java class into its native-compatible form.
     */
    public void addToNativeConverter(Class<?> cls, ToNativeConverter converter) {
        toNativeConverters.add(new Entry(cls, converter));
        Class<?> alt = getAltClass(cls);
        if (alt != null) {
            toNativeConverters.add(new Entry(alt, converter));
        }
    }
    /**
     * Add a {@link FromNativeConverter} to convert a native result type into the
     * given Java type.  Converters are checked for in the order added.
     *
     * @param cls Java class for the Java representation of a native type.
     * @param converter {@link FromNativeConverter} to transform a
     * native-compatible type into its Java equivalent.
     */
    public void addFromNativeConverter(Class<?> cls, FromNativeConverter converter) {
        fromNativeConverters.add(new Entry(cls, converter));
        Class<?> alt = getAltClass(cls);
        if (alt != null) {
            fromNativeConverters.add(new Entry(alt, converter));
        }
    }

    /**
     * Add a {@link TypeConverter} to provide bidirectional mapping between
     * a native and Java type.
     *
     * @param cls Java class representation for a native type
     * @param converter {@link TypeConverter} to translate between native and
     * Java types.
     */
    public void addTypeConverter(Class<?> cls, TypeConverter converter) {
        addFromNativeConverter(cls, converter);
        addToNativeConverter(cls, converter);
    }

    private Object lookupConverter(Class<?> javaClass, Collection<? extends Entry> converters) {
        for (Entry entry : converters) {
            if (entry.type.isAssignableFrom(javaClass)) {
                return entry.converter;
            }
        }
        return null;
    }

    @Override
    public FromNativeConverter getFromNativeConverter(Class<?> javaType) {
        return (FromNativeConverter)lookupConverter(javaType, fromNativeConverters);
    }

    @Override
    public ToNativeConverter getToNativeConverter(Class<?> javaType) {
        return (ToNativeConverter)lookupConverter(javaType, toNativeConverters);
    }
}

具体的用法参见src/com/sun/jna/win32/W32APITypeMapper.java

因为每个JNA实例只能有一个TypeMapper,所以想改变enum的行为,只能是定义DefaultTypeMapper的子类,然后在Library实例化时设置子类为生效的TypeMapper
如果工程里还有其他C/C++库,而其他C/C++库在Linux平台下enum的长度又是正常的4字节,则无法使用替换TypeMapper方式,因为没法两全其美。
更好的方式,是最小化特殊case的影响,即下面这种

让目标类型实现NativeMapped接口

一般结构体都会拆分成JNA识别的Native类型并被DefaultTypeMapper转换,如果某个类型你不想让DefaultTypeMapper转换,而是想自己处理,则需要让该数据类型实现NativeMapped接口,这样JNA遇到你的自定义类型时,会将其看作Native类型,不再做进一步的拆分,而是调用你提供的converter,将其转换成native,反之亦然

我的问题就是用这种方式解决的,不过用arm指代Linux(大家可以将os.arch改成os.name来判定是否为Linux),具体见代码:

import com.sun.jna.FromNativeContext;
import com.sun.jna.NativeMapped;

public class MvGvspPixelType implements NativeMapped {
    private static String os_arch;
    private Long value;
    static {
        os_arch = System.getProperty("os.arch");
    }

    public MvGvspPixelType() {} // must exist!

    private MvGvspPixelType(Long value){
        this.value = value;
    }

    @Override
    public MvGvspPixelType fromNative(Object o, FromNativeContext fromNativeContext) {
        if (os_arch.equals("arm")) {
            return new MvGvspPixelType(Long.valueOf((long) o));
        } else {
            return new MvGvspPixelType(Integer.toUnsignedLong((int) o));
        }
    }

    @Override
    public Object toNative() {
        if (os_arch.equals("arm")) {
            return this.value;
        } else {
            return this.value.intValue();
        }
    }

    @Override
    public Class<?> nativeType() {
        if (os_arch.equals("arm")) {
            return Long.class;
        } else {
            return Integer.class;
        }
    }
}

注意,数据类型必须提供一个无参的、public的构造函数,否则编译会报错:

The type "com.happen23.agv.hik_camera.lib_hik.MvGvspPixelType" is not supported: Can't create an instance of class com.happen23.agv.hik_camera.lib_hik.MvGvspPixelType, requires a public no-arg constructor: java.lang.NoSuchMethodException: com.happen23.agv.hik_camera.lib_hik.MvGvspPixelType.<init>()

总结

其实TypeConverter干的活儿非常像序列化、反序列化操作,只不过目标内存是native的而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值