Передача указателя на функцию-член C ++ в библиотеку C API - PullRequest
0 голосов
/ 06 декабря 2018

Первый пост здесь, и я попытался просмотреть предыдущие подобные посты, но ни один из них, кажется, не работает или делает то, что я хочу.

У меня есть некоторый код на 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 голосов
/ 06 декабря 2018

Ваш C API не очень полезен.Вот как я это сделаю:

  1. Обратный вызов должен по крайней мере принимать предоставленный пользователем параметр void*, который библиотека никоим образом не интерпретирует.Без этого параметра обратные вызовы бесполезны.Да, они действительно бесполезны , и пользователи ваших пользователей API будут ненавидеть вас за это.

  2. Если вы хотите, чтобы обратный вызов былВ состоянии изменить значение его параметра, вы можете передать адрес параметра void*.Это полезно, например, для распределения при регистрации и аналогичного использования, когда параметр изменяется во время выполнения обратного вызова.Это делает библиотеку полностью отделенной от использования указателя: она не только не интерпретирует указатель, но и не сохраняет его значение постоянным.

  3. Символы API библиотекивсе префиксы предотвращают конфликты в глобальном пространстве имен.

  4. При необходимости используются typedef для обеспечения читабельности кода.Распечатка типов указателей на функции в лучшем случае утомительна.

  5. Заголовок защищен от многократного включения, т. Е. Он должен быть в порядке, чтобы включить его несколько раз в блок перевода, без каких-либо ошибок.

  6. Заголовок объявляет интерфейс C при компиляции в модуле перевода C ++, так как, хорошо: интерфейс - C.C ++ искажает имена символов, и заголовок объявляет двоичные несовместимые символы.

  7. Заголовок объявляет интерфейс C noexcept в 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);
}
0 голосов
/ 06 декабря 2018

Передача указателя на функцию-член C ++ в библиотеку C API

... невозможна.

Из того, что я прочитал, std:: функция могла бы помочь или std :: mem_fn

Ни один из них не может быть вызван в C, но продолжайте читать 'до конца.

C имеет только регулярный не-членуказатели на функции, так что это единственные указатели на функции, которые может вызывать C-программа.В C ++ такой указатель может указывать на свободную функцию или статическую функцию-член.

В реализации C ++ такой статической функции или функции, не являющейся членом, вы, конечно, можете делать все что угодно в рамках C ++ (хотяесли позволить исключению избежать функции, это будет плохо), так что вы действительно можете вызвать нестатическую функцию-член.

Но для вызова нестатической функции-члена должен существовать экземпляр.Статический объект является тривиальным решением, но он не очень гибок и полезен только в нескольких ситуациях.

Хорошо спроектированные API обратного вызова в C позволяют пользователю API регистрировать общий указатель данных (т. Е. void*) вдополнение к указателю на функцию, и этот указатель данных будет перенаправлен на обратный вызов.Этот дизайн позволяет обратным вызовам быть с состоянием - состояние сохраняется в указанном объекте.При использовании такого C API вы можете передать указатель на объект, функции-члены которого затем могут вызвать обратный вызов.Или вы могли бы передать указатель данных на std::function или какой-либо другой упаковщик функции стирания с сохранением типа и использовать универсальную бесплатную функцию, которая просто перенаправляет вызов оболочке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...