4

有没有办法使用 boost 或 std bind() 以便我可以将结果用作 C API 中的回调?这是我使用的示例代码:

#include <boost/function.hpp>
#include <boost/bind/bind.hpp>

typedef void (*CallbackType)();

void CStyleFunction(CallbackType functionPointer)
{
    functionPointer();
}

class Class_w_callback
{
public:
    Class_w_callback()
    {
        //This would not work
    CStyleFunction(boost::bind(&Class_w_callback::Callback, this));
    }
    void Callback(){std::cout<<"I got here!\n";};
};

谢谢!

4

4 回答 4

5

不,没有办法做到这一点。问题在于,C 函数指针基本上只不过是一个指令地址:“到这个地址,然后执行你找到的指令”。您想要带入函数的任何状态都必须是全局的,或者作为参数传递。

这就是为什么大多数 C 回调 API 都有一个“上下文”参数,通常是一个 void 指针,您可以传入该参数,并且仅用于允许您传入所需的数据。

于 2013-09-17T11:49:44.563 回答
3

你不能在可移植的 C++ 中做到这一点。但是,有一些库可以创建类似于闭包的 C 函数。这些库在其实现中包含汇编代码,并且需要手动移植到新平台,但如果它们支持关心的体系结构,它们就可以正常工作。

例如,使用 Bruno Haible 的trampoline库,您可以编写如下代码:

extern "C" {
#include <trampoline.h>
}

#include <iostream>

typedef int (*callback_type)();

class CallbackDemo {
  static CallbackDemo* saved_this;
public:
  callback_type make_callback() {
    return reinterpret_cast<callback_type>(
      alloc_trampoline(invoke, &saved_this, this));
  }

  void free_callback(callback_type cb) {
    free_trampoline(reinterpret_cast<int (*)(...)>(cb));
  }

  void target(){
    std::cout << "I got here, " << this << '\n';
  };

  static int invoke(...) {
    CallbackDemo& me = *saved_this;
    me.target();
    return 0;
  }
};

CallbackDemo *CallbackDemo::saved_this;

int main() {
  CallbackDemo x1, x2;
  callback_type cb1 = x1.make_callback();
  callback_type cb2 = x2.make_callback();
  cb1();
  cb2();
}

请注意,尽管使用了静态成员,但创建的蹦床alloc_trampoline是可重入的:当调用返回的回调时,它首先将指针复制到指定地址,然后使用原始参数调用原始函数。如果代码还必须是线程安全的,saved_this则应使其成为线程本地的。

于 2013-09-17T12:33:30.577 回答
2

这行不通。

问题是它bind返回一个仿函数,即具有operator()成员函数的 C++ 类。这不会绑定到 C 函数指针。您需要的是一个静态或非成员函数,它将this指针存储在全局或静态变量中。当然,this为当前回调找到正确的指针可能是一项不平凡的任务。

于 2013-09-17T11:50:34.067 回答
1

全局变量

正如其他人所提到的,您需要一个全局(静态成员是作为变量成员隐藏的全局),当然,如果您需要多个对象在所述回调中使用不同的参数,它就行不通。

回调中的上下文参数

AC 库可能会提供一个void *或一些类似的上下文。在这种情况下,请使用该功能。

例如,ffmpeg 库支持读取数据的回调,其定义如下:

int(*read_packet)(void *opaque, uint8_t *buf, int buf_size);

opaque参数可以设置为this。在您的回调中,只需将其转换回您的类型(您的班级名称)。

Calback 中的库上下文参数

AC 库可能会使用其对象(struct指针)调用您的回调。假设您有一个名为的库example,它提供一个名为的类型example_t并定义如下回调:

callback(example_t *e, int param);

然后,您可以将您的上下文(也称为this指针)放在该example_t结构中,并在您的回调中取回它。

串行调用

假设您只有一个线程使用该特定 C 库,并且只有在调用库中的函数时才能触发回调(即,您不会在某个随机时间点触发事件),您仍然可以使用全局变量. 您需要做的是在每次调用之前将当前对象保存在全局中。像这样的东西:

object_i_am_working_with = this;
make_a_call_to_that_library();

这样,在回调内部,您始终可以访问object_i_am_working_with指针。这在多线程应用程序或库在后台自动生成事件(即按键、来自网络的数据包、计时器等)时不起作用。

每个对象一个线程 (C++11 起)

这是多线程环境中的一个有趣的解决方案。当您没有以前的解决方案可用时,您可以使用线程来解决问题。

在 C++11 中,有一个新的特殊说明符,名为thread_local. 在过去,您必须手动处理特定于每个线程实现的事情......现在您可以这样做:

thread_local Class_w_callback * callback_context = nullptr;

然后,在您的回调中,您可以将callback_context用作返回Class_w_callback类的指针。

当然,这意味着您需要为每个创建的对象创建一个线程。这在您的环境中可能不可行。就我而言,我的组件都在运行自己的循环,因此每个组件都有自己的thread_local环境。

请注意,如果库自动生成事件,您可能也不能这样做。

带线程的旧方法(和 C 解决方案)

正如我上面提到的,在过去,您必须自己管理本地线程环境。使用pthread(基于 Linux),您可以通过以下方式访问特定于线程的数据pthread_getspecific()

void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);

这利用了动态分配的内存。这可能thread_local是 Linux 下 g++ 的实现方式。

在 MS-Windows 下,您可能会使用TlsAlloc函数。

于 2019-06-19T00:03:16.217 回答