SWIG (Java):如何将带有回调函数的结构从 Android 应用程序传递给 C++?

Posted

技术标签:

【中文标题】SWIG (Java):如何将带有回调函数的结构从 Android 应用程序传递给 C++?【英文标题】:SWIG (Java): How do I pass a struct with callback functions to C++ from an Android application? 【发布时间】:2020-08-25 14:57:33 【问题描述】:

我正在使用基于 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,其中包含每个回调的 setter 和 getter,这也是 SWIG 生成的类型。但是,这些类型是按照 SWIGTYPE_p_f_ENUM_CALL_STATUS__void 命名的,只不过是 C 指针的包装器。

如何编写我的 SWIG 接口文件以便将回调(最好使用不那么无意义的名称)传递给 C++ 库?我认为可以以某种方式使用类型映射,但我就是无法理解如何做到这一点。

【问题讨论】:

会有多个并发回调对象吗?没有 userdata 指针,所以你必须以某种方式将 Java 接收器对象偷运到 C++ 代码中。 @Botje 不会有任何并发​​回调对象。您能否详细说明“走私接收者对象”的含义? 在我的回答中,接收者作为一个简单的全局变量被偷运到回调函数中。您可以完全使用线程局部变量或其他东西来设计更精细的方案。 【参考方案1】:

我认为 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&lt;Frame&gt; getFrameClass() 方法添加到Callbacks 接口来解决它。或者,您可以通过执行 g_receiver.getClass().getClassLoader().findClass("Frame") 的等效操作来实现非常。 您没有指定frameCallback 是否需要释放FRAME 对象本身。我的代码假定它没有并断开 Java 对象,因此您不会在回调结束后意外使用它。 您没有指定是否可以多次调用本机registerCallbacks,所以我认为可以。您也可以在 JNI_Onload 中调用 registerCallbacks

【讨论】:

以上是关于SWIG (Java):如何将带有回调函数的结构从 Android 应用程序传递给 C++?的主要内容,如果未能解决你的问题,请参考以下文章

通过 SWIG 从 C++ 调用 Go 回调函数

带有回调的 SWIG C++ 绑定

使用 SWIG 从 Ruby 调用 C 回调

将 python 函数传递给 SWIG 包装的 C++ 代码

在 SWIG 中将结构从 C++ 函数返回到 Python

如何在带有 Swig 的 Python 中使用 float **?