如何编写.i文件以在Java或C#中包装callback

我的C程序使用定期调用的callback函数。 我希望能够处理Java或C#程序中的callback函数。 我应该如何编写.i文件来实现这一目标?

Ccallback看起来如此:

static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) 

如果你有机会通过callback传递一些数据,你可以这样做,但是你需要写一些JNI粘合剂。 我整理了一个关于如何将C风格callback映射到Java界面的完整示例。

你需要做的第一件事就是决定一个适合Java方面的接口。 我在C中假设我们有如下callback:

 typedef void (*callback_t)(int arg, void *userdata); 

我决定用Java来表示:

 public interface Callback { public void handle(int value); } 

(因为我们可以在实现CallbackObject中存储状态,所以Java端的void *userdata的丢失并不是真正的问题。

然后,我写了下面的头文件(它不应该只是一个头,但它保持简单)来执行包装:

 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); } 

我能够成功地包装这个C与以下接口:

 %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" 

界面有相当多的部分:

  1. 存储调用Java接口所需信息的struct
  2. callback_t的实现。 它接受用户数据作为我们刚刚定义的struct ,然后使用一些标准的JNI调用Java接口。
  3. 一些使Callback对象直接传递给C实现的types映射作为一个真正的jobject
  4. 一个在Java端隐藏void*的types映射,并设置一个callback数据并填充实函数的相应参数,以使用我们刚刚编写的用于将调用分派回Java的函数。 它需要对Java对象进行全局引用,以防止随后被垃圾收集。

我写了一个Java类来testing它:

 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); } } 

哪一个按照你的希望工作。

有些要注意的地方:

  1. 如果多次调用set ,则会泄漏全局引用。 您可能需要提供一个callback未设置的方法,防止多次设置,或者使用弱引用。
  2. 如果你有多个线程,你将需要比JNIEnv更聪明,比我在这里。
  3. 如果你想混合callback的Java和C实现,那么你需要在这个解决scheme上做更多的扩展。 您可以公开C函数作为具有%constantcallback,但这些types映射将阻止您的包装函数接受这样的input。 可能你会想提供重载来解决这个问题。

在这个问题上还有一些更好的build议。

我相信C#的解决scheme会有些类似,不同的types映射名称以及您在C中编写的callback函数的不同实现。

C ++到C#callback示例

这很好地允许C ++执行到C#的callback。

经testing:

  • C# :Visual Studio 2015
  • C++ :Intel Parallel Studio 2017 SE
  • C++ :应该适用于Visual Studio 2015 C ++(任何人都可以validation这一点?)。
  • C++ :应该适用于任何其他生成标准.dll的Windows C ++编译器(任何人都可以validation这一点?)。

在你的SWIG .i文件中,包含这个文件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-cc-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 

C#解决scheme:您必须使用委托。 看代码示例

 public delegate void DoSome(object sender); public class MyClass{ public event DoSome callbackfunc; public void DoWork(){ // do work here if(callbackfunc != null) callbackfunc(something); } } 

这也像事件处理机制,但理论上都有相同的实现在C#

Java解决scheme:你必须使用接口,看这个示例代码

 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);} } } 

实际上,您可以使用通知列表来实现一组回叫接收器