这是我的第一篇文章,我试图看一下以前的类似文章,但它们似乎都不起作用或做我想做的事。
我有一些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 ++交互的超级新手。
答案 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不太可用。这是我的处理方式:
回调必须至少使用用户提供的void*
参数,库不会以任何方式解释该参数。没有此参数,回调是无用的。是的,他们真的没用,您的API用户的用户会为此而恨您。
如果您希望回调能够修改其参数的值,则可以传递void*
参数的地址。这对于例如注册分配和类似用途,其中参数在回调执行期间更改。这使得库与指针的使用完全脱钩:不仅它不解释指针,而且不保持其值不变。
库API符号均带有前缀,以防止在全局命名空间中发生冲突。
Typedef用于确保代码可读。键入函数指针类型充其量是乏味的。
标头应避免出现多重包含,即必须可以在翻译单元中多次包含标头,而不会出现任何错误。
在C ++转换单元中编译时,标头声明C接口,因为,好:该接口是C接口。 C ++会破坏符号名称,并且标头将声明二进制不兼容的符号。
标题declares the C interface noexcept
in C++11。这为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
)。
#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);
}