My C program uses callback functions which are periodically called. I want to be able to handle the callback functions in a Java or C# program. How should I write the .i file to achieve this?
The C callback looks so:
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata)
Callbacks in C are usually implemented using function pointers and an associated data pointer. You pass your function on_event() and data pointers to a framework function watch_events() (for example). When an event happens, your function is called with your data and some event-specific data.
Callbacks make sure that a function is not going to run before a task is completed but will run right after the task has completed. It helps us develop asynchronous JavaScript code and keeps us safe from problems and errors.
You can do this if you have a chance to pass some data around with the callback, but you'll need to write some JNI glue. I put together a complete example of how you might map C style callbacks onto a Java interface.
The first thing you need to do is decide on an interface that's appropriate on the Java side. I assumed in C we had callbacks like:
typedef void (*callback_t)(int arg, void *userdata);
I decided to represent that in Java as:
public interface Callback {
public void handle(int value);
}
(The loss of the void *userdata
on the Java side isn't a real problem since we can store state in the Object
that implements Callback
trivially).
I then wrote the following header file (it shouldn't really be just a header, but it keeps things simple) to exercise the wrapping:
typedef void (*callback_t)(int arg, void *data);
static void *data = NULL;
static callback_t active = NULL;
static void set(callback_t cb, void *userdata) {
active = cb;
data = userdata;
}
static void dispatch(int val) {
active(val, data);
}
I was able to successfully wrap this C with the following interface:
%module test
%{
#include <assert.h>
#include "test.h"
// 1:
struct callback_data {
JNIEnv *env;
jobject obj;
};
// 2:
void java_callback(int arg, void *ptr) {
struct callback_data *data = ptr;
const jclass callbackInterfaceClass = (*data->env)->FindClass(data->env, "Callback");
assert(callbackInterfaceClass);
const jmethodID meth = (*data->env)->GetMethodID(data->env, callbackInterfaceClass, "handle", "(I)V");
assert(meth);
(*data->env)->CallVoidMethod(data->env, data->obj, meth, (jint)arg);
}
%}
// 3:
%typemap(jstype) callback_t cb "Callback";
%typemap(jtype) callback_t cb "Callback";
%typemap(jni) callback_t cb "jobject";
%typemap(javain) callback_t cb "$javainput";
// 4:
%typemap(in,numinputs=1) (callback_t cb, void *userdata) {
struct callback_data *data = malloc(sizeof *data);
data->env = jenv;
data->obj = JCALL1(NewGlobalRef, jenv, $input);
JCALL1(DeleteLocalRef, jenv, $input);
$1 = java_callback;
$2 = data;
}
%include "test.h"
The interface has quite a few parts to it:
struct
to store the information needed to make a call to the Java interface.callback_t
. It accepts as user data the struct
we just defined and then dispatches a call to the Java interface using some standard JNI.Callback
objects to be passed straight to the C implementation as a real jobject
.void*
on the Java side and sets up a callback
data and fills in the corresponding arguments for the real function to use the function we just wrote for dispatching calls back to Java. It takes a global reference to the Java object to prevent it from being garbage collected subsequently.I wrote a little Java class to test it with:
public class run implements Callback {
public void handle(int val) {
System.out.println("Java callback - " + val);
}
public static void main(String argv[]) {
run r = new run();
System.loadLibrary("test");
test.set(r);
test.dispatch(666);
}
}
which worked as you'd hope.
Some points to note:
set
multiple times it will leak the global reference. You either need to supply a way for the callback to be unset, prevent setting multiple times, or use weak references instead. JNIEnv
than I've been here.%constant
but these typemaps will prevent your wrapped functions from accepting such inputs. Probably you would want to supply overloads to work around that.There's some more good advice in this question.
I believe that a solution for C# would be somewhat similar, with different typemap names and a differing implementation of the callback function you write in C.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With