QtDbus Часть 2. Победное сияние чистого разума

в 11:38, , рубрики: dbus, linux, qt, Qt Software, qtdbus, Программирование, метки: , ,

Предыдущая часть оставляла мрачные прогнозы, но все оказалось гораздо, гораздо, гораздо лучше.

Разбор бонус-левела

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

Вдобавок мое незнание патернов, есть такой патен «Адаптер». Так вот QAbstractAdaptor собственно это он и есть. С учетом вышесказанного и замечания доки, что этот класс должен быть легковесным пример, действительно проясняющий подход либы должен быть таким:
Проект 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);
    QDBusConnection connection = QDBusConnection::sessionBus();
    Pong pong;
    if( ! connection.registerObject("/", &pong)){
        fprintf(stderr, "%sn",
                qPrintable("Can't register object"));
        exit(1);
    }
    qDebug()<<"Pong connected to D-bus";

    if (!connection.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 <QDBusArgument>
#include <QDBusContext>

#include "../serviceNameAndProperty.h"

class Pong;

class PongAdapter : public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", BUFFER_NAME)
    Q_PROPERTY(QString IMAGE_DATA_SHARED_ID READ imageDataSharedId)
public:
    explicit PongAdapter(Pong *parent);

    QString imageDataSharedId();

public slots:
    TestStructure structureField();
signals:
    void callingMe(QString, QString);
private:
    Pong * m_parentPong;

};

class Pong : public QObject, public QDBusContext
{
    Q_OBJECT
public:
    Pong()
    {
        m_pongAdapter = new PongAdapter(this);
        QObject::connect(this, SIGNAL(callingMe(QString,QString)), m_pongAdapter, SIGNAL(callingMe(QString, QString)));
        m_imageDataSharedId = "testImageBufferId";

    }
public:
    QString imageDataSharedId();
    TestStructure& structureField();

signals:
    void callingMe(QString, QString);
private:
    PongAdapter *m_pongAdapter;
    QString m_imageDataSharedId;
    TestStructure test;

};
#endif // PONG_H>
Pong.cpp

#include "Pong.h"
#include <QDebug>
#include <QDBusMetaType>
#include <QDBusConnection>
#include <QDBusMessage>

PongAdapter::PongAdapter(Pong *parent) :
    QDBusAbstractAdaptor(parent)
{
    m_parentPong = parent;
    qRegisterMetaType<TestStructure>("TestStructure");
    qDBusRegisterMetaType<TestStructure>();

}

QString PongAdapter::imageDataSharedId()
{
    return m_parentPong->imageDataSharedId();
}

TestStructure PongAdapter::structureField()
{
    return m_parentPong->structureField();
}

QString Pong::imageDataSharedId()
{
    return m_imageDataSharedId;
}

TestStructure &Pong::structureField()
{
    qDebug()<<"Me calld"<<QDBusConnection::sessionBus().baseService()<<message().service();
    emit callingMe(QString("Panic"), QString("Super panic"));
    test.str = QString("ku");
    test.id =2;
    return test;
}

Проект 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);
    Ping ping;

    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";


    QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface();
    QDBusServiceWatcher watcher;
    watcher.setConnection(QDBusConnection::sessionBus());;
    watcher.addWatchedService(ping.m_aviableServiceName);
//    QObject::connect(&watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),&ping,  SLOT(manageConnection(QString,QString,QString)));
    QObject::connect(&watcher, SIGNAL(serviceRegistered(QString)), &ping, SLOT(connectToService(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 manageConnection(const QString &name, const QString &oldVAlue, const QString &newValue);
    void connectToService(const QString &name);
    void disconnect(const QString &name);
    void reacoOnMeCalling(QString message, QString message2);
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>
#include <QDBusMetaType>

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);
    qRegisterMetaType<TestStructure>("TestStructure");
    qDBusRegisterMetaType<TestStructure>();
}

void Ping::manageConnection(const QString& name, const QString &oldVAlue, const QString &newValue)
{
    Q_UNUSED(oldVAlue)
    if(name != m_aviableServiceName)
        return;
    if(newValue.isEmpty())
        disconnect(name);
    else
        connectToService(name);

}

void Ping::connectToService(const QString &name)
{
    if(name != m_aviableServiceName)
        return;
    qDebug()<<"Connceting";
    m_interface = new QDBusInterface(name, "/", m_interfaceName, QDBusConnection::sessionBus(), this);
    QObject::connect(m_interface, SIGNAL(callingMe(QString, QString)), this, SLOT(reacoOnMeCalling(QString, QString)));
    if(!m_interface->isValid()){
        qDebug()<<"Invalid interface"<<m_interface->lastError();
        delete m_interface;
        m_interface = NULL;
        return;
    }
    qDebug()<<m_interface->interface();

    QVariant var("sss");
    var = m_interface->property("imageDataSharedId");
    qDebug()<<var;
    QDBusReply<TestStructure> reply= m_interface->call("structureField");
    if(reply.isValid())
    {
        TestStructure testStructure = reply.value();
        qDebug()<<testStructure.id<<testStructure.str;
    }
}

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";
}

void Ping::reacoOnMeCalling(QString message, QString message2)
{
    qDebug()<<message<<message2;
}

Общий файл serviceNameAndProperty.h

#pragma once
#include<QMetaType>
#include <QString>
#include <QDBusArgument>
#define SERVICE_NAME "ru.sonarh.dbus.pong"
#define BUFFER_NAME "ru.buffer"
#define IMAGE_DATA_SHARED_ID imageDataSharedId
#define QUIOTING(text) #text


struct TestStructure{
    int id;
    QString str;
};
Q_DECLARE_METATYPE(TestStructure)

static QDBusArgument& operator <<(QDBusArgument &argument, const TestStructure & arg)
{
    argument.beginStructure();
    argument<<arg.id<<arg.str;
    argument.endStructure();
    return argument;
}

static const QDBusArgument& operator >>(const QDBusArgument &argument, TestStructure & arg)
{
    argument.beginStructure();
    argument>>arg.id>>arg.str;
    argument.endStructure();
    return argument;
}

И все таки, как это юзать?

Ради целостности представления как пользоваться модулем позволю себе повторение пройденного. Благо повторение — мать учения.
Итак у нас есть класс Pong, мы хотим, его сделать доступным для взаимодействия по D-Bus. Что для этого нужно:

  1. Подсоединится к D-Bus демону. Он может быть где угодно, но для стандартных шин есть уже готовые статические методы. В нашем примере это вызов идет сразу с проверкой
    if (!QDBusConnection::sessionBus().isConnected()) {
  2. Ради удобства и определенности можно закрепить за сервисом имя, аналогичное DNS. Мы делаем это для проекта Pong:
    if (!connection.registerService(SERVICE_NAME)) {

    Важно! оно должно содержать хотя бы один разделяющий символ — точку.

  3. Создать адаптер, родителем которого будет являться Pong. Этот адаптер будет принимать сигналы извне и перенаправлять их своему родителю, ровно как и в обратном направлении. Еще подчеркну, что по задумке проектировщиков либы адаптер действительно адаптер и с его помощью достаточно просто расширить уже имеющееся приложение. Вот как-то так происходит передача сигнала от проги в D-Bus-мир:
    QObject::connect(this, SIGNAL(callingMe(QString,QString)), m_pongAdapter, SIGNAL(callingMe(QString, QString)));

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

  4. Зарегистрировать Pong по некому пути. Выглядит это примерно так:
     if( ! connection.registerObject("/", &pong)){

    Важно! Этот путь должен содержать хотя бы один разделяющий символ -/

Интересный поворот в этой истории это то, что если мы хотим знать контекст сообщения, которое активировало слот, то нам нужно реальный объект (Pong) отнаследовать от QDbusContext, но при этом, контекстные методы, аля message нужно дергать в адаптере, если мы по-прежнему хотим абстрогироваться от D-Bus и не заполучить головную боль при тестировании. Довольно странный поворот, но пока с неудобствами, им вызванным, я не столкнулся.

И раз уже заговорили о контексте, то нельзя не упомянуть о сердце всего модуля. И сердце это двухкамерное: первая камера — это QDbusMessage т.е. те самые послания которыми обмениваются участники, а вторая камера это их интерпретация в виде QDbusArgument и QDbusReply. Впринципе, послания можно формировать вручную. Например, обращения к property в dbusviwer сделана так:

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());

Но гораздо проще доверить формирование послание QDbusArgument. Для это необходимо всего две вещи: реализовать операторы >> <<, если у вас тип не укладывающийся базовые в стандартные Qt:

struct TestStructure{
    int id;
    QString str;
};
Q_DECLARE_METATYPE(TestStructure)

static QDBusArgument& operator <<(QDBusArgument &argument, const TestStructure & arg)
{
    argument.beginStructure();
    argument<<arg.id<<arg.str;
    argument.endStructure();
    return argument;
}

И до первого использования вызвать qDBusRegisterMetaType. И после это трудности заканчиваются: с помощью QDbusInterface вы вызываете нужный метод, если это не void-метод, и возвращаемое значение нам важно и нужно, то используем шаблонный класс QDbusReply.
На этом все, действительно все. Знакомство с модулем закончено, впереди только поле где его надо использовать.

Автор: DancingOnWater

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js