简体   繁体   中英

Passing a pointer to a C++ member function to a C API library

First post here and I've tried to look at previous similar posts but none of them seem to work or do quite what I want.

I have some C code, call it library.c. I've removed a lot of the code and simplified it.

// 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);
}

Now I have c++ code that defines the callback function that I want to pass to the code in library.c

// someclass.cpp

class SomeClass {
    public:
        SomeClass() {

        };

        ~SomeClass() {

        };

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

And then in main.cpp I want to do something like

// 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
    }
}

Now I know one solution is to define callback as

static void callback(int data)

But I was wondering if there are any other ways to do this. From what I read, std::function might help or std::mem_fn. But I can't seem to figure out how.

I haven't included the header files and I wrote this code as an example of my problem so there might be some syntax errors, but hopefully the question/goal is clear.

Edit:

I should have mentioned that I can edit the c library.

Reading the answers, it seems I can change the c library to also accept a void* pointer to the class object to get this to work. Could someone show me an example for this case please? I'm super new to interfacing c code with c++.

Passing a pointer to a C++ member function to a C API library

... is not possible.

From what I read, std::function might help or std::mem_fn

Neither of those can be called in C either, but keep reading 'till the end.

C only has regular non-member function pointers so those are the only function pointers that a C program can call. In C++, such pointer can point to either a free function or a static member function.

Within the C++ implementation of such static- or non-member function you can of course do anything within the power of C++ (although, letting an exception escape the function would be bad), so you can indeed call a non-static member function there.

But to call a non-static member function, there needs to be an instance. A static object is a trivial solution, but is not very flexible and only useful in a few situations.

Well designed callback API's in C let user of the API register a generic data pointer (ie void* ) in addition to a function pointer, and that data pointer will be forwarded to the callback. This design allows the callbacks to be stateful - the state is stored in the pointed object. When using such C API, you can pass a pointer to an object whose member functions the callback can then call. Or, you could pass a data pointer to a std::function or some other stateful type erasing function wrapper, and use a generic free function that simply forwards the call to the wrapper.

Your C API is not very usable. Here's how I'd do it:

  1. The callback must at least take a user-provided void* parameter that the library doesn't interpret in any way. Without this parameter, callbacks are useless. Yes, they really are useless and the users of your API users will hate you for that.

  2. If you want the callback to be able to modify the value of its parameter, you can pass the address of the void* parameter. That is useful for eg allocation-on-registration and similar uses where the parameter changes during callback's execution. This makes the library completely decoupled from the use of the pointer: not only it doesn't interpret the pointer, but it doesn't keep its value constant.

  3. The library API symbols are all prefixed to prevent collisions in the global namespace.

  4. Typedefs are used as needed to ensure readable code. Typing out function pointer types is tedious at best.

  5. The header is guarded against multiple-inclusion, ie it must be OK to include it multiple times in a translation unit, without any errors.

  6. The header declares a C interface when compiled in a C++ translation unit, since, well: the interface is a C one. C++ mangles the symbol names and the header will declare binary-incompatible symbols.

  7. The header declares the C interface noexcept in C++11 . This presents optimization opportunities to the C++ users.

  8. Consider the library registering more than one callback, as well as possibly invoking the callback on registration and deregistration: those make interoperation with other programming languages much easier.

library.h - usable from C and 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

Below note that the private data and functions, ie those not part of the API, are declared so ( static ).

library.c - the implementation

#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;
   }
}

That way, the user registering the callback can pass arbitrary data to the callback. The library-using code would be implemented in C++ as follows:

// 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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM