Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 25.06.2014 | нет комментариев

Наше путешествие началось 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, "%sn",
                qPrintable("Can't register object"));
        exit(1);
    }
    qDebug()<<"Pong connected to D-bus";

    if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
        fprintf(stderr, "%sn",
                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. Попытка работы.

Выходит, мы одолели 1-й круг. Но чай сразу за ним идет 2-й! А выглядит он вот так:

Т.е. мы не можем сделать интерфейс. Вновь лезем в 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;

Завершение.

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

Ссылки

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB