How to write a C++ wrapper class method of a C function that takes callback?

  • A+
Category:Languages

Given the following C interface:

IoT_Error_t aws_iot_mqtt_subscribe(AWS_IoT_Client *pClient,                                    const char *pTopicName,                                    uint16_t topicNameLen,                                    QoS qos,                                    pApplicationHandler_t pApplicationHandler,                                     oid *pApplicationHandlerData); 

"aws_iot_mqtt_subscribe stores its arguments for latter reference - to call, in response to some event at some later point in time"

Handler:

typedef void (*pApplicationHandler_t)(     AWS_IoT_Client *pClient,     char *pTopicName,     uint16_t topicNameLen,     IoT_Publish_Message_Params *pParams,     void *pClientData); 

I am trying to wrap this into a C++ class that would have the following interface:

class AWS { // ... public:   void subscribe(const std::string &topic,                  std::function<void(const std::string&)> callback); // ... }; 

My goal is to make it possible to pass a capturing lambda function to AWS::subscribe. I have been trying with different approaches for a nearly a week now but none of them seemed to work.

Let me know if anything else needed to understand the problem, I'm happy to update the question.

 


The basic approach is to store a copy of callback somewhere, then pass a pointer to it as your pApplicationHandlerData.

Like this:

extern "C" void application_handler_forwarder(     AWS_IoT_Client *pClient,     char *pTopicName,     uint16_t topicNameLen,     IoT_Publish_Message_Params *pParams,     void *pClientData ) {     auto callback_ptr = static_cast<std::function<void(const std::string&)> *>(pClientData);     std::string topic(pTopicName, topicNameLen);     (*callback_ptr)(topic); } 

This is your (C compatible) generic handler function that just forwards to a std::function referenced by pClientData.

You'd register it in subscribe as

void AWS::subscribe(const std::string &topic, std::function<void(const std::string&)> callback) {     ...     aws_iot_mqtt_subscribe(pClient, topic.data(), topic.size(), qos,          application_handler_forwarder, &copy_of_callback); 

where copy_of_callback is a std::function<const std::string &)>.

The only tricky part is managing the lifetime of the callback object. You must do it manually in the C++ part because it needs to stay alive for as long as the subscription is valid, because application_handler_forwarder will be called with its address.

You can't just pass a pointer to the parameter (&callback) because the parameter is a local variable that is destroyed when the function returns. I don't know your C library, so I can't tell you when it is safe to delete the copy of the callback.


N.B: Apparently you need extern "C" on the callback even if its name is never seen by C code because it doesn't just affect name mangling, it also ensures the code uses the calling convention expected by C.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: