我的 C 程序使用定期调用的回调函数。我希望能够处理 Java 或 C# 程序中的回调函数。我应该如何编写 .i 文件来实现这一点?
C 回调看起来是这样的:
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata)
如果您有机会通过回调传递一些数据,您可以这样做,但您需要编写一些 JNI 胶水。我整理了一个完整的示例,说明如何将 C 风格的回调映射到 Java 接口。
您需要做的第一件事是确定一个适合 Java 端的接口。我假设在 C 中我们有如下回调:
typedef void (*callback_t)(int arg, void *userdata);
我决定在 Java 中将其表示为:
public interface Callback {
public void handle(int value);
}
(void *userdata
Java 端的丢失并不是一个真正的问题,因为我们可以将状态存储在很简单的Object
实现中Callback
)。
然后我编写了以下头文件(它不应该只是一个头文件,但它使事情变得简单)来练习包装:
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"
该界面有很多部分:
struct
存储调用 Java 接口所需的信息。callback_t
。它接受struct
我们刚刚定义的用户数据,然后使用一些标准 JNI 调度对 Java 接口的调用。Callback
对象作为真实的jobject
.void*
并设置callback
数据并为实际函数填充相应的参数,以使用我们刚刚编写的函数将调用分派回 Java。它采用对 Java 对象的全局引用,以防止随后对其进行垃圾收集。我写了一个小 Java 类来测试它:
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);
}
}
如您所愿。
需要注意的几点:
set
多次调用,它将泄漏全局引用。您要么需要提供一种取消设置回调的方法,防止设置多次,要么使用弱引用。JNIEnv
比我在这里更聪明。%constant
但这些类型映射将阻止您的包装函数接受此类输入。可能你会想要提供重载来解决这个问题。在这个问题中有一些更好的建议。
我相信 C# 的解决方案会有些相似,具有不同的类型映射名称和您用 C 编写的回调函数的不同实现。
这很好地允许 C++ 执行对 C# 的回调。
测试下:
C#
: 视觉工作室 2015C++
: 英特尔 Parallel Studio 2017 SEC++
:应该适用于 Visual Studio 2015 C++(任何人都可以验证这一点吗?)。C++
:对于生成标准 .dll 的任何其他 Windows C++ 编译器应该可以很好地工作(任何人都可以验证这一点吗?)。在您的 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-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
C# 解决方案:您必须使用委托。看代码示例
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解决方案:必须使用接口,看这个示例代码
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);}
}
}
实际上您可以使用通知列表来实现一组回调接收器