繁体   English   中英

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

[英]SWIG (Java): How do I pass a struct with callback functions to C++ from an Android application?

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

暂无
暂无

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

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