我应该如何编写 .i 文件以在 Java 或 C# 中包装回调

Posted

技术标签:

【中文标题】我应该如何编写 .i 文件以在 Java 或 C# 中包装回调【英文标题】:How should I write the .i file to wrap callbacks in Java or C# 【发布时间】:2012-08-31 06:42:38 【问题描述】:

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

C 回调看起来是这样的:

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

【问题讨论】:

您的回调是否有void* 用户数据参数? C 中的回调如下所示: static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) 什么是pjsip_rx_data - typedef 对应于void*?一个结构? (试图弄清楚我们是否可以简单地将jobject 传递给回调) 克劳迪奥,为什么不接受这个答案,因为它在过去 3 年里一直在浮动。 【参考方案1】:

如果您有机会通过回调传递一些数据,您可以这样做,但您需要编写一些 JNI 胶水。我整理了一个完整的示例,说明如何将 C 风格的回调映射到 Java 接口。

您需要做的第一件事是确定一个适合 Java 端的接口。我假设在 C 中我们有如下回调:

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

我决定在 Java 中将其表示为:

public interface Callback 
  public void handle(int value);

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

然后我编写了以下头文件(它不应该只是一个头文件,但它使事情变得简单)来练习包装:

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 直接传递给 C 实现。 在Java端隐藏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。 如果您想混合 Java 和 C 的回调实现,那么您需要对该解决方案进行相当多的扩展。您可以使用 %constant 公开 C 函数以用作回调,但这些类型映射将阻止您的包装函数接受此类输入。您可能希望提供重载来解决这个问题。

this question 有更多好的建议。

我相信 C# 的解决方案会有些相似,具有不同的类型映射名称和您用 C 编写的回调函数的不同实现。

【讨论】:

感谢您的详细回答。我正在寻找这样的东西。 +1 表示解决方案的完整性,但我注意到您正在执行malloc 没有相应的free。 (或者它是否在某处隐式释放?) @zub 我认为这就是我在最后一个列表中的第 1 点的意思。我会在周末尝试挖掘这个以扩展这一点。 是否有可能避免 set/dispatch 范式但做出这样的事情 - 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); 我试过但 arg1arg2_wrap.cxx 文件中保持统一。只有一个解决方案我发现它在 run 类中有适当的字段并使用它们而不是传递的空参数。 @triclosan - 可能是可能的,但我不太了解你想要做的事情的语义。您可以将我的示例的 set/dispatch 合并为一个,但您需要使用全局来保存对要执行回调的 Java 实例的引用。可能您想通过 API 的 C 端的完整但简单的示例提出后续问题? (如果你愿意,请随时在聊天或这里戳我)【参考方案2】:

C++ 到 C# 回调示例

这很好地允许 C++ 执行对 C# 的回调。

测试于:

C#: Visual Studio 2015 C++: Intel Parallel Studio 2017 SE C++:应该很好地适用于 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://***.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
  

【讨论】:

【参考方案3】:

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

其实你可以使用一个通知列表来实现一组回调接收器

【讨论】:

以上是关于我应该如何编写 .i 文件以在 Java 或 C# 中包装回调的主要内容,如果未能解决你的问题,请参考以下文章

如何检测非法 UTF-8 字节序列以在 java 输入流中替换它们?

节点 JS 客户端与服务器

我应该如何阅读键盘输入以在 WPF 中创建 2D 游戏?

如何下载和运行.exe文件c#

如何编写多部分 MIME 混合消息以在 Outlook 中正确显示

如何将包含数百张工作表的 excel 文件导入数据库以在 C# 项目中使用?