Home  >  Article  >  Java  >  How to solve the callback problem in JNA in advanced java usage

How to solve the callback problem in JNA in advanced java usage

PHPz
PHPzforward
2023-05-05 11:37:061463browse

    Introduction

    What is a callback? To put it simply, callback is a callback notification. When we need to notify certain tasks after a method is completed or an event is triggered, we need to use callback.

    The language you are most likely to see callback in is javascript. Basically in javascript, callback is everywhere. In order to solve the problem of callback hell caused by callbacks, promises were specially introduced in ES6 to solve this problem.

    In order to facilitate interaction with native methods, JNA also provides Callback for callback. The essence of a callback in JNA is a pointer to a native function. Through this pointer, methods in the native function can be called. Let's take a look.

    Callback in JNA

    First look at the definition of Callback in JNA:

    public interface Callback {
        interface UncaughtExceptionHandler {
            void uncaughtException(Callback c, Throwable e);
        }
        String METHOD_NAME = "callback";
    
        List<String> FORBIDDEN_NAMES = Collections.unmodifiableList(
                Arrays.asList("hashCode", "equals", "toString"));
    }

    All Callback methods need to implement this Callback interface. The Callback interface is very simple. It defines an interface and two properties.

    Let’s look at this interface first. The interface name is UncaughtExceptionHandler, and there is an uncaughtException method in it. This interface is mainly used to handle exceptions that are not caught in JAVA's callback code.

    Note that in the uncaughtException method, exceptions cannot be thrown, and any exceptions thrown from this method will be ignored.

    METHOD_NAME This field specifies the method to be called by Callback.

    If only one public method is defined in the Callback class, then the default callback method is this method. If multiple public methods are defined in the Callback class, the method with METHOD_NAME = "callback" will be selected as the callback.

    The last attribute is FORBIDDEN_NAMES. Indicates that the names in this list cannot be used as callback methods.

    Currently it seems that there are three method names that cannot be used, namely: "hashCode", "equals", and "toString".

    Callback also has a sibling called DLLCallback. Let’s take a look at the definition of DLLCallback:

    public interface DLLCallback extends Callback {
        @java.lang.annotation.Native
        int DLL_FPTRS = 16;
    }

    DLLCallback is mainly used for accessing Windows API.

    For callback objects, we need to be responsible for releasing the callback objects ourselves. If native code attempts to access a recycled callback, it may cause the VM to crash.

    Application of callback

    Definition of callback

    Because callback in JNA actually maps a pointer to a function in native. First, take a look at the function pointers defined in the struct:

    struct _functions {
      int (*open)(const char*,int);
      int (*close)(int);
    };

    In this structure, two function pointers are defined, with two parameters and one parameter respectively.

    The corresponding JNA callback definition is as follows:

    public class Functions extends Structure {
      public static interface OpenFunc extends Callback {
        int invoke(String name, int options);
      }
      public static interface CloseFunc extends Callback {
        int invoke(int fd);
      }
      public OpenFunc open;
      public CloseFunc close;
    }

    We define two interfaces in Structure that inherit from Callback, and the corresponding invoke method is defined in the corresponding interface.

    Then take a look at the specific calling method:

    Functions funcs = new Functions();
    lib.init(funcs);
    int fd = funcs.open.invoke("myfile", 0);
    funcs.close.invoke(fd);

    In addition, Callback can also be used as the return value of the function, as shown below:

    typedef void (*sig_t)(int);
    sig_t signal(int signal, sig_t sigfunc);

    For this kind of separate function pointer , we need to customize a Library and define the corresponding Callback in it, as shown below:

    public interface CLibrary extends Library {
        public interface SignalFunction extends Callback {
            void invoke(int signal);
        }
        SignalFunction signal(int signal, SignalFunction func);
    }

    Acquisition and application of callback

    If the callback is defined in Structure, then it can be Structure is automatically instantiated when it is initialized, and then you only need to access the corresponding properties from Structure.

    If the callback is defined in an ordinary Library, it is as follows:

    public static interface TestLibrary extends Library {
            interface VoidCallback extends Callback {
                void callback();
            }
            interface ByteCallback extends Callback {
                byte callback(byte arg, byte arg2);
            }
            void callVoidCallback(VoidCallback c);
            byte callInt8Callback(ByteCallback c, byte arg, byte arg2);
        }

    In the above example, we defined two callbacks in a Library, one with no return value callback, one is a callback that returns byte.

    JNA provides a simple tool class to help us get Callback. This tool class is CallbackReference, and the corresponding method is CallbackReference.getCallback, as shown below:

    Pointer p = new Pointer("MultiplyMappedCallback".hashCode());
    Callback cbV1 = CallbackReference.getCallback(TestLibrary.VoidCallback.class, p);
    Callback cbB1 = CallbackReference.getCallback(TestLibrary.ByteCallback.class, p);
    log.info("cbV1:{}",cbV1);
    log.info("cbB1:{}",cbB1);

    The output results are as follows:

    INFO com.flydean.CallbackUsage - cbV1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
    INFO com.flydean.CallbackUsage - cbB1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)

    It can be seen that these two Callbacks are actually proxies for the native method. If you look at the implementation logic of getCallback in detail:

    private static Callback getCallback(Class<?> type, Pointer p, boolean direct) {
            if (p == null) {
                return null;
            }
            if (!type.isInterface())
                throw new IllegalArgumentException("Callback type must be an interface");
            Map<Callback, CallbackReference> map = direct ? directCallbackMap : callbackMap;
            synchronized(pointerCallbackMap) {
                Reference<Callback>[] array = pointerCallbackMap.get(p);
                Callback cb = getTypeAssignableCallback(type, array);
                if (cb != null) {
                    return cb;
                }
                cb = createCallback(type, p);
                pointerCallbackMap.put(p, addCallbackToArray(cb,array));
                // No CallbackReference for this callback
                map.remove(cb);
                return cb;
            }
        }

    You can see that its implementation logic is to first determine whether the type is an interface. If it is not an interface, an error will be reported. Then determine whether it is direct mapping. In fact, the current implementation of JNA is all interface mapping, so the next logic is to obtain the callback corresponding to the function pointer from pointerCallbackMap. Then find the specific Callback according to the type passed in.

    If it is not found, create a new callback, and finally store the newly created callback in pointerCallbackMap.

    Everyone should note that there is a key parameter here called Pointer. When actually using it, you need to pass in a pointer to the real naitve function. In the above example, for the sake of simplicity, we customized a Pointer, which does not have much practical significance.

    如果真的要想在JNA中调用在TestLibrary中创建的两个call方法:callVoidCallback和callInt8Callback,首先需要加载对应的Library:

    TestLibrary lib = Native.load("testlib", TestLibrary.class);

    然后分别创建TestLibrary.VoidCallback和TestLibrary.ByteCallback的实例如下,首先看一下VoidCallback:

    final boolean[] voidCalled = { false };
            TestLibrary.VoidCallback cb1 = new TestLibrary.VoidCallback() {
                @Override
                public void callback() {
                    voidCalled[0] = true;
                }
            };
            lib.callVoidCallback(cb1);
            assertTrue("Callback not called", voidCalled[0]);

    这里我们在callback中将voidCalled的值回写为true表示已经调用了callback方法。

    再看看带返回值的ByteCallback:

    final boolean[] int8Called = {false};
            final byte[] cbArgs = { 0, 0 };
            TestLibrary.ByteCallback cb2 = new TestLibrary.ByteCallback() {
                @Override
                public byte callback(byte arg, byte arg2) {
                    int8Called[0] = true;
                    cbArgs[0] = arg;
                    cbArgs[1] = arg2;
                    return (byte)(arg + arg2);
                }
            };
    
    final byte MAGIC = 0x11;
    byte value = lib.callInt8Callback(cb2, MAGIC, (byte)(MAGIC*2));

    我们直接在callback方法中返回要返回的byte值即可。

    在多线程环境中使用callback

    默认情况下, callback方法是在当前的线程中执行的。如果希望callback方法是在另外的线程中执行,则可以创建一个CallbackThreadInitializer,指定daemon,detach,name,和threadGroup属性:

     final String tname = "VoidCallbackThreaded";
            ThreadGroup testGroup = new ThreadGroup("Thread group for callVoidCallbackThreaded");
            CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup);

    然后创建callback的实例:

    TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() {
                @Override
                public void callback() {
                    Thread thread = Thread.currentThread();
                    daemon[0] = thread.isDaemon();
                    name[0] = thread.getName();
                    group[0] = thread.getThreadGroup();
                    t[0] = thread;
                    if (thread.isAlive()) {
                        alive[0] = true;
                    }
                    ++called[0];
                    if (THREAD_DETACH_BUG && called[0] == 2) {
                        Native.detach(true);
                    }
                }
            };

    然后调用:

    Native.setCallbackThreadInitializer(cb, init);

    将callback和CallbackThreadInitializer进行关联。

    最后调用callback方法即可:

    lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);

    The above is the detailed content of How to solve the callback problem in JNA in advanced java usage. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete