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;

Заключение.

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

Ссылки

ссылка на оригинал статьи http://habrahabr.ru/post/185212/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *