繁体   English   中英

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

[英]How should I write the .i file to wrap callbacks in Java or C#

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

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

界面有很多部分:

  1. 用于存储调用Java接口所需信息的struct
  2. callback_t的实现。 它接受我们刚刚定义的struct作为用户数据,然后使用一些标准JNI调度对Java接口的调用。
  3. 这导致一些typemaps Callback对象传递直奔C语言实现真正的jobject
  4. 一个类型映射,它隐藏了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);    
  }
}

这是你希望的工作。

有些要点需要注意:

  1. 如果多次调用set ,它将泄漏全局引用。 您需要提供一种方法来取消设置回调,防止多次设置,或者使用弱引用。
  2. 如果你有多个线程,你需要更聪明地使用JNIEnv不是我在这里。
  3. 如果你想混合使用回调的Java和C实现,那么你需要对这个解决方案进行一些扩展。 您可以将C函数公开为具有%constant回调,但这些类型映射将阻止您的包装函数接受此类输入。 可能你会想要提供重载来解决这个问题。

这个问题上有一些更好的建议。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM