3

I've been trying to figure out a way to change a type of function registered with a C-callback, to conform with an interface I am implementing.

C-callback (external lib, can't be changed):

typedef void* handle_t;
register_callback(void(*callback)(handle_t handle));

My class:

MyClass::MyClass(std::function<void()> callback) { // <- interface forces this parameter
    register_callback(callback);// <- this is the idea.
}

I want user to provide void(void) function and register it with this callback.

Reason: standardized implementation of an interface. Interface defines "std::function<void()>" parameter, so I can't use void(void*) as a parameter. This is ok, because that handle_t parameter in callback is completely useless, as a user has access to this handle via my wrapper class.

Obviously I can't register void(void) with void(void*) callback, so I need some sort of a wrapper. The problem is, I can't create this wrapper as a member function inside my class, as they can't be passed to a C-callback.

I've been playing with an idea of using lambdas like this:

MyClass::MyClass(std::function<void()> callback) : callback_(std::move(callback)) {
    std::function<void(handle_t)> callback_wrapper = [&](handle_t) { callback_(); };
    register_callback(callback);
}

But this lambda probably won't work, because it is created inside constructor and will be freed on exit. Ideally I would have it inlined, so the lambda itself isn't called, but it is replaced with my desired callback function.

Will gladly accept any guidance on how to tackle this problem.

user16217248
  • 3,119
  • 19
  • 19
  • 37
vict
  • 158
  • 1
  • 11
  • 1
    The bigger problem why `[&](handle_t) { callback_(); };` won't work is because that lambda cannot be converted to a function pointer. Only non-capturing lambdas can be converted to function pointers – UnholySheep Jul 23 '22 at 21:03
  • 1
    What's `handle`? Can you control what value gets passed, or does that come from somewhere else? – Stephen Newell Jul 23 '22 at 21:04
  • @UnholySheep you are absolutely right. I've written it to showcase the idea, while being fully aware it won't work easily like that. – vict Jul 23 '22 at 21:07
  • 2
    Bad news: this C API cannot be used with C++ classes in any convenient and meaningful way. – Sam Varshavchik Jul 23 '22 at 21:08
  • @StephenNewell handle_t is just a typedef for void*. What value are you asking about? If you are talking about std::function parameter, then it can't be changed easily as it as a part of interface that I am implementing. – vict Jul 23 '22 at 21:09
  • @SamVarshavchik I am fearing the worst case scenario, which will require changing the whole interface, to allow for registering of std::function callbacks. But that won't be nice... – vict Jul 23 '22 at 21:10
  • I'm talking about the actual value `handle` will have when the function is called. `this` can be cast to `void*` and back, but that only works if you can control the actual value that'll be passed. – Stephen Newell Jul 23 '22 at 21:11
  • Well, I can't say anything about that. I can only say that this is fundamentally incompatible with C++. – Sam Varshavchik Jul 23 '22 at 21:12
  • Either do as Stephen Newell wrote, or you might have N callback functions generated (via e.g. macros), which you can think of as resources. For each of them, you can have a static pointer to pass `this`. This works if you have relatively few callbacks. – lorro Jul 23 '22 at 21:14
  • @StephenNewell Oh, sadly not. This handle is a pointer to a structure containing configuration. I have no control over it and I can't make it return this pointer. Theoretically it is possible to create a std::map that hold relations between those handles and "thises", which actually might not be a bad idea when I think about this (while being a little bit liberal on memory side though). – vict Jul 23 '22 at 21:15
  • 2
    @user16750834 - *Theoretically it is possible to create a std::map that hold relations between those handles and "thises"* -- This isn't just theory, it is used in real life. The Microsoft MFC wrapper for the Windows API uses this approach in some aspects. The bottom line is that whatever it takes to make the `C` function pointer-ness into a C++ app, it is a valid approach. No one can say you are right or wrong. – PaulMcKenzie Jul 23 '22 at 21:21
  • @user16750834 "*This handle is a pointer to a structure containing configuration*" - does that include user-defined data? If so, that will make using this API in a C++ class much easier. – Remy Lebeau Jul 23 '22 at 22:43
  • 1
    People who design (C) 'register callback' API's that don't take a `context` pointer are a public nuisance. We seem to see a lot of it here (I'm looking at you, `qsort`). – Paul Sanders Jul 23 '22 at 23:20
  • *"I want user to provide void(void) function and"* -- more precisely, you want the user to provide a `void(void)` function **object**. A `std::function` is an object, and it need not correspond directly to a function. – JaMiT Jul 24 '22 at 00:14
  • @user16750834 *"This handle is a pointer to [...]"* -- this information should be part of the question, so that one does not need to dig through the ephemeral comments to discover it. (This might seem like side information, but it is actually rather key. See,for example, [wrapping a C-function with a static callback parameter to a C++-function which accepts a private member as callback](https://stackoverflow.com/questions/32415873/).) – JaMiT Jul 24 '22 at 00:28
  • 1
    Does this answer your question? [What's the best way to wrap a C callback with a C++11 interface?](https://stackoverflow.com/questions/18169180/whats-the-best-way-to-wrap-a-c-callback-with-a-c11-interface) – JaMiT Jul 24 '22 at 00:33
  • Much of the time, a callback will have a mechanism to pass custom data to it. Make a static function (instance functions automatically get access to `this` often altering what c would perceive as the function signature) in the class and arrange the instance pointer to be fed. If there is no such mechanism, you will have to arrange your own (such as only having one global instance of the class able to use the callback at a time) – Abel Jul 24 '22 at 01:02

1 Answers1

0

This is quite a tricky problem. Unfortunately, simply creating a lambda will not work because the C function is not going to be able to access the lambda capture. The only way to wrap the functions for the C function is to create a new wrapper function for every function you register.

This could be obtained with templates or macros:

template <void (*callback)(handle_t)> MyClass::MyClass() {
    register_callback([](){callback(nullptr);});
}
#define REGISTER(callback) register_callback([](){(callback)(nullptr);});

Alternatively, depending on your needs, you could create your own registry and then register a function to the external registry that simply calls every single function from your own registry:

std::vector<void(*)(void)> my_registry = {};

register_callback([](handle_t unused){ // Call once in main/init function
    for (void(*func)(void) : my_registry)
        func();
});

MyClass::MyClass(void(*callback)(void)) {
    my_registry.push_back(callback);
}
user16217248
  • 3,119
  • 19
  • 19
  • 37