Introduction

There are many kinds of mappings in JNA, library mapping, function mapping and function parameter and return value mapping. The mapping between libary and function is relatively simple. We have explained it in the previous article. For type mapping, because There are many types of types in JAVA, so here we will extract the type mapping of JNA and explain it separately.

The nature of typemaps

We mentioned earlier that there are two ways to map methods in JAVA and methods in native libary in JNA, one is called interface mapping, and the other is called direct mapping.

But have we considered what is the nature of these two mappings?

For example, native has a method. How do we pass the method parameters in the JAVA code to the native method, and convert the return value of the native method into the return type of the function in JAVA?

The answer is serialization.

Because essentially all interactions are binary interactions. The JAVA type and the native type are converted. The simplest case is that the data length of the bottom layer of the JAVA type and the native type is consistent, so that the data conversion will be simpler.

Let's look at the mapping and length relationship between JAVA types and native types:

C Type Meaning of Native type Java Type
char 8-bit integer byte
wchar_t related to the platform char
short 16-bit integer short
int 32-bit integer int
int boolean flag boolean
enum enum type int (usually)
long long, __int64 64-bit integer long
float 32-bit floating point number float
double 64-bit floating point number double
pointer (eg void*) Platform related Buffer Pointer
pointer (eg void*), array Platform related <P>[] (array of primitive types)

The above JAVA types are all types that come with JDK (except Pointer).

In addition to the type mapping that comes with JAVA, JNA also defines some data types, which can be mapped with native types:

C Type Meaning of Native type Java Type
long Platform dependent (32- or 64-bit integer) NativeLong
const char* String (native encoding or jna.encoding) String
const wchar_t* string (unicode) WString
char** array of strings String[]
wchar_t** String array (unicode) WString[]
void** array of pointers Pointer[]
struct* struct Structure pointers and structures Structure
union structure Union
struct[] array of structures Structure[]
void (*FP)() Function pointer (Java or native) Callback
pointer (<T>*) pointer PointerType
other Integer type IntegerType
other custom mapping type NativeMapped

TypeMapper

In addition to the defined mapping relationship, you can also use TypeMapper to customize the conversion of parameter types. Let's first look at the definition of TypeMapper:

 public interface TypeMapper {

    FromNativeConverter getFromNativeConverter(Class<?> javaType);

    ToNativeConverter getToNativeConverter(Class<?> javaType);
}

TypeMapper is an interface that defines two converter methods, getFromNativeConverter and getToNativeConverter.

If you want to use TypeMapper, you need to implement it and these two methods are enough. Let's take a look at how the official W32APITypeMapper is implemented:

 TypeConverter stringConverter = new TypeConverter() {
                @Override
                public Object toNative(Object value, ToNativeContext context) {
                    if (value == null)
                        return null;
                    if (value instanceof String[]) {
                        return new StringArray((String[])value, true);
                    }
                    return new WString(value.toString());
                }
                @Override
                public Object fromNative(Object value, FromNativeContext context) {
                    if (value == null)
                        return null;
                    return value.toString();
                }
                @Override
                public Class<?> nativeType() {
                    return WString.class;
                }
            };
            addTypeConverter(String.class, stringConverter);
            addToNativeConverter(String[].class, stringConverter);

First define a TypeConverter, and implement three methods toNative, fromNative and nativeType in TypeConverter. In this example, the native type is WString and the JAVA type is String. And this TypeConverter is the FromNativeConverter and ToNativeConverter that will eventually be used.

With typeMapper, how should it be used? The easiest way to do this is to add it to the third parameter of Native.load like this:

 TestLibrary lib = Native.load("testlib", TestLibrary.class, Collections.singletonMap(Library.OPTION_TYPE_MAPPER, mapper));

NativeMapped

TypeMapper needs to be passed in when calling the Native.load method, so as to provide the conversion relationship between JAVA type and native type. TypeMapper can be seen as an external maintainer of type conversion relationships.

Many friends may have thought that since the conversion relationship can be maintained outside the JAVA type, can this conversion relationship be maintained in the JAVA type itself? The answer is yes, we only need to implement the NativeMapped interface in the JAVA type that needs to implement the conversion type relationship.

Let's first look at the definition of the NativeMapped interface:

 public interface NativeMapped {

    Object fromNative(Object nativeValue, FromNativeContext context);

    Object toNative();

    Class<?> nativeType();
}

It can be seen that the methods defined in NativeMapped to be implemented are basically the same as those defined in FromNativeConverter and ToNativeConverter.

The following is a specific example to illustrate how NativeMapped should be used. First we define an enum class to implement the NativeMapped interface:

 public enum TestEnum implements NativeMapped {
        VALUE1, VALUE2;

        @Override
        public Object fromNative(Object nativeValue, FromNativeContext context) {
            return values()[(Integer) nativeValue];
        }

        @Override
        public Object toNative() {
            return ordinal();
        }

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

This class implements the conversion from Integer to TestEnum enumeration.

To use the TestEnum class, you need to define an interface:

 public static interface EnumerationTestLibrary extends Library {
        TestEnum returnInt32Argument(TestEnum arg);
    }

The specific calling logic is as follows:

 EnumerationTestLibrary lib = Native.load("testlib", EnumerationTestLibrary.class);
assertEquals("Enumeration improperly converted", TestEnum.VALUE1, lib.returnInt32Argument(TestEnum.VALUE1));
assertEquals("Enumeration improperly converted", TestEnum.VALUE2, lib.returnInt32Argument(TestEnum.VALUE2));

As you can see, because NativeMapped already contains type conversion information, there is no need to specify TypeMapper.

Note that testlib is used here. This testlib is compiled from the native module of JNA. If you are in a MAC environment, you can copy the JNA code and run ant native to get it. After the compilation is complete, copy this libtestlib.dylib to you You can use darwin-aarch64 or darwin-x86 in the resources directory of the project.

If you don't know, you can contact me.

Summarize

This article explains the type mapping rules in JNA and the method of custom type mapping.

Code for this article: https://github.com/ddean2009/learn-java-base-9-to-20.git

This article has been included in www.flydean.com

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com