SWIG(Java):如何将具有回调函数的结构从Android应用程序传递给C ++? [英] SWIG (Java): How do I pass a struct with callback functions to C++ from an Android application?

查看:59
本文介绍了SWIG(Java):如何将具有回调函数的结构从Android应用程序传递给C ++?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用基于WebRTC的C ++代码库为Android开发实时通信应用(视频和音频).我使用SWIG生成了一个JNI桥,以从Java访问本机代码.调用的行为由在应用程序层中定义并以结构形式传递给库代码的许多回调函数确定.传递这些回调的函数如下所示:

I am developing a real-time communication app (video and audio) for Android using a WebRTC-based C++ code library. I generate a JNI bridge using SWIG to access the native code from Java. The behaviour of the call is determined via a number of callback functions that are defined in the application layer and passed in a struct to the library code. The function for passing these callbacks looks like this:

void registerCallbacks(CALL_CALLBACKS* callbacks);

其中,CALL_CALLBACKS是包含许多回调函数的结构,如下所示:

where CALL_CALLBACKS is a struct containing a number of callback functions as exemplified here:

struct CALL_CALLBACKS {
    // CALL_STATE is an Enum
    // Called whenever the state of the call changes
    void (*statusCallback)(CALL_STATE);

    // FRAME is a struct representing a video frame
    // Called whenever the other participant sends a video frame, typically at 30 fps
    void (*frameCallback)(const FRAME*);

    // ...
}

问题在于,当我让SWIG默认执行其操作时,结果将不可用.它生成一个Java类型CALL_CALLBACKS,其中包含每个回调的设置器和获取器,它们也是SWIG生成的类型.但是,这些类型沿SWIGTYPE_p_f_ENUM_CALL_STATUS__void的名称命名,只是C指针的包装.

The issue is that when I let SWIG do its thing by default, the result is unusable. It generates a java type CALL_CALLBACKS which contains setters and getters for each of the callbacks, which are also types generated by SWIG. However, these types are named along the lines of SWIGTYPE_p_f_ENUM_CALL_STATUS__void and are nothing but wrappers for C pointers.

我如何编写我的SWIG接口文件,以将回调(最好使用较少的荒谬名称)传递给C ++库?我认为可以以某种方式使用类型映射,但我只是无法解决该问题.

How can I write my SWIG interface file in order to pass callbacks (preferably with less nonsensical names) to the C++ library? I assume typemaps can be used somehow but I just can't wrap my head around how that can be done.

推荐答案

我不认为SWIG可以自行完成此操作.这是我将在普通JNI中执行的操作:

I don't think SWIG can do this by itself. Here's how I would do it in plain JNI:

首先,创建一个可以在Java端实现的Java接口.

First, create a Java interface that you can implement on the Java side.

interface Callbacks {
  void statusCallback(int status);
  void frameCallback(Frame frame);
  static native void registerCallbacks(Callbacks cb);
}

接下来,创建将C ++参数转发到实现接口的jobject g_receiver的函数:

Next, create functions that forward the C++ arguments to a jobject g_receiver that implements the interface:

jobject g_receiver;
void my_statusCallback(CALL_STATE s) {
   if (!g_receiver) {
      // Print a warning?
      return;
   }

   JNIEnv *env = getEnv();
   env->PushLocalFrame(10);

   jclass cls_Callbacks = env->GetObjectClass(g_receiver);
   jmethodID mid_Callbacks_statusCallback = env->GetMethodID(cls_Callbacks, "statusCallback", "(I)V");
   env->CallVoidMethod(g_receiver, mid_Callbacks_statusCallback, s);
   env->PopLocalFrame(nullptr);
}

void my_frameCallback(const FRAME* frame) {
   if (!g_receiver) {
      // Print a warning?
      return;
   }

   JNIEnv *env = getEnv();
   env->PushLocalFrame(10);

   // Create a Frame object from the C++ pointer.
   // See Proxy classes at http://swig.org/Doc4.0/Java.html#Java_imclass
   jclass cls_Frame = env->FindClass("Frame");
   jmethodID ctr_Frame = env->GetMethodID(cls_Frame, "<init>", "(JZ)V");
   jobject jFrame = env->NewObject(cls_Frame, ctr_Frame, (jlong) frame, (jboolean)false);
   jmethodID mid_Frame_delete = env->GetMethodID(cls_Frame, "delete", "(LFrame;)V");

   jclass cls_Callbacks = env->GetObjectClass(g_receiver);
   jmethodID mid_Callbacks_frameCallback = env->GetMethodID(cls_Callbacks, "frameCallback", "(LFrame;)V");
   env->CallVoidMethod(g_receiver, mid_Callbacks_frameCallback, jFrame);

   env->CallVoidMethod(jFrame, mid_Frame_delete); // Disconnect the Java Frame object from the C++ FRAME object.
   env->PopLocalFrame(nullptr);
}

CALL_CALLBACKS global_callbacks = { my_statusCallback, my_frameCallback };

最后,您可以按以下方式实现Callbacks#registerCallbacks.这有点棘手,因为您必须确保g_receiver是nullptr或有效的全局引用:

Finally, you can implement Callbacks#registerCallbacks as follows. This is a bit more tricky because you have to make sure g_receiver is either nullptr or a valid global reference:

JNIEXPORT void Java_Callbacks_registerCallbacks(JNIEnv *env, jclass cls_Callbacks, jobject receiver) {
    if (g_receiver) {
       env->DeleteGlobalRef(g_receiver);
    }

    g_receiver = receiver ? env->NewGlobalRef(receiver) : nullptr;
    registerCallbacks(global_callbacks);
}

我做了一些假设:

  • 生成代码时未使用任何软件包.这将影响JNI方法名称以及对签名中类的任何引用.
  • 我假设您的本机代码在不同的线程上运行,所以getEnv函数应使用JNI调用API附加当前线程
  • You did not use any package when generating the code. That will affect the JNI method names and any references to classes in signatures.
  • I am assuming your native code runs on a different thread, so the getEnv function should use the JNI invocation API to attach the current thread as a daemon thread. You can stash the pointer in another global variable.
  • Since you're on Android, you can only call FindClass from the main thread. You can work around this by creating a global reference to the class in the JNI_Onload method or hack around it by adding a Class<Frame> getFrameClass() method to the Callbacks interface. Or you can take the very long way round by doing the equivalent of g_receiver.getClass().getClassLoader().findClass("Frame").
  • You did not specify whether the frameCallback needs to free the FRAME object itself. My code assumes it does not and disconnects the Java object so you cannot accidentally use it after the callback ends.
  • You did not specify whether the native registerCallbacks could be called multiple times, so I assumed it does. You could also call registerCallbacks as part of JNI_Onload.

这篇关于SWIG(Java):如何将具有回调函数的结构从Android应用程序传递给C ++?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆