D-Bus和QT4
2010-10-27 13:41 wwang 阅读(11349) 评论(4) 编辑 收藏 举报本文以一个实作为例,介绍D-Bus在QT4下的绑定。在实作中,我们会在Session Bus上注册一个Hotel Service,通过这个Service,可以实现check in,check out以及query的动作。
为避免歧义,本文对D-Bus中的一些关键术语的表述依然采用英文。这些术语包括:D-Bus, IPC, Message, Message Bus, System Bus, Session Bus, Service, Object, Method, Signal, Interface, Unique Connection Name, Well-known Name等。
本文的源代码可以从这里下载。
D-Bus概述
什么是D-Bus?
D-Bus是一种进程间通信的机制,它被设计成为一种低开销、低延迟的IPC,并被多种桌面环境(如KDE、GNOME等)所采用。
基本概念
D-Bus提供了多种Message Bus用于应用程序之间的通信。通常,Linux发行版都会提供两种Message Bus:System Bus和Session Bus。System Bus 主要用于内核和一些系统全局的service之间通信;Session Bus 主要用于桌面应用程序之间的通信。
当应用程序连接到M essage Bus上时,D-Bus会分配一个unique connection name,这个unique name通常的格式如":34-907"。Unique name以":"开头,后面的数字没有特别的意义,只是为了保证这个unique name的唯一性。
另外,应用程序还可以向Message Bus请求一个well-known name,格式如同一个反置的域名,例如"com.mycompany.myapp"。当一个应用程序连接到Message Bus上时,可以拥有两种名称:unique connection name和well-known name。这两种名称的关系可以理解为网络上的IP地址和域名的关系。
在D-Bus规范里,unique connection name和well-known name都叫做Bus Name。这点比较奇怪,也比较拗口,Bus Name并不是Message Bus的名称,而是应用程序和Message Bus之间的连接的名称。
应用程序和Message Bus之间的连接也被称为Service,这样一来,把Bus Name称作Service Name在概念上会更清晰一点。
当应用程序连接到Message Bus上时,该应用程序可以在Bus上创建一到多个Object(我们可以把D-Bus的object理解成面向对象语言里的object)。Service通过Object 为其他应用程序提供访问接口。因为在Message Bus上,一个应用程序可以对应多个Object,所以不同的Object必须由Object Path(类似于文件系统的路径)来区分。Object Path的格式如"/foo/bar"。
对于Service Name和Object Path,QT4文档中有一个类比还是比较直观的,如下图所示:
图中的ftp.example.com可以看作是Service Name,/pub/something可以看作是Object Path。
D-Bus通过Signal/Method来发送和接收Message。Signal/Method可以理解为QT4中的Signal/Slot这个概念。一个Object可以提供多个Method/Signal,这些Method/Signal的集合又组成了Interface。
因此,D-Bus的这些概念从大到小可以表示为:Message Bus->Service->Object->[Interface]->Method/Signal。
其中,Interface是可选的。
D-Bus 调试工具
常用的D-Bus调试工具有 D-Feet、qdbusviewer等。
在C onsole窗口中键入qdbusviewer命令可以打开QT4自带的qdbusviewer 。
如上图所示,我们可以通过qdbusviewer来调用Object在Message Bus上发布的所有Method。
D -Bus 的 QT4 绑定
下面,我们通过一个实例来介绍D-Bus的QT4绑定。(参见hotel.pro)
我们在Session bus上创建一个"com.test.hotel" service,通过这个service可以执行check in,check out和query三个动作。
创建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; };
运行这个程序,我们可以使用qdbusviewer查看和操作这个Object。
通过QDBusMessage访问Service
在QT4中,用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"); }
通过QDBusInterface 访问Service
在QT4中,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"); }
看,用QDBusInterface来访问Service是不是更加方便?
从D-Bus XML自动生成Proxy类
用QDB usInterface访问Service已经非常方便了,但还不够直观。还有没有更直观的方法,就像访问本地类成员变量的方式访问远程的method?答案是Proxy。
Proxy Object提供了一种更加直观的方式来访问Service,就好像调用本地对象的方法一样。
概括的说,达成上述目标需要分三步走:
(1)使用工具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。
下面是举例(参见checkin3.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
如前文所述,我们可以直接把class Hotel注册为Message Bus上的一个Object,但这种方式并不是QT4所推荐的。QT4推荐使用Adapter来注册Object。
很多情况下,我们可能只需要把我们定义的类里的方法有选择的发布到Message Bus上,使用Adapter可以很方便的实现这种意图。
以前文中的Hotel为例,假设我们只需要把checkIn和checkOut发布到Message Bus上,应该怎么办?
(1)使用工具 qdbuscpp2xml从hotel.h生成XML文件;
qdbuscpp2xml -M hotel.h -o com.test.hotel.xml
(2)编辑com.test.hotel.xml,把其中的query部分去掉;
即去掉以下三条语句:
<method name="query">
<method name="query">
<arg type="i" direction="out"/>
</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(); }
运行这个应用程序,我们从qdbusviewer上可以看到,只有checkIn和checkOut两个method被发布。如下图所示:
自动启动Service
D-Bus系统提供了一种机制可以在访问某个service时,自动把该程序运行起来。
我们需要在/usr/share/dbus-1/services下面建立com.test.hotel.service文件,文件的内容如下:
[D-BUS Service]
Name=com.test.hotel
Exec=/path/to/your/hotel
这样,我们在访问Hotel的method之前,就不必手动运行该应用程序了。
作者:wwang
出处:http://www.cnblogs.com/wwang
本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
--------懒人评论(请勿重复点击)--------