Ваш C API не очень полезен.Вот как я это сделаю:
Обратный вызов должен по крайней мере принимать предоставленный пользователем параметр void*
, который библиотека никоим образом не интерпретирует.Без этого параметра обратные вызовы бесполезны.Да, они действительно бесполезны , и пользователи ваших пользователей API будут ненавидеть вас за это.
Если вы хотите, чтобы обратный вызов былВ состоянии изменить значение его параметра, вы можете передать адрес параметра void*
.Это полезно, например, для распределения при регистрации и аналогичного использования, когда параметр изменяется во время выполнения обратного вызова.Это делает библиотеку полностью отделенной от использования указателя: она не только не интерпретирует указатель, но и не сохраняет его значение постоянным.
Символы API библиотекивсе префиксы предотвращают конфликты в глобальном пространстве имен.
При необходимости используются typedef для обеспечения читабельности кода.Распечатка типов указателей на функции в лучшем случае утомительна.
Заголовок защищен от многократного включения, т. Е. Он должен быть в порядке, чтобы включить его несколько раз в блок перевода, без каких-либо ошибок.
Заголовок объявляет интерфейс C при компиляции в модуле перевода C ++, так как, хорошо: интерфейс - C.C ++ искажает имена символов, и заголовок объявляет двоичные несовместимые символы.
Заголовок объявляет интерфейс C noexcept
в C ++ 11 .Это открывает возможности оптимизации для пользователей C ++.
Рассмотрим библиотеку, регистрирующую более одного обратного вызова, а также, возможно, вызывающую обратный вызов при регистрации и отмене регистрации: они значительно улучшают взаимодействие с другими языками программированияпроще.
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);
}