介绍D-Bus在QT4下的绑定。在实作中,我们会在Session Bus上注册一个Hotel Service,通过这个Service,可以实现check in,check out以及query的动作。


-Bus中的一些关键术语的表述依然采用英文。这些术语包括:D-Bus, IPC, Message, Message Bus, System Bus, Session Bus, Service, Object, Method, Signal, Interface, Unique Connection Name, Well-known Name等。
本文的源代码可以从这里下载。

-Bus?

D-Bus是一种进程间通信的机制,它被设计成为一种低开销、低延迟的IPC,并被多种桌面环境(如KDE、GNOME等)所采用。
D-Bus Specification 。

基本概念

-Bus提供了多种Message Bus用于应用程序之间的通信。通常,Linux发行版都会提供两种Message Bus:System Bus和Session Bus。System Bus 主要用于内核和一些系统全局的service之间通信;Session Bus 主要用于桌面应用程序之间的通信。
D-Bus Specification 。
-Bus规范里,unique connection name和well-known name都叫做Bus Name。这点比较奇怪,也比较拗口,Bus Name并不是Message Bus的名称,而是应用程序和Message Bus之间的连接的名称。
应用程序和Message Bus之间的连接也被称为Service,这样一来,把Bus Name称作Service Name在概念上会更清晰一点。

ervice Name和Object Path,QT4文档中有一个类比还是比较直观的,如下图所示:


图中的ftp.example.com可以看作是Service Name,/pub/something可以看作是Object Path。

-Bus通过Signal/Method来发送和接收Message。Signal/Method可以理解为QT4中的Signal/Slot这个概念。一个Object可以提供多个Method/Signal,这些Method/Signal的集合又组成了Interface。

-Bus的这些概念从大到小可以表示为:Message Bus->Service->Object->[Interface]->Method/Signal。

nterface是可选的。

D-Bus 调试工具

-Feet、qdbusviewer等。

 

dbusviewer来调用Object在Message Bus上发布的所有Method。


-Bus 的 QT4 绑定

-Bus的QT4绑定。(参见hotel.pro)

创建Service并且注册Object

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    // 用于建立到session bus的连接
    QDBusConnection bus = QDBusConnection::sessionBus();
    // 在session bus上注册名为"com.test.hotel"的service
    if (!bus.registerService("com.test.hotel")) {
            qDebug() << bus.lastError().message();
            exit(1);
    }
    Hotel my_hotel;
    // 注册名为"/hotel/registry"的object。
    // "QDBusConnection::ExportAllSlots"表示把类Hotel的所有Slot都导出为这个Object的method
    bus.registerObject("/hotel/registry", &my_hotel,
                       QDBusConnection::ExportAllSlots);
    return a.exec();
}
我们再看一下Hotel类的定义。
class Hotel : public QObject
{
    Q_OBJECT
    // 定义Interface名称为"com.test.hotel.registry"
    Q_CLASSINFO("D-Bus Interface", "com.test.hotel.registry")
public:
    Hotel() { m_rooms = MAX_ROOMS; }
public slots:
    // Check in,参数为房间数,返回成功拿到的房间数
    int checkIn(int num_room);
    // Check out,参数为房间数,返回成功退回的房间数
    int checkOut(int num_room);
    // Query,用于查询目前还剩下的房间数 
    int query();
private:
    int m_rooms;
    QReadWriteLock m_lock;
};

dbusviewer查看和操作这个Object。

通过QDBusMessage访问Service

4中,用QDBusMessage表示在D-Bus上发送和接收的Message。(参见checkin.pro)
        // 用来构造一个在D-Bus上传递的Message
        QDBusMessage m = QDBusMessage::createMethodCall("com.test.hotel",
                                                      "/hotel/registry",
                                                      "com.test.hotel.registry",
                                                      "checkIn");
        if (argc == 2) {
                // 给QDBusMessage增加一个参数;
                // 这是一种比较友好的写法,也可以用setArguments来实现
                m << QString(argv[1]).toInt();
        }
        // 发送Message
        QDBusMessage response = QDBusConnection::sessionBus().call(m);
        // 判断Method是否被正确返回
        if (response.type() == QDBusMessage::ReplyMessage) {
                // QDBusMessage的arguments不仅可以用来存储发送的参数,也用来存储返回值;
                // 这里取得checkIn的返回值
                int num_room = response.arguments().takeFirst().toInt();
                printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
        } else {
                fprintf(stderr, "Check In fail!\n");
        }

访问Service

4中,QDBusInterface可以更加方便的访问Service。(参见checkin2.pro)
        // 创建QDBusInterface
        QDBusInterface iface( "com.test.hotel", "/hotel/registry",
                              "com.test.hotel.registry", QDBusConnection::sessionBus());
        if (!iface.isValid()) {
                qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message());
                exit(1);
        }
        // 呼叫远程的checkIn,参数为num_room
        QDBusReply<int> reply = iface.call("checkIn", num_room);
        if (reply.isValid()) {
                num_room = reply.value();
                printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
        } else {
                fprintf(stderr, "Check In fail!\n");
        }

usInterface来访问Service是不是更加方便?

-Bus XML自动生成Proxy类

概括的说,达成上述目标需要分三步走:
qdbuscpp2xml从hotel.h生成XML文件;
            qdbuscpp2xml -M hotel.h -o com.test.hotel.xml
(2)使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类;
            qdbusxml2cpp com.test.hotel.xml -i hotel.h -p hotelInterface
       这条命令会生成两个文件:hotelInterface.cpp和hotelInterface.h
(3)调用(2)生成的类来访问Service。

heckin3.pro ):
        // 初始化自动生成的Proxy类com::test::hotel::registry
        com::test::hotel::registry myHotel("com.test.hotel",
                                           "/hotel/registry",
                                           QDBusConnection::sessionBus());
        // 调用checkIn
        QDBusPendingReply<int> reply = myHotel.checkIn(num_room);
        // qdbusxml2cpp生成的Proxy类是采用异步的方式来传递Message,
        // 所以在此需要调用waitForFinished来等到Message执行完成
        reply.waitForFinished();
        if (reply.isValid()) {
                num_room = reply.value();
                printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
        } else {
                fprintf(stderr, "Check In fail!\n");
        } 

使用Adapter注册Object

lass Hotel注册为Message Bus上的一个Object,但这种方式并不是QT4所推荐的。QT4推荐使用Adapter来注册Object。
essage Bus上,使用Adapter可以很方便的实现这种意图。
otel为例,假设我们只需要把checkIn和checkOut发布到Message Bus上,应该怎么办?

qdbuscpp2xml从hotel.h生成XML文件;
(2)编辑com.test.hotel.xml,把其中的query部分去掉;
        </method>
(3)使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类;
            qdbusxml2cpp com.test.hotel.xml -i hotel.h -a hotelAdaptor
       这条命令会生成两个文件:hotelAdaptor.cpp和hotelAdaptor.h
(4)调用(3)生成的类来注册Object。

(参见hotel2.pro)
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QDBusConnection bus = QDBusConnection::sessionBus();
    Hotel myHotel;
    // RegistryAdaptor是qdbusxml2cpp生成的Adaptor类
    RegistryAdaptor myAdaptor(&myHotel);
    if (!bus.registerService("com.test.hotel")) {
            qDebug() << bus.lastError().message();
            exit(1);
    }
    bus.registerObject("/hotel/registry", &myHotel);
    return a.exec();
}

dbusviewer上可以看到,只有checkIn和checkOut两个method被发布。如下图所示:

自动启动Service

D-Bus系统提供了一种机制可以在访问某个service时,自动把该程序运行起来。
sr/share/dbus-1/services下面建立com.test.hotel.service文件,文件的内容如下:

[D-BUS Service]
Name=com.test.hotel
Exec=/path/to/your/hotel

这样,我们在访问Hotel的method之前,就不必手动运行该应用程序了。

相关文章: