关于函数指针的一些问题小结

最近接到一个需求,使用 sdk 提供的消息回调,一般我们是继承 sdk 的消息类,然后 sdk 的消息回调(虚函数)会在有消息的时候调用回调指针,从而触发回调

不过因为 sdk 那边又对该消息类二次封装了并提供了一些接口,所以在研究二次封装的方法时,遇到了一些有意思的问题,故记录下

typedef void(__stdcall* fSDKLogCallback)(void* ctx, int32_t severity,
                                         const char* message,
                                         uint32_t message_size);

  

std::pair<void*, fSDKLogCallback> sdk_log_cb_;

在编译时,如果删除 __stdcall 调用约定后,

typedef void* fSDKLogCallback(void* ctx, int32_t severity,
                                         const char* message,
                                         uint32_t message_size);

程序会在 std::pair 处报错

 error C2207: 'std::pair<_Ty1,_Ty2>::second': a member of a class template cannot acquire a function type

这是因为 std::pair 的模板参数要求是可复制构造的类型,而函数类型是不可复制构造的。因此,如果你直接使用没有 __stdcall 的函数指针类型作为 std::pair 的模板参数之一,会导致编译错误。

通过将函数指针类型添加 __stdcall 关键字,你可以将其定义为可复制构造的类型,并在 std::pair 的模板参数中使用它,从而避免编译错误。

补充:

__stdcall 是一种调用约定,它指定了函数调用时参数的传递方式、栈的清理方式以及函数调用的规则。在某些情况下,特别是在 Windows 平台的一些 API 中,函数指针的类型定义可能要求使用 __stdcall 调用约定。

当你定义一个函数指针类型 fSDKLogCallback 时,如果你的实际使用场景要求使用 __stdcall 调用约定,那么你必须在函数指针类型的定义中显式添加 __stdcall 关键字。

后续:

要使用二次封装的接口,首先要将我们自己定义的回调函数地址传给接口

// 声明
void SetSDKLogCallback(void* ctx, fSDKLogCallback cb);

// 定义
void XXX::SetSDKLogCallback(void* ctx, fSDKLogCallback cb) {
  sdk_log_cb_.first = ctx;
  sdk_log_cb_.second = cb;
}

在原始 sdk 的回调里直接调用该地址

void on_sdk_log(int severity, const char* message,
                size_t message_size) override {
  if (sdk_log_cb_.second) {
    sdk_log_cb_.second(sdk_log_cb_.first, severity, message,
                       (uint32_t)message_size);
  }
}

最终在我们项目中使用

// interface_ptr 是二次封装类的接口指针
// onSDKLog 是我们自己定义的回调
interface_ptr->SetSDKLogCallback(this, onSDKLog);

void ::onSDKLog(void* ctx, int severity, const char* message,
                              size_t message_size) {
  VLOG(1) << message;
}

  

 

posted @ 2023-06-14 18:45  strive-sun  阅读(23)  评论(0编辑  收藏  举报