SWIGJava:如何将具有回调函数的结构从Android应用程序传递给C ++?

问题描述

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

void registerCallbacks(CALL_CALLBACKS* callbacks);

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

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指针的包装。

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

解决方法

我认为SWIG不能单独完成此操作。这是我将在普通JNI中执行的操作:

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

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

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

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或有效的全局引用:

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附加当前线程as a daemon thread。您可以将指针存储在另一个全局变量中。
  • 由于您使用的是Android,因此只能从主线程调用FindClass。您可以通过在JNI_Onload方法中创建对该类的全局引用来解决此问题,也可以通过在Class<Frame> getFrameClass()接口中添加一个Callbacks方法来解决该问题。或者,您也可以执行g_receiver.getClass().getClassLoader().findClass("Frame")的等效功能,从而使非常走得很远。
  • 您未指定frameCallback是否需要释放FRAME对象本身。我的代码假定它没有,并断开了Java对象的连接,因此您不会在回调结束后意外使用它。
  • 您没有指定是否可以多次调用本机registerCallbacks,所以我假设可以。您也可以将registerCallbacks的一部分称为JNI_Onload