I am trying to implement a wrapper for a library called libumqtt. Libumqtt is a C library that uses libev to have callbacks for events from the MQTT protocol.
What I didn't realize until the other day is that I cannot pass a member function to a function that expects a normal, static function. This causes problems as I was planning on launching multiple instances of libumqtt to handle multiple connections at the same time.
My code is in C++ as that is the most convenient to use with the Godot's (a game engine) GDNative module.
While researching for either a way to sandbox multiple instances of a c library or to somehow get the pointers to work anyway, I found this answer. I do not understand this quote from the answer:
If you need to access any non-static member of your class and you need to stick with function pointers, e.g., because the function is part of a C interface, your best option is to always pass a void* to your function taking function pointers and call your member through a forwarding function which obtains an object from the void* and then calls the member function.
What I am trying to do is setup callbacks that libev will use to send the data to the right instance of my object when it is handling potentially up to 500 or more connections simultaneously.
Will passing void* help me with my goals and how would I implement this? Also, how does a forwarding function work?
Edit: To Provide Code Example That Walnut Is Asking For
This example below comes from a version of my class that uses static functions. If I tried to use run this when the functions are not static, then I would get an error about not being able to pass in a member function in place of a regular function.
// Client.cpp
void Client::signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
ev_break(loop, EVBREAK_ALL);
}
// ...
void Client::do_connect(struct ev_loop *loop, struct ev_timer *w, int revents) {
//Godot::print("Attempt MQTT Start!!!\n");
//write_log("debug", "MQTT Wrapper - Attempt MQTT Start!!!");
struct umqtt_client *cl; // Move to Class Access (Private)?
cl = umqtt_new(loop, cfg.host, cfg.port, cfg.ssl);
if (!cl) {
//Godot::print("Failed To Create Client!!!\n");
//write_log("debug", "MQTT Wrapper - Failed To Create Client!!!");
start_reconnect(loop);
return;
}
//Godot::print("Setup Client Callbacks!!!\n");
//write_log("debug", "MQTT Wrapper - Setup Client Callbacks!!!");
// For StackOverflow: These cl->... lines do not work because of not being able to pass a member function as a regular function. These are the main callbacks I have trouble with.
// How do I convert from `void (libumqtt::Client::*)(struct umqtt_client *)` to `void (*)(struct umqtt_client *)`?
cl->on_net_connected = Client::on_net_connected; // Pass member function as a non-static object
cl->on_conack = Client::on_conack; // Pass member function as a non-static object
cl->on_suback = Client::on_suback; // Pass member function as a non-static object
cl->on_unsuback = Client::on_unsuback; // Pass member function as a non-static object
cl->on_publish = Client::on_publish; // Pass member function as a non-static object
cl->on_pingresp = Client::on_pingresp; // Pass member function as a non-static object
cl->on_error = Client::on_error; // Pass member function as a non-static object
cl->on_close = Client::on_close; // Pass member function as a non-static object
//Godot::print("MQTT Start!!!\n");
//write_log("debug", "MQTT Wrapper - MQTT Start!!!");
}
void Client::initialize() {
// For StackOverflow: These two lines cannot work in an object as libev expects signal_cb and do_connect to be regular functions. These callbacks are also necessary, but I am not sure how to handle this.
ev_signal_init(&signal_watcher, Client::signal_cb, SIGINT);
ev_timer_init(&reconnect_timer, Client::do_connect, 0.1, 0.0); // Fix Me - Make ev.h object
// ...
}
Edit: I should mention I am a noob at using C and C++. The most I've done in it before is testing a buffer overflow. So, if their's anything I am obviously doing wrong, I would appreciate the tip in the comments.
So the issue is that
umqtt_clientdoes not seem to provide any way of passing additional user data to the callback (thevoid*mentioned in your quote). It expects the callback to take just a pointer to theumqtt_clientinstance. (I may be wrong here, I am basing this just on a quick look at the source files.)If your member functions don't actually access any non-static member of your class, then you can simply make them
static. Then you can use them directly as normal function pointers.Otherwise you need to obtain a pointer to your instance from the
umqtt_client*pointer. One way of doing this would be to maintain a static map between the pointers, e.g. inClientadd a declaration:and insert into it when creating a
Client(I will assume here that you actually maintain theclpointer as class member ofClient), preferably inClient's constructor:Then in
Client's destructor (or where ever theumqtt_clientobject is destroyed) erase the corresponding element from the map:Then you can use a lambda looking like this to pass as callback:
Note that
on_net_connectedwon't need the pointer as argument if it is a member of the class.This also assumes that you make the class non-copyable and non-movable or that you implement the copy- and move-constructor and -assignment operators with the correct semantics of erasing and inserting into
umqtt_client_mapas well.The library seems to offer a function
umqtt_initinstead ofumqtt_newthat doesn't allocate theumqtt_clientobject. If you use that instead you could do the following:Wrap the
umqtt_clientin a small standard-layout class:You would then use that as member of
Clientinstead ofumqtt_client*directly and initialize theumqtt_client*withumqtt_init) andclientwiththisinClient`'s constructor. Then you can use a cast in the lambda for the callback:Note that this depends on
umqtt_client_wrapperbeing standard-layout and thatumqtt_client*is its first member. Not following these rules will cause undefined behavior. Thestatic_assertgives some assurance that at least part of it is not accidentally violated. It requires#include<type_traits>and C++17 in the form that I used here.Again this requires special care to implement the copy- and move- special member functions of
Clientcorrectly or to delete them, but with this method no action in the destructor is required.This approach is more performant than the other one and in principle you could avoid the extra
Clientpointer if you make sure thatClientitself is standard-layout, but that is probably too restrictive and risky.Another way, saving the extra indirection, is to use the wrapper as a base class of
Client:Then let
Clientinherit fromumqtt_client_wrapperand you can use:Note that here the first cast must be
static_cast, otherwise you could easily cause undefined behavior.The same remarks as before apply.