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 most likely to see callbacks is javascript. Basically, in javascript, callbacks are everywhere. In order to solve the problem of callback hell caused by callback, ES6 specially introduced promise to solve this problem.
In order to facilitate interaction with native methods, JNA also provides Callback for callback. The essence of the callback in JNA is a pointer to the native function, through which the method 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 first look at this interface, the interface name is UncaughtExceptionHandler, which has an uncaughtException method. This interface is mainly used to handle exceptions that are not caught in JAVA's callback code.
Note that in the uncaughtException method, no exception can be thrown, any exception thrown from this method will be ignored.
The METHOD_NAME field specifies the method to be called by the Callback.
If there is only one public method 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.
At present it seems that there are three method names that cannot be used, namely: "hashCode", "equals", "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 in Windows API access.
For the callback object, we need to be responsible for the release of the callback object. If native code tries to access a callback that has been recycled, it may crash the VM.
Application of callback
Definition of callback
Because the callback in JNA actually maps the pointer to the function in the native. First look at the function pointer 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 is defined 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 look at the specific call 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 such a separate function pointer, we need to customize a Library and define the corresponding Callback in it, as follows:
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 the Structure, it can be automatically instantiated when the Structure is initialized, and then you only need to access the corresponding properties from the Structure.
If the callback definition is in a normal Library, it looks like this:
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 define two callbacks in a Library, one is a callback with no return value, and the other is a callback that returns a 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 is 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 to native methods. 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;
}
}
It can be seen that its implementation logic is to first determine whether the type is an interface, and an error will be reported if it is not an interface. Then judge whether it is direct mapping. In fact, the current implementation of JNA is interface mapping, so the next logic is to obtain the callback corresponding to the function pointer from the pointerCallbackMap. Then find the specific Callback according to the type passed in.
If it is not found, a new callback is created, and finally this newly created is stored in the pointerCallbackMap.
Everyone should note that there is a key parameter 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 have customized a Pointer, which does not have much practical significance.
If you really want to call the two call methods created in TestLibrary in JNA: callVoidCallback and callInt8Callback, you first need to load the corresponding Library:
TestLibrary lib = Native.load("testlib", TestLibrary.class);
Then create instances of TestLibrary.VoidCallback and TestLibrary.ByteCallback respectively as follows, first look at 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]);
Here we write back the value of voidCalled to true in the callback to indicate that the callback method has been called.
Take another look at ByteCallback with a return value:
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));
We can directly return the byte value to be returned in the callback method.
Using callbacks in a multithreaded environment
By default, the callback method is executed in the current thread. If you want the callback method to be executed in another thread, you can create a CallbackThreadInitializer and specify the daemon, detach, name, and threadGroup properties:
final String tname = "VoidCallbackThreaded";
ThreadGroup testGroup = new ThreadGroup("Thread group for callVoidCallbackThreaded");
CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup);
Then create an instance of 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);
}
}
};
Then call:
Native.setCallbackThreadInitializer(cb, init);
Associate callback with CallbackThreadInitializer.
Finally, call the callback method:
lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);
Summarize
The callback in JNA can realize the function of passing the method to the native method, which is very useful in some cases.
Code for this article: https://github.com/ddean2009/learn-java-base-9-to-20.git
This article has been included in http://www.flydean.com/09-jna-callbacks/
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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。