Introduction

In JNA, in order to map with native functions, we can have two mapping methods, the first one is interface mapping and the second one is direct mapping. Although the two approaches are different, in the specific method mapping, we both need to define a method in JAVA that maps to the native method..

This JAVA mapping is a function in JNA, and by using a function object, we can achieve some very powerful functionality.

function definition

First look at the definition of Function in JNA.

1
public class Function extends Pointer

You can see that Function is actually a Pointer, which points to a native function.

So how do you get an instance of Function?

We know that the JNA process is to map the Library first, and then map the Function in the Library. So naturally we should be able to get the Function from the Library.

Let’s look at the definition of the method to get an instance of function according to Library name.

This method can accept 4 parameters, the first two parameters should be familiar, the third parameter is callFlags, which is the flags of the function call, Function defines three callFlags.

1
2
3
4
5
    public static final int C_CONVENTION = 0;

    public static final int ALT_CONVENTION = 0x3F;

    public static final int THROW_LAST_ERROR = 0x40;

where C_CONVENTION indicates a method call of type C.

ALT_CONVENTION indicates the other call methods.

THROW_LAST_ERROR means that a LastErrorException will be thrown if the return value of the native function is non-zero.

The last parameter is encoding, which is the encoding of the string, and actually refers to the conversion between Java unicode and native (const char*) strings.

In addition to getting Function according to Library name, JNA also provides a way to get Function according to Pointer.

1
2
3
    public static Function getFunction(Pointer p, int callFlags, String encoding) {
        return new Function(p, callFlags, encoding);
    }

The Pointer here is a pointer to a native method, because Function itself is inherited from Pointer, so the essence of creating Function with Pointer is to add some Function-specific properties on top of Pointer.

With the definition of Function, the more important thing is how to call the corresponding method through Function. Similar to reflection, Function also has an invoke method, and by calling invoke, we can execute the corresponding Function function.

There are two types of invoke methods in Function, one is the generic object Object, and the other is the invoke method with return value, such as invokeString, invokePointer, invokeInt, etc.

Function’s practical application

The actual use of Function is somewhat similar to reflection in JAVA. The workflow is to first get the NativeLibrary to be loaded, then find the Function to be called from the NativeLibrary, and finally invoke some of the Function’s methods.

The C language printf should be the most familiar native method. Let’s see how to use Function to call this method.

1
2
3
4
5
6
7
8
        NativeLibrary lib = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME);
        Function f = lib.getFunction("printf");
        try {
            f.invoke(getClass(), new Object[] { getName() });
            fail("Invalid return types should throw an exception");
        } catch(IllegalArgumentException e) {
            // expected
        }

As you can see the flow of the call is very concise. If we are using interface Mapping or direct Mapping, we also need to customize an interface or class and define a corresponding java method mapping in it. But if we use Function, none of this is needed. We can get the corresponding function directly from NativeLibrary and finally call the method in it.

The prototype of printf in C is as follows.

1
2
# include <stdio.h>
int printf(const char *format, ...) ;

printf comes with a return value, and to output this return value, you can call the function’s invokeInt command. Let’s look at another example of a call with a return value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
        NativeLibrary lib = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME);
        Function f = lib.getFunction("printf");
        Object[] args = new Object[Function.MAX_NARGS+1];
        // Make sure we don't break 'printf'
        args[0] = getName();
        try {
            f.invokeInt(args);
            fail("Arguments should be limited to " + Function.MAX_NARGS);
        } catch(UnsupportedOperationException e) {
            // expected
        }

Summarize

Using Function can reduce the work of handwriting Mapping, which is very useful in some cases, but Function’s invoke supports TypeMapper, not FunctionMapper, so there are still some limitations in using it.