Я пытаюсь написать программу, которая использует DBus для взаимодействия с BlueZ. Судя по информации, которую мне удалось получить из документации DBus, документации BlueZ DBus API и различных форумов, мой мыслительный процесс был следующим:
hci0
).org.freedesktop.DBus.ObjectManager.GetManagedObjects()
, чтобы найти доступные устройства.Поэтому я написал следующий код:
#include <...>
#include <dbus-1.0/dbus/dbus.h>
struct DBusDeleter
{
void operator()(DBusConnection *connection) {dbus_connection_unref(connection);}
void operator()(DBusMessage *msg) {dbus_message_unref(msg);}
void operator()(DBusError *err) {dbus_error_free(err);}
};
void print_managed_objects(std::unique_ptr<DBusMessage, DBusDeleter> message)
{
DBusMessageIter message_iter;
dbus_message_iter_init(message.get(), &message_iter);
std::cout << (char) dbus_message_iter_get_arg_type(&message_iter) << '\n';
DBusMessageIter dict_iter;
dbus_message_iter_recurse(&message_iter, &dict_iter);
std::cout << (char) dbus_message_iter_get_arg_type(&dict_iter) << '\n';
while(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY)
{
DBusMessageIter entry_iter;
dbus_message_iter_recurse(&dict_iter, &entry_iter);
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << object_path << "\n";
std::cout << (char) dbus_message_iter_get_arg_type(&entry_iter) << '\n';
dbus_message_iter_next(&dict_iter);
}
std::cout << std::flush;
}
int main()
{
std::unique_ptr<DBusError, DBusDeleter> dbus_error(new DBusError);
dbus_error_init(dbus_error.get());
std::shared_ptr<DBusConnection> conn(dbus_bus_get(DBUS_BUS_SYSTEM, dbus_error.get()), DBusDeleter());
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "DBus Error: " << dbus_error->message << std::endl;
return 1;
}
std::string service_name = "org.bluez";
std::unique_ptr<DBusMessage, DBusDeleter> msg, reply;
// Start Device Discovery
msg.reset(dbus_message_new_method_call(service_name.c_str(), "/org/bluez/hci0", "org.bluez.Adapter1", "StartDiscovery"));
reply.reset(nullptr);
uint32_t serial = 1;
dbus_connection_send(conn.get(), msg.get(), &serial);
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "BlueZ Error (Failed to start discovery): " << dbus_error->message << std::endl;
return 1;
}
std::chrono::duration<int64_t> scan_duration = std::chrono::seconds(10);
std::chrono::_V2::system_clock::time_point start_time = std::chrono::system_clock::now();
msg.reset(dbus_message_new_method_call(service_name.c_str(), "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"));
while (start_time + scan_duration >= std::chrono::system_clock::now())
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
reply = std::unique_ptr<DBusMessage, DBusDeleter>(dbus_connection_send_with_reply_and_block(conn.get(), msg.get(), -1, dbus_error.get()));
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "error with reply: " << dbus_error->message << "\n";
break;
}
print_managed_objects(std::move(reply));
if (reply == nullptr) {
std::cout << "All Good!\n";
continue;
}
std::cout << "Something went wrong!\n";
break;
}
// more code...
}
Проблема возникает прямо при вызове функции print_managed_objects
. Когда функция вернется, я ожидал, что, поскольку функция print_managed_objects
уже использовала сообщение, поскольку я передал ей право собственности на сообщение, все будет в порядке, но проверка nullptr
не удалась в исходном unique_ptr
, из которого он был перемещен.
Это означает, что указатель reply
все еще имеет ссылку на сообщение, которое было уничтожено после возврата print_managed_objects
, и при сбросе указателя на следующей итерации цикла программа вылетит, потому что DBus выдаст эту ошибку:
dbus[15920]: аргументы dbus_message_unref() были неверными, утверждение «message->generation == _dbus_current_generation» не выполнено в строке 1728 файла dbus-message.c
Мой вопрос: почему внутренний указатель reply
не устанавливается на nullptr
после перемещения? Согласно другому вопросу о переполнении стека, вызов std::move()
по уникальному указателю установит внутренний указатель этого указателя на nullptr
.
Обновлено: я пропустил строки в функции print_managed_objects
, которые, как я думал, можно было бы пропустить для минимального воспроизводимого примера, но эти строки на самом деле были необходимы для возникновения ошибки. В частности, эти строки:
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << *object_path << "\n";
Действительно ли определение необходимо? Он просто перебирает ответ, отправленный от BlueZ, и все. он больше ничего не делает с указателем, кроме перебора массива управляемых объектов и распечатки путей к объектам. Вероятно, мне следовало упомянуть, что цикл while успешно выполняется на первой итерации.
Вместо описания кода стоит опубликовать минимально воспроизводимый пример .
хорошо. Я разместил функцию во фрагменте кода.
Кажется, кода намного больше, чем необходимо. Я ожидаю, что он будет более минималистичным.
Это выглядит как правильный код, возможно, это не то, что вы на самом деле запускаете.
Да, изначально я исключил несколько строк из функции print_managed_objects
для минимально воспроизводимого примера, не проверив, все ли ошибка произошла в коде, который я действительно опубликовал. :(
@SwampyX Я не вижу в этом коде ничего, что могло бы вызвать описанный вами симптом. Это едва ли можно назвать минимально воспроизводимым примером (акцент на воспроизводимом). Но в любом случае построение unique_ptr
из перемещенного unique_ptr
гарантирует, что последний будет сброшен в nullptr
, поэтому то, что вы утверждаете, не может быть тем, что происходит на самом деле.
Вот проблема.
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << *object_path << "\n";
Вы передаете неинициализированный указатель на функцию. Это неопределенное поведение. Кажется, в вашем случае вы получили поврежденные данные стека.
std::cout << *object_path << "\n";
Я не знаю, чего вы там ожидаете. Что касается третьей строки, вы, вероятно, ожидаете, что данные будут состоять из одного символа. Код должен быть
char object_path;
dbus_message_iter_get_basic(&entry_iter, &object_path);
std::cout << object_path << "\n";
&object_path
передает адрес хранилища символов.
Верхний код отображается
std::cout << object_path << "\n"
Итак, меня смущает ваш код.
Аналогичный пример показан в инструкции. Вы можете легко следовать этому вместо испытаний. dbus_message_iter_get_basic().
Я пытался получить строковое значение, поэтому object_path
по-прежнему будет char *
, но, похоже, я забыл оператор адреса при отправке его в dbus_message_iter_get_basic
, и, как мне кажется, на самом деле произошло то, что я распечатывал адреса памяти, где находились фактические строки. располагается. до сих пор не понимаю, почему это может привести к тому, что reply
не будет нулевым. Есть идеи?
Похоже на повреждение памяти стека. «Почему» — неправильный вопрос, когда вы получаете неопределенное поведение.
Просто помните:
std::move()
не перемещает содержимое объекта. Пожалуйста, покажитеprint_managed_objects()
определение. это еще одна функция, которую я написал, но она не имеет значения — эта функция здесь наиболее актуальна.