QtDbus — тьма, покрытая тайною. Часть 1

    Наше путешествие началось Qt Graphics Framework, нас завербовали его светлой стороной, а потом мы долго получали граблями по разным частям тела.

    Данная статья — это спин-офф основного сюжета. В ней сказ пойдет о QtDBus. Этот модуль Qt появился еще в четвертой версии и был хоть как-то документирован и снабжен примерами. Но грянул Qt 5.0, и уж не знаю по чему, но это привело к тому, что на сторону тьмы перешла вышеназванная дока. .
    Дело №1. Как что и с чем готовить

    Пытаться понять логику работы с DBus по доке Qt — дело неблагодарное. Есть что-то гораздо лучше — это туториал от самих разработчиков dbus. И хотя я не скажу ничего нового, но ради целостности статьи приведу общую схему как все это работает.
    Итак, сама концепция:
    imageКаждое сообщение D-Bus, передаваемое по шине, имеет своего отправителя. В случае, если сообщение не является широковещательным сигналом, то оно имеет и получателя. Адреса отправителей и получателей называются путями объектов, поскольку D-Bus предполагает, что каждое приложение состоит из набора объектов, а сообщения пересылаются не между приложениями, а между объектами этих самых приложений.

    Итак, чтоб открыть доступ к объекту, в простейшем случае надо
    1. Подключится к демону на шине. Для это мы должны использовать QDBusConnection, заметим, что для стандартных шин есть статические методы.
    2. Зарегистирировать там свое имя. Это если хотим, иметь нормальное, удобочитаемое, а главное фиксированное имя, по которому к нам могут подключатся.Для это есть метод QDBusConnection::registerService().
    3. И зарегистрировать объект по некому пути(QDBusConnection::registerObject()).
    4. Подцепить к нему интерфейс, ну и ему тоже надо задать какое-то имя.

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

    Для это есть целый комплекс программ и методов:
    • Способ из Qt-шной доки.
    • qdbus
    • qdbusviewer
    • dbus-monitor

    Дело №2. Попытка установить соединение.
    Итак, готовьтесь, врата ада открываются. Первое что мы попытаемся сделать на основе этого примера — Установить соединение. Создадим два проекта: Ping и Pong(аналогично этому примеру), которые будут взаимодействовать.
    Проект Ping:
    main.cpp
    #include <stdio.h>
    
    #include <QObject>
    #include <QCoreApplication>
    #include <QDBusConnection>
    #include <QDBusConnectionInterface>
    #include <QDBusServiceWatcher>
    #include <QDebug>
    
    #include "Ping.h"
    #include "../serviceNameAndProperty.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        if (!QDBusConnection::sessionBus().isConnected()) {
                 fprintf(stderr, "Cannot connect to the D-Bus session bus.\n"
                         "To start it, run:\n"
                         "\teval `dbus-launch --auto-syntax`\n");
                 return 1;
        }
        qDebug()<<"Ping connected to D-bus";
        Ping ping;
    
        QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface();
        QObject::connect(iface, SIGNAL(serviceRegistered(QString)), &ping, SLOT(connectToService(QString)));
            QObject::connect(iface, SIGNAL(serviceUnregistered(QString)), &ping, SLOT(disconnect(QString)));
        QStringList registedServices = iface->registeredServiceNames();
        if(registedServices.contains(ping.m_aviableServiceName))
            ping.connectToService(ping.m_aviableServiceName);
    
        return a.exec();
    }
    
    Ping.h
    #ifndef PING_H
    #define PING_H
    
    #include <QObject>
    #include <QDBusAbstractInterface>
    #include <qdbusinterface.h>
    
    class Ping : public QObject
    {
        Q_OBJECT
    public:
        explicit Ping(QObject *parent = 0);
    public slots:
        void connectToService(const QString &name);
        void disconnect(const QString &name);
    public:
        QString m_aviableServiceName;
    private:
        QDBusInterface *m_interface;
        QString m_interfaceName;
        static const QString _propertyName;
    };
    #endif // PING_H
    
    Ping.cpp
    #include "Ping.h"
    #include "../serviceNameAndProperty.h"
    #include <QDBusConnectionInterface>
    #include <QDebug>
    
    const QString Ping::_propertyName(QUIOTING(IMAGE_DATA_SHARED_ID));
    Ping::Ping(QObject *parent) :
        QObject(parent)
    {
        m_interface = NULL;
        m_interfaceName = QString(BUFFER_NAME);
        m_aviableServiceName = QString(SERVICE_NAME);
    }
    
    void Ping::connectToService(const QString &name)
    {
        if(name != m_aviableServiceName)
            return;
        qDebug()<<"Connceting";
        m_interface = new QDBusInterface(name, "/", m_interfaceName, QDBusConnection::sessionBus(), this);
        if(!m_interface->isValid()){
            qDebug()<<"Invalid interface"<<m_interface->lastError();
            delete m_interface;
            m_interface = NULL;
            return;
        }
        qDebug()<<m_interface->interface();
    
        QVariant var("ku");
        var = m_interface->property("imageDataSharedId");
        qDebug()<<var;
    }
    
    void Ping::disconnect(const QString &name)
    {
        if(name != m_aviableServiceName)
            return;
        if(name != m_interface->service())
            return;
    
        delete m_interface;
        m_interface = NULL;
        qDebug()<<"Disconnect";
    }
    

    Проект Pong:
    main.cpp
    #include <QCoreApplication>
    #include <QDBusConnection>
    #include <QDBusError>
    #include <QDebug>
    
    #include "Pong.h"
    #include "../serviceNameAndProperty.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        QObject obj;
        Pong *pong = new Pong(&obj);
        if( ! QDBusConnection::sessionBus().registerObject("/", &obj)){
            fprintf(stderr, "%s\n",
                    qPrintable("Can't register object"));
            exit(1);
        }
        qDebug()<<"Pong connected to D-bus";
    
        if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
            fprintf(stderr, "%s\n",
                    qPrintable(QDBusConnection::sessionBus().lastError().message()));
            exit(1);
        }
        qDebug()<<"Test service start";
        return a.exec();
    }
    
    Pong.h
    #ifndef PONG_H
    #define PONG_H
    #include <QDBusAbstractAdaptor>
    #include <QDBusVariant>
    
    #include "../serviceNameAndProperty.h"
    class Pong : public QDBusAbstractAdaptor
    {
        Q_OBJECT
        Q_CLASSINFO("D-Bus Interface", BUFFER_NAME)
        Q_PROPERTY(QString IMAGE_DATA_SHARED_ID READ imageDataSharedId)
    public:
        explicit Pong(QObject *parent = nullptr);
        
        QString imageDataSharedId();
    
    private:
        QString m_imageDataSharedId;
        
    };
    #endif // PONG_H
    
    Pong.cpp
    #include "Pong.h"
    
    Pong::Pong(QObject *parent) :
        QDBusAbstractAdaptor(parent)
    {
        m_imageDataSharedId = "testImageBufferId";
    }
    
    QString Pong::imageDataSharedId()
    {
        return m_imageDataSharedId;
    }
    

    Общий файл serviceNameAndProperty.h
    #ifndef SERVICENAMEANDPROPERTY_H
    #define SERVICENAMEANDPROPERTY_H
    #define SERVICE_NAME "ru.sonarh.dbus.pong"
    #define BUFFER_NAME "buffer"
    #define IMAGE_DATA_SHARED_ID imageDataSharedId
    #define QUIOTING(text) #text
    #endif // SERVICENAMEANDPROPERTY_H
    

    Собираем проекты и запускает сначала пинг, а затем понг. Но результат неожиданный:

    Иными словами пинг не узнает о появлении понга. В непонятках обращаемся к коду qdbusviewer:
    QDBusConnectionInterface *iface = c.interface();
            connect(iface, SIGNAL(serviceRegistered(QString)),
                    this, SLOT(serviceRegistered(QString)));
            connect(iface, SIGNAL(serviceUnregistered(QString)),
                    this, SLOT(serviceUnregistered(QString)));
            connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
                    this, SLOT(serviceOwnerChanged(QString,QString,QString)));
    

    Вроде тоже самое. Ан-нет, в слотах у них совсем другое:
    void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner,
                                          const QString &newOwner)
    {
        QModelIndex hit = findItem(servicesModel, name);
    
        if (!hit.isValid() && oldOwner.isEmpty() && !newOwner.isEmpty())
            serviceRegistered(name);
        else if (hit.isValid() && !oldOwner.isEmpty() && newOwner.isEmpty())
            servicesModel->removeRows(hit.row(), 1);
        else if (hit.isValid() && !oldOwner.isEmpty() && !newOwner.isEmpty()) {
            servicesModel->removeRows(hit.row(), 1);
            serviceRegistered(name);
        }
    }
    

    Внезапно код похож на пример из доки. Ладно, пишем что-то аналогичное у себя:
    void Ping::manageConnection(const QString& name, const QString &oldVAlue, const QString &newValue)
    {
        if(name != m_aviableServiceName)
            return;
        if(newValue.isEmpty())
            disconnect(name);
        else
            connectToService(name);
    }
    

    И убеждаемся, что работает только serviceOwnerChanged. Но это не все, тролли нас предупреждают, что этот сигнал deprecated. Хорошо, тогда пишем такой код:
    QDBusServiceWatcher watcher;
        watcher.setConnection(QDBusConnection::sessionBus());;
    
        QObject::connect(&watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),&ping,  SLOT(manageConnection(QString,QString,QString)));
    Компилируем, запускаем. Не работает… Эй, тролли, вы троллите слишком жирно! Скажите, как надо юзать сие поделие? Нет, я понимаю, если добавить строку
        watcher.addWatchedService(ping.m_aviableServiceName);
    Что все зарабаотает и мы даже начнем получать сигналы регистрации и дерегистрации сервиса, ну а если я не знаю точного имени, а знаю лишь маску?

    Дело №3. Попытка работы.
    Итак, мы преодолели первый круг. Но ведь сразу за ним идет второй! А выглядит он вот так:

    Т.е. мы не можем создать интерфейс. Снова лезем в qdbusviewer и видим там следующие строки:
        QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
        QList<QVariant> arguments;
        arguments << sig.mInterface << sig.mName;
        message.setArguments(arguments);
        c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
    

    Интересный вариант, да, он работает. Но дока нам обещает нечто больше, мягче, абстрактнее. Если теперь заменить эти строчки на
        QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface,c);
        if( !iface.isValid())
            qDebug()<<(QDBusError(iface.lastError()).message());
        else
            qDebug()<<iface.property(sig.mName.toLatin1().data());
    
    То проблема, остановившая нас, повторится.
    Итак, проблема есть, но причина ее непонятна. Первое желание — залезть в исходники Qt. Решение лобовое, но за час к успеху я не пришел, а мозг напряг изрядно. Решение пришло со стороны: наблюдая за тем как выстроены интерфейсы в KDE, осознал, что
    To facilitate remembering of the naming formats and their purposes, the following table can be used:

    Service name Network hostnames Dot-separated («looks like a hostname»)
    Object path URL path component Slash-separated («looks like a path»)
    Interface Plugin identifier Dot-separated
    это не рекомендация или ассоциация, а соглашение, обязательное к исполнению. И действительно, если заменить BUFFER_NAME на невразумительное fdgfsgf.buffer, то все заработает.
    Если изучить доку D-Bus по-внимательнее, то обнаружится, что наличие точки в имени интерфейса обязательно, но почему тогда работает вариант предложенный в qdbusviewer?

    Бонус-левел
    Если в понге, в main.cpp первые строчки сделать вот такими:
    Pong pong;
        if( ! QDBusConnection::sessionBus().registerObject("/", &pong)){
    
    то у меня программа вываливается с Segmentation fault;

    Заключение.

    Изначально статья планировалась единой, но по мере моего изучения вопроса накапливалось все больше и больше вопросов и статья все разрасталась и разрасталась. И выросла до текущих размеров только после изучения основ, чтоже будет дальше?

    Ссылки
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 20
    • +3
      Не перестаю узнавать все новые и новые грани использования Qt :)
    • 0
      Вообще насколько я понял D-Bus это не только и не столько Qt, сколько отдельный механизм межпроцессного взаимодействия.
      Поэтому собственно
      Пытаться понять логику работы с DBus по доке Qt — дело неблагодарное.

      Была бы полезна отдельная статья чисто по D-Bus, без привязки к языку.
      • 0
        Да, D-bus отдельная тема. Статье по ней написать можно, но если честно, я сомневаюсь в своих способностях сказать лучше, чем сделали это в вики D-bus--а(ссылка в статье есть) + туториал тамже.
      • 0
        кто-то может обьяснить обстоятельство с «Бонус-левелом»? можно пробовать предположить, что `pong` удаляется по выходу из области видимости, но ведь рано ему удаляться(выход будет только по завершению приложения).
        • 0
          Точно не в этом. Можно сделать что в подобном духе:
          Pong * pong = new Pong();
              if( ! QDBusConnection::sessionBus().registerObject("/", pong)){
          

          Но вылет всеравно будет.
          • 0
            я понимаю, что это глупо, но что будет, если
            Pong * pong = new Pong(new QObject);
            ?
            • 0
              Шаманство — не падает. Но интерфейс не регистрируется
              • 0
                я думаю, тут не шаманство, а наше полное непонимание концепции Qt.
                вот тут почитай, как все устроено. довольно просто описано. 15 минут: голова в норме

                когда почитаешь, поймешь, что родительский объект обязателен. хотя да, товарищи могли сделать более изящно и создать его сами, если он — nullptr. вообще говоря, стоит перечитать пример, с которого ты списал свой. ты далеко не все списал и далеко не все понял, из того, что там было написано. я вот только начал читать и многие вещи становятся очевидными.
                • 0
                  Эээ, ссылки нет.
                  Дело в том, что сам QAbstractDbusAdaptor — наследник QObjetc предполагать, что для нужен родитель очень странно. Тем более, если взять вариант, наследующийся напрямую от Qobject, объявить его итерфейсом, то все заработает и не будет вылетов.
                  • 0
                    да. увидел. вот ссылка
                    qt.gitorious.org/qt/qt/blobs/00fa3f364fe9657317cb14191167aa9991c1758e/src/dbus/qdbusabstractadaptor.cpp

                    (странно, но у меня совсем не работают теги разметки)
                    • 0
                      Угу, понял, почитаю чуть позже, может пойму чего они хотели добиться.
                      И да, мне до чертиков любопытно знать, что ты понял и вызнал.
                      • 0
                        ну, как минимум, сразу бросается в глаза:
                        explicit QAbstractButton(QWidget* parent=0); // это выбрал из рандомного файла. помню этот концепт наизусть.
                        QDBusAbstractAdaptor(QObject *parent);

                        казалось бы, не такая уж и странная вещь, но у Qt-шников довольно тсрогий стиль. и если в первом случае мы видим, что наличие дефолтного параметра как-бы намекает, что родитель вполне себе может отсутствовать, то во втором — родитель строго нужен.
                        то, что указатель может быть nullptr — минус конструкции языка и недоработка, собственно программистов.
                        лень им было ассерт поставить и выбросить логическую ошибку при nullptr?
                        свой создавать, по факту, плохо: им пришлось бы думать, как после него память освободить. хотя есть, конечно, обходные пути, типа создания совсем уж корневого статического объекта.

                        именно это отличие заставило меня начать вчитываться в пример. что я там вычитал, могу рассказать завтра. если нужно, в личку(сегодня поезд, потому чтение я отложил пока).
                        • 0
                          М-да, подстава. Привык что родитель это необязательный элемент, а где не так — так сразу и не припомню. Да, надо будет исходники почитать.

                          Ну можно и не в личку. Чистого пути.
                          • 0
                            спасибо
                            • 0
                              Прошу прощения сразу за 2 вещи:
                              — как-то затянулась моя интеграция в среду после приезда и я забыл отписаться сюда
                              — понимание примеров было все-таки полным (чего все-таки не скажешь про документацию. о ней как раз будет идти речь)

                              как я уже написал ранее, на мысль меня натолкнуло отсутствие знаения аргумента по-умолчанию. и это оказалось вполне обоснованным. читая документацию, буквально в первом-же абзаце я наткнулся на следующую мысль:

                              The QDBusAbstractAdaptor class is the starting point for all objects intending to provide interfaces to the external world using D-Bus. This is accomplished by ATTACHING a one or more classes derived from QDBusAbstractAdaptor TO A NORMAL QObject AND THEN REGISTERING THAT QObject with QDBusConnection::registerObject. QDBusAbstractAdaptor objects are intended to be light-weight wrappers, mostly just relaying calls into the real object (its parent) and the signals from it.

                              собственно, заглавными буквами я обозначил нужную мысль, которая была упущена тобой.
                              вот еще несколько мыслей об использовании наследников QDbusAbstractAdaptor:
                              — (могу ошибаться в переводе) нельзя создать больше одного экземпляра одного класса
                              — каждый экземпляр должен создаваться с помощью оператора new
                              — ни один экземпляр не должен быть удален пользователем. они удалятся автоматически при удалении родительского обьекта

                              остальное буду читать как-нибудь позже, когда будет больше свободного времени.
                              сразу прошу прощения за отсутствие разметки и/или наличие опечаток в тексте.
                              • 0
                                Если заметить, что родительский элемент обязателен, то последнее два очевидно — родитель сам удалит своего потомка. Первый пункт тоже очевиден — иначе возникнет траблы при подключении к D-Bus, ибо только один интерфейс может быть расположен по тому же пути. Сейчас разбираюсь в коде, похоже это тупо патерн Адаптер для связки Qt-мира и D-bus-мира
                                • 0
                                  не согласен насчет очевидности удаления: с точки зрения логики(да и в коде Qt есть на это намеки, хотя я сильно не вникал), если ты удаляешь обьект, он автоматически вытирает запись о себе в родительском обьекте и удаляет всех своих потомков(порядок может быть обратным).
                                  первый же пункт становится очевидным лишь после понимания того, что система использует Q_CLASSINFO для идентификации обьекта.
                                  • 0
                                    Да, действительно, но это уже некий изврат(в данном случае), ИМХО.
                                    Насчет Q_CLASSINFO: а разве это не очевидно?
                                    • 0
                                      исключительно после прочтения доки…

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