C++ SDK提供回调函数接口给Android/iOS 集成

C++如何提供跨平台的回调函数接口是业务开发很常遇到的需求,但是实际上公开的资料都很杂乱,而且不一定正确,因此在此文章中,以Android Java和iOS Swift为例,系统化说明一下如何制作一个跨平台C++回调函数接口。

对于C++而言,最常用的建构回调函数方法是基于std::function

std::function 是 C++ 标准库中的一个模板类,用于封装函数对象(Function Object)、函数指针、以及可以调用的(如 lambda 表达式)。它提了一种通用的方式来存储、传递和调用各种类型的可调用对象。

std::function 的主要作用是将可调用对象视为一个函数类型,并提供了一组成员函数来操作和调用这个函数对象。它的声明如下:

template 
class function;

其中 Signature 是函数类型的签名,包括返回类型和参数类型。

std::function 的使用非常灵活,可以用于存储各种类型的可调用对象,包括函数指针、成员函数指针、函数对象、以及 lambda 表达式等。可以通过以下方式来创建 std::function 对象:

std::function func;  // 声明一个接受 int 类型参数并返回 int 类型的函数对象

// 将一个 lambda 表达式赋值给 func
func = [](int x) { return x * 2; };

// 调用 func
int result = func(5);  // 调用 lambda 表达式,得到结果 10

std::function 还可以作为函数参数或返回值使用,以实现更灵活的函数接口。

总而言之,std::function 提供了一种通用的封装方式,使得各种可调用对象可以以统一的方式进行存储、传递和调用,提高了代码的灵活性和可重用性。它在函数回调、事件处理等场景下非常有用。

整体思路

因此制作跨平台的回调函数接口也是围绕std::function展开,主要其实就分成两的步骤

1. 使用std::function制作C++ 回调函数接口,以及回调函数触发的代码 

2. 使用目标平台的语言实现方法转换,将目标平台的语言的方法转换成std::function,本文章会介绍使用Android Java和iOS Swift实现转换的代码

1. C++ 回调函数接口

首先在C++声明一个回调函数

std::function callback_ = nullptr;

然后制作一个回调函数注册接口

void registCallback( std::function  callback){
            callback_ = callback;
  }

最后在代码中加入触发回调的代码

int func(std::string tag){
    for (int i=0; i<100 ; i++){
        callback_(tag, i);
    }
}

2. 使用Android Java方法作为回调函数

2.1 Java传入的回调方法注册

需要在jni层建构一个Java和C++函数的桥接,首先我们建构的一个Java回调函数注册的接口

jobject jCallback = nullptr;
jmethodID jCallbackMethId = nullptr;
JavaVM *g_pJVM = nullptr
std::mutex g_CallbackMutex;

extern "C" JNIEXPORT int JNICALL
Java_com_test_registerCallback(JNIEnv *env,jclass clazz,object call_back) {
    // 线程保护
    std::unique_lock lck(g_CallbackMutex);
    // 避免重复注册
    if (jCallback) {
        return -1;
    }

    jCallback = env->NewGlobalRef(call_back);
    jclass callbackClazz = env->GetObjectClass(call_back);
    jCallbackMethId =
            env->GetMethodID(callbackClazz, "onUpdateCallback", "(Ljava/lang/String;I)V");
    env->GetJavaVM(&g_pJVM);

    return 0;
}

具体分成几个步骤,首先是为android端的函数建构一个Reference,这个Reference是Java跟C++通用的地址, 其中callback就是从Java层传入的回调实现

jCallback = env->NewGlobalRef(call_back);
jclass callbackClazz = env->GetObjectClass(call_back);

接著是找到具体方法的id,这里需要名称跟入参出参的类型都正确无误的对应,否则会出现地址错误,详细的说明可以参考这里

jCallbackMethId =
            env->GetMethodID(callbackClazz, "onUpdateCallback", "(Ljava/lang/String;I)V");

2.2 jni层实际的回调触发函数

我们在上面已经建构了一个名叫onUpdateCallback的Java 方法,接著我们需要把这个这个Java方法封装成C++ 方法,并在调用这个C++方法时调用Java方法

void onUpdateCallback(std::string tag, in p) {
    JNIEnv *pNewEnv = nullptr;
    if (jCallback && jCallbackMethId && g_pJVM) {
        if (g_pProgressJVM->AttachCurrentThread(
                reinterpret_cast(&pNewEnv), NULL) < 0) {
            return;
        }
        // std::string 转 jstring
        jstring js = pNewEnv->NewStringUTF(key.c_str());
        // 调用Java方法:onUpdateCallback
        pNewEnv->CallVoidMethod(jPCallback, jCallbackMethId,
                                js, p);
    }
}

接著在jni层找个合适的地方将建构好onUpdateCallback的Java注册到C++就行,

// 某个方法内部,可以是初始化函数,但需要避免会重复调用的方法
...
{
     int ret = registCallback(onProgressCallback);
}
...

2.3 Java层接口封装

回到刚刚在jni建构了一个注册回调函数的接口

Java_com_test_registerCallback(JNIEnv *env,jclass clazz,object call_back) {

这里的object call_back就是我们在Java层实现的回调函数本体,但是因为需要有固定的地址,因此需要做一个interface的封装,确保传入时候方法已经实例化,而且地址唯一

interface的声明如下

  public static interface ICallback{
        void onCallback(String k, float p);
    };

接著在registerCallback声明的地方将interface做为入参

public static native int registerCallback(IProgressCallback callBack);

到此为止,回调函数的封装已经全数完成了,可以在业务层实现一个简单的回调函数测试一下

testSdk.registerCallback(new testSdk.ICallback() {
            @Override
            public void onCallback(String k, float p) {
                Log.e("[onUpdateCallback][" + k +  "] p: " + p);  
                });
            }
        });

3. 使用iOS Swift方法作为回调函数

一般C++跟Swift交互会需要一个Object C的桥接层,而且C++本身跟ObjectC兼容起来相对容易很多,因此建议就从Object C的桥接层著手

3.1 Object C 桥接层声明

// bridge.h

// 定义callback function 类型
#ifdef __cplusplus
extern "C" {
#endif
    typedef void(*callback_t)(const char * k, float p);
#ifdef __cplusplus
} // extern "C"
#endif

// 注册回调函数
- (int) registCallback: (callback_t) callback;

根据C++ 声明的回调函数声明一个type,并将这个type当成注册函数registCallback的入参

3.2 Object C注册函数的实现

注册函数的实现相对于Android相对单纯,直接调用C++层注册函数即可

// bridge.mm

- (int) setProgressCallback: (callback_t) callback{
    // 调用C++层注册函数
    int ret = test_sdk->setCallback(callback);
    return ret;
}

3.3 Swift层实现回调函数

以下是在Swift中实现一个简单的回调函数示例

test_sdk.setPCallback({(value1, value2) -> Void in
            if let pointer = value1 {
                let swiftString = String(cString: pointer)
                print("p[\(swiftString)]:", value2);
            } else {
                print("CallBack Return None")
            }    
        })

结论

本文章将两种不同平台的方法传入C++层做为回调函数的实现,其实可以发现C++层是不需要进行更动的,主要是需要在桥接层进行函数转换,安卓的部分在JNI实现,iOS则是在ObjectC Bridge实现。目的其实也是相同的,就是将一个Java/Swift方法实例化,并且具有固定的调用地址,提供给C++层进行调用。

你可能感兴趣的:(android,ios,c++,算法,objective-c,swift,java)