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的而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章