gpt4 book ai didi

c++ - 我将如何使用在 C++ 对象中扩展 libev 的库?

转载 作者:行者123 更新时间:2023-12-02 10:22:57 31 4
gpt4 key购买 nike

我正在尝试为一个名为 libumqtt 的库实现一个包装器。 Libumqtt是一个 C 库,它使用 libev 对来自 MQTT 协议(protocol)的事件进行回调。

直到前几天我才意识到我无法将成员函数传递给需要正常静态函数的函数。这会导致问题,因为我计划启动多个 libumqtt 实例以同时处理多个连接。

我的代码在 C++ 中,因为这是与 Godot 的(游戏引擎)GDNative 模块一起使用最方便的。

在研究将 c 库的多个实例沙箱化或以某种方式让指针正常工作的方法时,我发现 this 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.



我想要做的是设置回调,当它同时处理可能多达 500 个或更多连接时,libev 将使用该回调将数据发送到我的对象的正确实例。

通过 void* 会帮助我实现我的目标吗?我将如何实现它?另外,转发功能如何工作?

编辑:提供核桃要求的代码示例

下面的这个例子来自我使用静态函数的类的一个版本。如果我在函数不是静态的时候尝试使用 run this,那么我会收到一个错误,即无法传入成员函数来代替常规函数。

// 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

// ...
}

编辑:我应该提到我是使用 C 和 C++ 的菜鸟。我之前做的最多的是测试缓冲区溢出。因此,如果我显然做错了什么,我将不胜感激评论中的提示。

最佳答案

所以问题是 umqtt_client似乎没有提供将额外用户数据传递给回调的任何方式(您的报价中提到的 void*)。它期望回调只接受一个指向 umqtt_client 的指针。实例。 (我在这里可能错了,我只是基于快速查看源文件。)

如果您的成员函数实际上并没有访问您的类的任何非静态成员,那么您可以简单地将它们设为 static .然后您可以直接将它们用作普通函数指针。

否则,您需要从 umqtt_client* 获取指向您的实例的指针。指针。
这样做的一种方法是在指针之间维护一个静态映射,例如在 Client添加声明:

static std::map<umqtt_client*, Client*> umqtt_client_map;

并在创建 Client 时插入其中(我在这里假设您实际上将 cl 指针维护为 Client 的类成员),最好在 Client 中的构造函数:
umqtt_client_map[cl] = this;

然后在 Client的析构函数(或 umqtt_client 对象被销毁的地方)从映射中删除相应的元素:
umqtt_client_map.erase(cl);

然后你可以使用一个看起来像这样的 lambda 作为回调传递:
cl->on_net_connected = [](umqtt_client* ptr){
umqtt_client_map[ptr]->on_net_connected();
};

请注意 on_net_connected如果它是类的成员,则不需要指针作为参数。

这还假设您使类不可复制和不可移动,或者您使用正确的删除和插入语义来实现复制和移动构造函数和赋值运算符 umqtt_client_map也是。

该库似乎提供了一个功能 umqtt_init而不是 umqtt_new不分配 umqtt_client目的。如果您改用它,您可以执行以下操作:

包裹 umqtt_client在一个小的标准布局类中:
struct umqtt_client_wrapper {
umqtt_client cl; // must be first member!
Client* client;
static_assert(std::is_standard_layout_v<umqtt_client_wrapper>);
};

然后,您将使用它作为 Client 的成员。而不是 umqtt_client*直接初始化 umqtt_client*umqtt_init) and客户 with这个 in客户端的构造函数。然后您可以在 lambda 中使用强制转换来进行回调:
cl->on_net_connected = [](umqtt_client* ptr) {
reinterpret_cast<umqtt_client_wrapper*>(ptr)->client->on_net_connected();
};

请注意,这取决于 umqtt_client_wrapper是标准布局和 umqtt_client*是它的第一个成员。不遵守这些规则将导致未定义的行为。 static_assert提供了一些保证,至少它的一部分不会被意外违反。它需要 #include<type_traits>和我在这里使用的形式的 C++17。

同样,这需要特别注意实现 Client 的复制和移动特殊成员函数。正确或删除它们,但使用此方法不需要在析构函数中执行任何操作。

这种方法比另一种方法性能更高,原则上您可以避免额外的 Client如果您确定 Client本身是标准布局,但这可能过于限制和冒险。

另一种节省额外间接性的方法是将包装器用作 Client 的基类。 :
struct umqtt_client_wrapper {
umqtt_client cl; // must be first member!
static_assert(std::is_standard_layout_v<umqtt_client_wrapper>);
};

然后让 Client继承自 umqtt_client_wrapper你可以使用:
cl->on_net_connected = [](umqtt_client* ptr) {
static_cast<Client*>(reinterpret_cast<umqtt_client_wrapper*>(ptr))
->on_net_connected();
};

注意这里的第一个 Actor 必须是 static_cast ,否则您很容易导致未定义的行为。

与前面相同的注释适用。

关于c++ - 我将如何使用在 C++ 对象中扩展 libev 的库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59258736/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com