Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing stateful lambda into C style function without context-argument

I'm trying to integrate a C library into my C++ project. The C library has functions which take function pointers as arguments but these function pointers are written as typedefs.

typedef void(*FileHandler_t)(File* handle);

Then a function to register the callback like so:

void RegisterCallback(FileHandler_t handler);

I can create a lambda expression and pass that into RegisterCallback for the argument handler

auto handler = [](File* handle){ //handle cb };

And this works fine.

RegisterCallback(handler);

But when I try to pass local variables to be used inside the handler I receive compiler errors.

auto handler = [&container](File* handle){ //handle cb };

Now RegisterCallback no longer compiles. I wanted to do it this way because I didn't want to use global variables. Is there anyway around the error which is formally used in these scenarios?

From what I can see, there's no way around this other than either modifying the library itself.

like image 600
Irelia Avatar asked Oct 12 '18 11:10

Irelia


3 Answers

Normally, designers of C libraries that use callbacks like this provide some way to associate a state variable to the callback. Often a void* argument. Some libraries take that state pointer when you pass the callback, and pass the pointer to the callback function, e.g. WinAPI usually does that, see EnumWindows. Other libraries allow to place that thing into some object they’re passing to the callback, e.g. libpng does that with png_set_write_fn and png_get_io_ptr API functions.

If after reading available documentation you concluded that’s not the case for your library, and you can’t or don’t want to ask library author for support, this means you have to be more creative.

One workaround is using a hash map to associate files with containers. Like this:

static std::unordered_map<File*, Container*> s_containers;

Associate the file with container before RegisterCallback, and in the callback, lookup by file pointer to find out the container. Think about threading, maybe you need to guard that static hash map with a mutex. Also think about exception handling, maybe you need RAII class to register in constructor/unregister in destructor.

Another, much simpler but more limited method, is using thread_local storage class specifier introduced in C++/11, declare

static thread_local Container* s_container;

And use it in the callback. If your library does blocking IO and doesn’t internally use threading, there’s a good chance this will work OK. But still you need to handle errors, i.e. reset the global variable to nullptr when container goes out of scope.

Update: If you can change the library, doing so is much better than both workarounds. Pass one more void* argument to RegisterCallback, and change handler to typedef void(*FileHandler_t)(File* handle, void* context); If you’re consuming the library from C++, it’s usually good idea to implement the callback as private static method, and pass this pointer into the library. This will allow you to call instance methods in the callback while keeping internal state of your class hidden.

like image 152
Soonts Avatar answered Sep 19 '22 02:09

Soonts


Lambdas normally have a type you can't rely on; only in the case that they don't capture anything, does the type "decay to" (equal) a function pointer.

The most common workaround is to use static data in the lambda instead:

#include <iostream>
#include <vector>

typedef void(*FuncInt)(int);

static bool DataTheFunctionNeeds = true;

int main() {
    FuncInt a = [](int) { };
    FuncInt b = [](int) { if(DataTheFunctionNeeds){} };
}
like image 37
TamaMcGlinn Avatar answered Sep 21 '22 02:09

TamaMcGlinn


As you mentioned, there is no void* pointer which is normaly used for identification in the callback function.

However, as a workaround you could introduce a class template which stores this information for you. live demo:

template<typename FN>
class dispatcher {
    static inline std::function<void(int)> fn_;

    static void foo(int x) { fn_(x); }

public:
    static fn_type dispatch(FN fn) {
        fn_ = fn;
        return &dispatcher::foo;
    }
};

The class is a template for the purpose to resolve the ambiguity of the member function fn_.

This won't work very well with classes, though. But you can change it so that you provide some sort of own "disambiguiteer". live demo:

template<int DSP>
class dispatcher {
    static inline std::function<void(int)> fn_;

    static void foo(int x) { fn_(x); }

public:
    template<typename FN>
    static fn_type dispatch(FN fn) {
        fn_ = fn;
        return &dispatcher::foo;
    }
};
like image 36
user1810087 Avatar answered Sep 19 '22 02:09

user1810087