7

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)
Contango
  • 76,540
  • 58
  • 260
  • 305
Claudio Ferraro
  • 4,551
  • 6
  • 43
  • 78
  • Does your callback have a `void*` user data argument? – Flexo Aug 31 '12 at 08:02
  • the callback in C look so: static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) – Claudio Ferraro Sep 03 '12 at 07:22
  • What is `pjsip_rx_data` - a `typedef` for `void*`? A struct? (Trying to figure out if we can pass a `jobject` around to the callback trivially) – Flexo Sep 03 '12 at 07:45
  • 2
    Claudio, why not to accept the answer, since it is floating around for last 3 years. – jojeck Sep 15 '15 at 13:06

3 Answers3

24

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:

  1. A struct to store the information needed to make a call to the Java interface.
  2. An implementation of the 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.
  3. Some typemaps that cause the Callback objects to be passed straight to the C implementation as a real jobject.
  4. A typemap that hides the 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:

  1. If you call 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.
  2. If you have multiple threads around you will need to be smarter with the JNIEnv than I've been here.
  3. If you wanted to mix Java and C implementations of callbacks then you'll need to expand on this solution quite a bit. You can expose C functions to be used as callbacks with %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.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Thanks for the detailed answer. I was looking exactly for something like this. – Zubzub Feb 05 '13 at 10:50
  • +1 for the completeness of the solution, but I noticed you're doing a `malloc` without a corresponding `free`. (or is it implicitly freed somewhere?) – Dmitry Brant Nov 12 '13 at 20:36
  • @zub I think that was what I meant by point 1 in the last list. I'll try and dig this out over the weekend to expand on that point. – Flexo Nov 13 '13 at 08:28
  • Is it possible to avoid `set/dispatch` paradigm but make someting like this - `void native(callback_t cb, int arg1, int arg2, void *place_result_on_arg1_arg2_here) { cb(arg1, arg2, place_result_on_arg1_arg2_here); }` I've tried but `arg1` and `arg2` remain unitialized in `_wrap.cxx` file. Only one solution I've found it to have appropriate fields in `run` class and use them instead of passed empty arguments. – triclosan Dec 21 '13 at 11:37
  • @triclosan - probably possible but I'm not quite understanding the semantics of what you're trying to do. You can merge the set/dispatch of my example into one, but you'd need to use a global to hold a reference to the Java instance you want to execute the call back on. Possibly you want to ask a follow up question with a complete but simple example of the C side of the API? (Feel free to poke me on chat or here if you do) – Flexo Dec 21 '13 at 12:45
  • I made separate question http://stackoverflow.com/questions/20719497/java-code-from-c-callbacks-passing-parameters – triclosan Dec 21 '13 at 14:29
  • Hello @Flexo! I followed your answer, but I don't know why I get this error : Exception in thread "main" java.lang.UnsatisfiedLinkError: my/pkg/to.TestJNI.set(Lmy/pkg/to/Callback;)V – Farah Feb 27 '14 at 19:39
  • @farah - at a guess I'd say you haven't got the interface `Callback` in the right package, or accidentally made that native somehow too. – Flexo Feb 27 '14 at 19:41
  • @Flexo There is something that eludes me: How do I retrieve the userdata on the Java side? – celavek May 07 '15 at 08:06
  • @celavek you don't need it in Java, the object that receives the call back is a proper object so it can have any members you like. Use that instead, more oo, more flexible. – Flexo May 07 '15 at 16:24
  • @Flexo But I would like to pass some meaningful data from C to Java via the callback parameter. So how could I have that via the object that receives the callback? I know how to do that via hand coded JNI but I spent the last day trying to do that via SWIG. – celavek May 07 '15 at 16:31
  • @celavek pass it as a parameter to the function like I did with the `int` argument. Abusing the callback userdata is definitely not the way to pass state to a Java object. – Flexo May 07 '15 at 17:02
  • Excellent answer. It works as given, How to make it work when the function arguments are pointers [please see the question](http://stackoverflow.com/questions/32210021/jni-for-c-using-swig-trouble-with-function-pointer) – Amol Aug 25 '15 at 17:09
2

C++ to C# Callback Example

This works nicely to allow C++ to execute callbacks to C#.

Tested under:

  • C#: Visual Studio 2015
  • C++: Intel Parallel Studio 2017 SE
  • C++: Should work nicely for Visual Studio 2015 C++ (can anyone verify this?).
  • C++: Should work nicely for any other Windows C++ compiler that generates a standard .dll (can anyone verify this?).

In your SWIG .i file, include this file callback.i:

//////////////////////////////////////////////////////////////////////////
// cs_callback is used to marshall callbacks. It allows a C# function to
// be passed to C++ as a function pointer through P/Invoke, which has the
// ability to make unmanaged-to-managed thunks. It does NOT allow you to
// pass C++ function pointers to C#.
//
// Tested under:
// - C#: Visual Studio 2015
// - C++: Intel Parallel Studio 2017 SE
//
// Anyway, to use this macro you need to declare the function pointer type
// TYPE in the appropriate header file (including the calling convention),
// declare a delegate named after CSTYPE in your C# project, and use this
// macro in your .i file. Here is an example:
//
// C++: "callback.h":
//    #pragma once
//    typedef void(__stdcall *CppCallback)(int code, const char* message);
//    void call(CppCallback callback);
//
// C++: "callback.cpp":
//    #include "stdafx.h" // Only for precompiled headers.
//    #include "callback.h"
//    void call(CppCallback callback)
//    {
//        callback(1234, "Hello from C++");
//    }
//
// C#: Add this manually to C# code (it will not be auto-generated by SWIG):
//    public delegate void CSharpCallback(int code, string message);
//
// C#: Add this test method:
//    public class CallbackNUnit
//    {
//        public void Callback_Test()
//        {
//            MyModule.call((code, message) =>
//            {
//                // Prints "Hello from C++ 1234"
//                Console.WriteLine(code + " " + message);
//            });   
//        }        
//    }
//
// SWIG: In your .i file:
//   %module MyModule
//   %{
//     #include "callback.h"
//   %}
//   
//   %include <windows.i>
//   %include <stl.i>
//   
//   // Links typedef in C++ header file to manual delegate definition in C# file.
//   %include "callback.i" // The right spot for this file to be included!
//   %cs_callback(CppCallback, CSharpCallback)
//   #include "callback.h"
//
// As an alternative to specifying __stdcall on the C++ side, in the .NET
// Framework (but not the Compact Framework) you can use the following
// attribute on the C# delegate in order to get compatibility with the
// default calling convention of Visual C++ function pointers:
// [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
//
// Remember to invoke %cs_callback BEFORE any code involving Callback. 
//
// References: 
// - http://swig.10945.n7.nabble.com/C-Callback-Function-Implementation-td10853.html
// - http://stackoverflow.com/questions/23131583/proxying-c-c-class-wrappers-using-swig             
//
// Typemap for callbacks:
%define %cs_callback(TYPE, CSTYPE)
    %typemap(ctype) TYPE, TYPE& "void *"
    %typemap(in) TYPE  %{ $1 = ($1_type)$input; %}
    %typemap(in) TYPE& %{ $1 = ($1_type)&$input; %}
    %typemap(imtype, out="IntPtr") TYPE, TYPE& "CSTYPE"
    %typemap(cstype, out="IntPtr") TYPE, TYPE& "CSTYPE"
    %typemap(csin) TYPE, TYPE& "$csinput"
%enddef
  
Community
  • 1
  • 1
Contango
  • 76,540
  • 58
  • 260
  • 305
-3

C# solution : you must use delegates. look at the code sample

public delegate void DoSome(object sender);

public class MyClass{
      public event DoSome callbackfunc;

      public void DoWork(){
            // do work here 
            if(callbackfunc != null)
                     callbackfunc(something);
      }
}

this is also like event handling mechanism but by theory both of have same implementation in c#

Java solution: you must use interfaces, look at this sample code

interface Notification{
      public void somthingHappend(Object obj);
}

class MyClass{
     private Notification iNotifer;

     public void setNotificationReciver(Notification in){
            this.iNotifier = in;
     }

     public void doWork(){
            //some work to do
            if(something heppens){ iNotifier.somthingHappend(something);}
     }
}

actually you can use a List of Notification to implement a set of callback receivers

Bahram
  • 1,464
  • 2
  • 22
  • 38