Std::unique_ptr<DBusMessage> != nullptr после перехода в параметр функции

Я пытаюсь написать программу, которая использует DBus для взаимодействия с BlueZ. Судя по информации, которую мне удалось получить из документации DBus, документации BlueZ DBus API и различных форумов, мой мыслительный процесс был следующим:

  1. Подключиться к Ббусу
  2. Запустите обнаружение с помощью адаптера «по умолчанию» (просто жестко закодированного в hci0).
  3. Используйте 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";

Просто помните: std::move() не перемещает содержимое объекта. Пожалуйста, покажите print_managed_objects() определение. это еще одна функция, которую я написал, но она не имеет значения — эта функция здесь наиболее актуальна.

3CxEZiVlQ 11.04.2024 04:32

Действительно ли определение необходимо? Он просто перебирает ответ, отправленный от BlueZ, и все. он больше ничего не делает с указателем, кроме перебора массива управляемых объектов и распечатки путей к объектам. Вероятно, мне следовало упомянуть, что цикл while успешно выполняется на первой итерации.

SwampyX 11.04.2024 04:37

Вместо описания кода стоит опубликовать минимально воспроизводимый пример .

3CxEZiVlQ 11.04.2024 04:40

хорошо. Я разместил функцию во фрагменте кода.

SwampyX 11.04.2024 04:53

Кажется, кода намного больше, чем необходимо. Я ожидаю, что он будет более минималистичным.

Eljay 11.04.2024 05:07

Это выглядит как правильный код, возможно, это не то, что вы на самом деле запускаете.

3CxEZiVlQ 11.04.2024 05:17

Да, изначально я исключил несколько строк из функции print_managed_objects для минимально воспроизводимого примера, не проверив, все ли ошибка произошла в коде, который я действительно опубликовал. :(

SwampyX 11.04.2024 05:34

@SwampyX Я не вижу в этом коде ничего, что могло бы вызвать описанный вами симптом. Это едва ли можно назвать минимально воспроизводимым примером (акцент на воспроизводимом). Но в любом случае построение unique_ptr из перемещенного unique_ptr гарантирует, что последний будет сброшен в nullptr, поэтому то, что вы утверждаете, не может быть тем, что происходит на самом деле.

Remy Lebeau 11.04.2024 05:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
8
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот проблема.

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 не будет нулевым. Есть идеи?

SwampyX 11.04.2024 06:21

Похоже на повреждение памяти стека. «Почему» — неправильный вопрос, когда вы получаете неопределенное поведение.

3CxEZiVlQ 11.04.2024 06:43

Другие вопросы по теме