您的C API不太易用。这是我会这样做的:
回调函数必须至少接受一个用户提供的 void*
参数,库不会以任何方式解释它。没有这个参数,回调函数就是无用的。是的,他们真的是无用的,你的API用户会讨厌你。
如果您希望回调函数能够修改其参数的值,可以传递 void*
参数的地址。这对于例如在注册时进行分配等参数在回调执行期间更改的情况非常有用。这使得库与指针的使用完全解耦:它不仅不解释指针,而且不保持其值的恒定。
库API符号都有前缀,以防止全局命名空间中的冲突。
必要时使用typedef确保可读性。手动输入函数指针类型很繁琐。
头文件受到多重包含的保护,即在一个翻译单元中多次包含它应该是可以的,而不会出现任何错误。
当在C++翻译单元中编译时,头文件声明了C接口,因为接口是C接口。C++会对符号名称进行编码,头文件将声明不兼容的二进制符号。
头文件在C++11中声明了C接口noexcept
。这为C++用户提供了优化机会。
考虑库注册多个回调函数,以及可能在注册和注销时调用回调函数:这使得与其他编程语言的互操作性更加容易。
library.h - 可用于 C 和 C++
#pragma once
#ifdef __cplusplus
extern "C" {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc++17-compat-mangling"
#endif
#ifndef LIBRARY_NOEXCEPT
#if __cplusplus >= 201103L
#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) {
}
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;
callback *el = cb_head;
while (el) {
el->function(LIBRARY_SAMPLE, data, &el->parameter);
el = el->next;
}
}
那么,注册回调的用户可以向回调函数传递任意数据。库使用的代码应该按照以下方式在C++中实现:
#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());
}
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();
assert(data.counter == 4);
}
void *
作为额外的回调参数,或者允许你在方便的地方将void *
放进去。你可以使用它来存储附加的元数据并使自己逐步转向正确的C++方法调用。 - Sam Varshavchikthis
作为一个不透明的void *
传递给C库,同时还要传递一个指向你的类的static
成员函数的指针。这个成员函数将接收到C库返回的void *
作为参数,并将其重新转换回对象指针,然后调用对象的方法。例如,这是一个从gnutls C库调用的回调函数,它会调用一个C++对象的方法。 - Sam Varshavchik