I wrapped a C library's subscribe
function into a higher level C++ class' method. Inside that method the underlying C subscribe
is called with a local char*
variable (char key[10]
) passed in as reference. The problem is now - which I have just figured out - since key
is a local variable its value is not protected. I can pass its reference but that memory will be free once the scope is left. I am experiencing this undefined
behaviour by the callback is never called - after debugging I saw that the value of the key
is altered.
I have tried using new char[10]
which seemed to work. But I guess it's not the way I supposed to go to.
What is the correct solution for this? Update: it is now fixed by replacing with string
.
Update
Interface function:
IoT_Error_t aws_iot_mqtt_subscribe(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen,
QoS qos, pApplicationHandler_t pApplicationHandler, void *pApplicationHandlerData)
Wrapper:
std::function<void()> AWS::subscribe(const std::string &topic, std::function<void(const std::string&)> callback, QoS qos) {
ESP_LOGI(TAG, "subscribe: %s", topic.c_str());
std::string key("Test...");
auto task = c_style_callback(
[=] (AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, IoT_Publish_Message_Params *params) {
std::string json;
json.assign((char *)params->payload, (size_t)params->payloadLen);
ESP_LOGI(TAG, "subscribe cb payload=%s", json.c_str()); // works
ESP_LOGI(TAG, "key '%s'", key.c_str()); // undefined behaviour
callback(key); // error, exit
callback(json);
}
);
m_error = ::aws_iot_mqtt_subscribe(
&m_client,
key.c_str(),
key.length(),
qos,
task.get_callback<AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*>(),
task.get_pvoid()
);
if (m_error != SUCCESS) {
ESP_LOGD(TAG, "subscribe: error=%d", m_error);
return nullptr;
}
return [=] () {
ESP_LOGI(TAG, "unsubscribe %s", key.c_str()); // works
callback(key); // works
};
} // subscribe
c_style_callback
utility function:
template<class F>
struct c_style_callback_t {
F f;
template<class...Args>
static void(*get_callback())(Args..., void*) {
return [](Args...args, void* fptr)->void {
(*static_cast<F*>(fptr))(std::forward<Args>(args)...);
};
}
void* get_pvoid() {
return std::addressof(f);
}
};
template<class F>
c_style_callback_t< std::decay_t<F> >
c_style_callback( F&& f ) { return {std::forward<F>(f)}; }
Main task where the subscribe
wrapper is being called:
{
...
aws->subscribe(
topic,
[&] (const std::string &json) -> void {
ESP_LOGI(TAG, "got json: %s", json.c_str());
}
);
...
}
Update #2
The callback lambda inside the c_style_callback
does not access to the correct values of callback
and key
. How to protect these 2 from getting overwritten? "Wrapping" them inside a unique_ptr
? Return task
to the caller for reference? Also, the return value of the helper get_pvoid()
points the user data which is the lambda function, maybe that should be protected?