将指向C ++成员函数的指针传递给C API库

时间:2018-12-06 01:05:03

标签: c++ c pointers callback

这是我的第一篇文章,我试图看一下以前的类似文章,但它们似乎都不起作用或做我想做的事。

我有一些C代码,将其称为library.c。我删除了很多代码并对其进行了简化。

// libary.c
// A callback function that takes an int
void (*library_cb)(int);

void init(void (*cb())) {
    // some other code
    library_cb = cb;
}

void sample() {
    int data;
    // execute a bunch of code and then call the callback function
    (*library_cb)(data);
}

现在,我有了c ++代码,该代码定义了我想传递给library.c中代码的回调函数。

// someclass.cpp

class SomeClass {
    public:
        SomeClass() {

        };

        ~SomeClass() {

        };

        void callback(int data) {
            // Do some stuff
        }
};

然后在main.cpp中,我想做类似的事情

// main.cpp
extern "C" {
    #include "library.h"
}
#include "someclass.h"

SomeClass some_class;

int main() {
    init(&some_class.callback) // obviously doesn't work

    while(true) {
        sample(); // this would call callback in SomeClass
    }
}

现在我知道一种解决方案是将回调定义为

static void callback(int data)

但是我想知道是否还有其他方法可以做到这一点。从我阅读的内容来看,std :: function可能会有所帮助,或者std :: mem_fn可能有所帮助。但是我似乎不知道怎么做。

我还没有包含头文件,而是以这段代码为例说明了我的问题,因此可能存在一些语法错误,但希望问题/目标很明确。

编辑:

我应该提到我可以编辑c库。

阅读答案,似乎我可以更改c库,使其也接受指向类对象的void *指针,以使它起作用。有人可以给我举个例子吗?我是将c代码与c ++交互的超级新手。

2 个答案:

答案 0 :(得分:3)

  

将指向C ++成员函数的指针传递给C API库

...是不可能的。

  

据我了解,std :: function可能会有所帮助,或者std :: mem_fn

这些都不能用C来调用,但要一直读到最后。

C仅具有常规的非成员函数指针,因此它们是C程序可以调用的唯一函数指针。在C ++中,此类指针可以指向自由函数或静态成员函数。

在此类静态或非成员函数的C ++实现中,您当然可以在C ++的能力范围内做任何事情(尽管让异常转义该函数是不好的),因此您确实可以调用非静态的那里的成员函数。

但是要调用非静态成员函数,需要有一个实例。静态对象是一个简单的解决方案,但它不是非常灵活,仅在某些情况下有用。

在C中精心设计的回调API使API的用户除了注册了函数指针外,还注册了通用数据指针(即void*),并且该数据指针将转发至回调。这种设计允许回调具有状态-状态存储在指向的对象中。使用这种C API时,您可以将指针传递给对象,然后可以调用其成员函数。或者,您可以将数据指针传递到std::function或其他一些有状态类型擦除函数包装器,并使用简单的通用函数将调用转发给包装器。

答案 1 :(得分:1)

您的C API不太可用。这是我的处理方式:

  1. 回调必须至少使用用户提供的void*参数,库不会以任何方式解释该参数。没有此参数,回调是无用的。是的,他们真的没用,您的API用户的用户会为此而恨您

  2. 如果您希望回调能够修改其参数的值,则可以传递void*参数的地址。这对于例如注册分配和类似用途,其中参数在回调执行期间更改。这使得库与指针的使用完全脱钩:不仅它不解释指针,而且不保持其值不变。

  3. 库API符号均带有前缀,以防止在全局命名空间中发生冲突。

  4. Typedef用于确保代码可读。键入函数指针类型充其量是乏味的。

  5. 标头应避免出现多重包含,即必须可以在翻译单元中多次包含标头,而不会出现任何错误。

  6. 在C ++转换单元中编译时,标头声明C接口,因为,好:该接口是C接口。 C ++会破坏符号名称,并且标头将声明二进制不兼容的符号。

  7. 标题declares the C interface noexcept in C++11。这为C ++用户提供了优化机会。

  8. 考虑该库注册多个回调,以及可能在注册和注销时调用该回调:这使得与其他编程语言的互操作更加容易。

library.h-可在C和C ++中使用

#pragma once

#ifdef __cplusplus
extern "C" {
#pragma GCC diagnostic push
// clang erroneously issues a warning in spite of extern "C" linkage
#pragma GCC diagnostic ignored "-Wc++17-compat-mangling"
#endif

#ifndef LIBRARY_NOEXCEPT
#if __cplusplus >= 201103L
// c.f. https://stackoverflow.com/q/24362616/1329652
#define LIBRARY_NOEXCEPT noexcept
#else
#define LIBRARY_NOEXCEPT
#endif
#endif

enum library_register_enum { LIBRARY_REG_FAILURE = 0, LIBRARY_REG_SUCCESS = 1, LIBRARY_REG_DUPLICATE = -1 };
enum library_call_enum { LIBRARY_SAMPLE, LIBRARY_REGISTER, LIBRARY_DEREGISTER };
typedef enum library_register_enum library_register_result;
typedef enum library_call_enum library_call_type;
#if __cplusplus >= 201103L
void library_callback_dummy(library_call_type, int, void**) LIBRARY_NOEXCEPT;
using library_callback = decltype(&library_callback_dummy);
#else
typedef void (*library_callback)(library_call_type, int, void**);
#endif

void library_init(void) LIBRARY_NOEXCEPT;
library_register_result library_register_callback(library_callback cb, void *cb_param) LIBRARY_NOEXCEPT;
void library_deregister_callback(library_callback cb, void *cb_param) LIBRARY_NOEXCEPT;
void library_deregister_any_callback(library_callback cb) LIBRARY_NOEXCEPT;
void library_deregister_all_callbacks(void) LIBRARY_NOEXCEPT;
void library_deinit(void) LIBRARY_NOEXCEPT;

void library_sample(void) LIBRARY_NOEXCEPT;

#ifdef __cplusplus
#pragma GCC diagnostic pop
}
#endif

以下请注意,私有数据和函数(即不属于API的私有数据和函数)已声明为(static)。

library.c-实现

#include "library.h"
#include <stdlib.h>

typedef struct callback_s {
   struct callback_s *next;
   library_callback function;
   void *parameter;
} callback;

static callback *cb_head;

void library_init(void) { /* some other code */
}
void library_deinit(void) { library_deregister_all_callbacks(); }

library_register_result library_register_callback(library_callback cb, void *cb_param) {
   callback *el = cb_head;
   while (el) {
      if (el->function == cb && el->parameter == cb_param) return LIBRARY_REG_DUPLICATE;
      el = el->next;
   }
   el = malloc(sizeof(callback));
   if (!el) return LIBRARY_REG_FAILURE;
   el->next = cb_head;
   el->function = cb;
   el->parameter = cb_param;
   cb_head = el;
   cb(LIBRARY_REGISTER, 0, &el->parameter);
   return LIBRARY_REG_SUCCESS;
}

static int match_callback(const callback *el, library_callback cb, void *cb_param) {
   return el && el->function == cb && el->parameter == cb_param;
}

static int match_any_callback(const callback *el, library_callback cb, void *cb_param) {
   return el && el->function == cb;
}

static int match_all_callbacks(const callback *el, library_callback cb, void *cb_param) {
   return !!el;
}

typedef int (*matcher)(const callback *, library_callback, void *);

static void deregister_callback(matcher match, library_callback cb, void *cb_param) {
   callback **p = &cb_head;
   while (*p) {
      callback *el = *p;
      if (match(el, cb, cb_param)) {
         *p = el->next;
         el->function(LIBRARY_DEREGISTER, 0, &el->parameter);
         free(el);
      } else
         p = &el->next;
   }
}

void library_deregister_callback(library_callback cb, void *cb_param) {
   deregister_callback(match_callback, cb, cb_param);
}

void library_deregister_any_callback(library_callback cb) {
   deregister_callback(match_any_callback, cb, NULL);
}

void library_deregister_all_callbacks(void) {
   deregister_callback(match_all_callbacks, NULL, NULL);
}

void library_sample(void) {
   int data = 42;
   // execute a bunch of code and then call the callback function
   callback *el = cb_head;
   while (el) {
      el->function(LIBRARY_SAMPLE, data, &el->parameter);
      el = el->next;
   }
}

这样,注册回调的用户可以将任意数据传递给回调。使用库的代码将在C ++中实现,如下所示:

// https://github.com/KubaO/stackoverflown/tree/master/questions/c-cpp-library-api-53643120
#include <iostream>
#include <memory>
#include <string>
#include "library.h"

struct Data {
   std::string payload;
   static int counter;
   void print(int value) {
      ++counter;
      std::cout << counter << ": " << value << ", " << payload << std::endl;
   }
};

int Data::counter;

extern "C" void callback1(library_call_type type, int value, void **param) noexcept {
   if (type == LIBRARY_SAMPLE) {
      auto *data = static_cast<Data *>(*param);
      data->print(value);
   }
}

using DataPrintFn = std::function<void(int)>;

extern "C" void callback2(library_call_type type, int value, void **param) noexcept {
   assert(param && *param);
   auto *fun = static_cast<DataPrintFn *>(*param);
   if (type == LIBRARY_SAMPLE)
      (*fun)(value);
   else if (type == LIBRARY_DEREGISTER) {
      delete fun;
      *param = nullptr;
   }
}

void register_callback(Data *data) {
   library_register_callback(&callback1, data);
}

template <typename F>
void register_callback(F &&fun) {
   auto f = std::make_unique<DataPrintFn>(std::forward<F>(fun));
   library_deregister_callback(callback2, f.get());
   library_register_callback(callback2, f.release());
   // the callback will retain the functor
}

int main() {
   Data data;
   data.payload = "payload";

   library_init();
   register_callback(&data);
   register_callback([&](int value) noexcept { data.print(value); });

   library_sample();
   library_sample();
   library_deinit();  // must happen before the 'data' is destructed
   assert(data.counter == 4);
}