1 D-Bus简介
D-Bus是Desktop Bus的缩写,是针对桌面环境优化的IPC(interprocess communication)机制,用于进程间的通信或进程与内核的通信。
IPC种类很多,适用的情景也不一样:CORBA 是用于面向对象编程中复杂的IPC的一个强大的解决方案。DCOP是一个较轻量级的IPC框架,功能较少,但是可以很好地集成到K桌面环境中。SOAP和XML-RPC设计用于Web 服务,因而使用HTTP作为其传输协议。D-BUS设计用于桌面应用程序和OS通信。D-Bus中D是代表桌面“Desktop”的意思,即用于桌面操作系统的通信通道。现在逐渐被引入到嵌入式系统中,不过名字还是保留原先的叫法而已。
典型的桌面都会有多个应用程序在运行,而且,它们经常需要彼此进行通信。DCOP是一个用于KDE的解决方案,但是它依赖于Qt,所以不能用于其他桌面环境之中。类似的,Bonobo是一个用于GNOME 的解决方案,但是非常笨重,因为它是基于CORBA 的。它还依赖于GObject,所以也不能用于GNOME之外。 D-BUS的目标是将DCOP和Bonobo替换为简单的IPC,并集成这两种桌面环境。由于尽可能地减少了D-BUS所需的依赖,所以其他可能会使用D-BUS的应用程序不用担心引入过多依赖。相对于其它的IPC,D-Bus丢掉了一些不必要的、复杂的东西,也正是因为这个原因,D-Bus比较快、简单。D-Bus不和低层的IPC直接竞争,比如sockets, shared memory 或者是message queues。这些低层点的IPC有它们自己的特点,和D-Bus并不冲突。
与其他重量级的进程间通信技术不同,D-Bus并未使用会话进行通信。D-Bus使用了状态以及连接的概念,使其比UDP等低级的信息传输协议更“聪明”。另一方面,它传送的是离散消息,这又与TCP协议将数据看做“流”有所不同。D-Bus支持点对点的传信,以及广播/订阅式的传信两种传信方式。
总之,D-Bus是轻量级、快速,为主流桌面环境提供统一的进程间通信界面。
2 D-Bus体系结构
D-Bus是按一定的层次结构实现的,总体上D-Bus分为三层:
Ø 底层库——libdbus,通过底层库的接口可以实现两个进程之间进行连接并发送消息。
Ø 消息通道守护进程(message bus daemon ),消息通道守护进程是基于底层库的,可以路由消息。
Ø 封装库(Wrapper libraries),将D-Bus绑定到某一通用的应用框架上,封装D-Bus底层接口为方便用户使用的通用API。
下面是对组侧D-Bus进程间通信系统的各部分的介绍。
2.1 D-Bus底层库(libdbus)
象网络套接字一样,libdbus只支持点对点的通信,即只支持一进程与另外的一个进程进行通信。通信的基于消息的,消息包含头部和消息体。
libdbus提供C语言的底层API,这些API是为D-Bus绑定到特点的对象或是语言中设计的,官方文档中建议不要在应用上直接使用底层D-Bus的底层接口。应用上使用D-Bus的绑定更方便,比如GLib绑定、Python绑定、Qt绑定和Mono绑定等。
2.2 消息通道守护进程(message bus daemon)
Bus daemon是一个特殊的进程:这个进程可以从一个进程传递消息给另外一个进程。当然了,如果有很多applications链接到这个通道上,这个 daemon进程就会把消息转发给这些链接的所有程序。在最底层,D-Bus只支持点对点的通信,一般使用本地套接字(AF_UNIX)在应用和bus daemon之间通信。D-Bus的点对点是经过bus daemon抽象过的,由bus daemon来完成寻址和发送消息,因此每个应用不必要关心要把消息发给哪个进程。D-Bus发送消息通常包含如下步骤:
Ø 创建和发送消息给后台bus daemon进程。
Ø 后台bus daemon进程会处理该消息。
Ø 目标程序接收到消息,然后根据消息的种类,做不同的响应:要么给个确认、要么应答、还有就是忽略它。
消息通道守护进程就象是一个路由器,连接到通道上的进程发送消息到通道,通道能把该消息路由到对应的一个或多个进程中去。因此在该层次上,实现了点对点通信的支持,也实现了广播/订阅通信方式。
通常消息守护进程在一个系统上有多个实例,典型的有两类实例:系统通道(system bus)和用户通道(session bus)。系统通道通常提供类似硬件变更之类消息,每个用户都有一条用户用户通道。每条用户通道仅供单独的用户使用,同时D-Bus也提供了用户权限机制供系统通道使用。
2.3 封装库(Wrapper libraries)
封装库是将libdbus的底层API绑定到特定的对象系统或是语言中,封装libdbus底层不方便在应用层使用的接口为方便用户使用的上层接口。象这类的封装库有libdbus-glib、libdbus-qt,本文档后面将就libdbus-glib上的使用进行简单介绍。
3 D-Bus术语
D-Bus中有很多术语有必要进行介绍,其中有“接口”、“对象”、“方法”等这些面向对象语言的概念,但其中的含义跟面向对象语言中的含义是不一样的。下面就对通道、地址、连接名、对象路径、对象、接口、方法和信号进行介绍。
在介绍之前,先就这些相关概念,给出一个整体的描述。正如D-Bus的体系结构中所述,D-Bus的第二层是消息通道守护进程(message bus daemon),通常一个daemon有多个实例,典型的有system bus和session bus两个实例,这里的daemon的实例就是通道。每个通道都有一个地址,应用进程就是通过这个地址和该通道进程连接的。对于通道上的每一个连接都有一个连接名对应,连接名也称bus name(很容易误会,但这里确实不是通道名,而是连接名)。每个连接上有至少一个对象,通常有多个对象,对象路径唯一标识着这个对象。对象要实现一个或多个接口,每个接口包含有若干个方法或信号。引入官方文档中的一个表格借以说明。
A... |
is identified by a(n)... |
which looks like... |
and is chosen by... |
Bus |
address |
unix:path=/var/run/dbus/system_bus_socket |
system configuration |
Connect- ion |
bus name |
:34-907 (unique) or com.mycompany.TextEditor (well-known) |
D-Bus (unique) or the owning program (well-known) |
Object |
path |
/com/mycompany/TextFileManager |
the owning program |
Interface |
interface name |
org.freedesktop.Hal.Manager |
the owning program |
Member |
member name |
ListNames |
the owning program |
3.1 通道(Bus)
在D-Bus中,通道(bus)是核心的概念:不同的程序可以通过这个通道做些操作,比如方法调用、发送信号和监听特定的信号。如前面所述通道通常有两种,系统通道(system bus)和用户通道(session bus),系统通道通常有一条,用户通道在用户登录时创建。
一条消息通道就是一个消息路由器,是消息通道守护进程(message bus daemon)的一个实例。通道在进程通信中扮演着重要的角色,但其本身也是一个进程,是一个比较特殊的进程,后面我们会介绍通道中的一些接口、方法和信号。
3.1.1 System bus
System bus是一个持久的通道,在系统启动时就创建,供系统内核和后台进程使用,具有较高的安全性。这种通道的最常用的方面就是发送系统消息,比如:插入一个新的存储设备、有新的网络连接等。
3.1.2 Session bus
Session bus是在某个用户登录后启动,属于某个用户私有,是某用户的应用程序用来通话的通道。在很多嵌入式系统中,只有一个用户ID登录运行,因此只有一个session bus,这是跟通常桌面系统中D-Bus不同的地方。
3.2 地址(Address)
每一条通道都有一个地址,进程通过这个地址连接到通道上去。连接建立有server和client,对于bus daemon,应用就是client;daemon是server。一个D-Bus的地址是指server用于监听,client用于连接的地方,例如unix:path=/tmp/abcedf标识server将在路径/tmp/abcedf的UNIX domain socket监听。地址可以是指定的TCP/IP socket或者其他在或者将在D-Bus协议中定义的传输方式。
如果使用bus daemon,libdbus将通过读取环境变量自动获取session bus damon的地址,通过检查一个指定的UNIX domain socket路径获取system bus的地址。如果使用D-bus,但不是daemon,需要定义那个应用是server,那个是client,并定义一套机制是他们认可server的地址,这样做起来很不方便,所以通常不采用。
3.3 连接名(Bus Name)
通道上的每个连接都有一个或多个名字。当连接建立以后,D-Bus 服务会分配一个不可改变的连接名,称为唯一连接名(unique connection name),这个连接名即使在进程结束后也不会再被其他进程所使用。唯一连接名以冒号开头,如:34-907″。但是这种名字总是临时分配,无法确定的,也难以记忆,因此应用可以要求有另外一个名字公共名(well-known name)来对应这个唯一标识,就像我们使用域名来对应IP地址一样。例如可以使用com.mycompany来映射:34-907。应用程序可能会要求拥有额外的周知名字公共名(well-known name)。例如,你可以写一个规范来定义一个名字叫做 com.mycompany.TextEditor。你的协议可以指定自己拥有这个名字,一个应用程序应该在路径/com/mycompany /TextFileManager下有一个支持接口org.freedesktop.FileHandler的对象。应用程序就可以发送消息到这个通道名字,对象,和接口以执行方法调用。
3.4 对象和对象路径(Object & Object Path)
这里的对象和面向对象语言中的对象含义是不同的,这里的对象表示的是D-Bus通道中信息流向的端点。对象由客户进程创建,并在连接进程中保持不变。所有使用D-BUS的应用程序都包含一些对象, 当经由一个D-BUS连接收到一条消息时,该消息是被发往一个对象而不是整个应用程序。 对象是客户进程提供服务的方式,当然客户进程可以创建多个对象。
通道的通信以对象为中心,其上的任何信息不外乎以下三类:
Ø 发送到对象上的请求;
Ø 从对象到请求进程的请求的响应;
Ø 单向的广播消息,发送到之前注册过这类消息的进程;
每条通道至少都有一个对象,这个对象代表通道本身。客户进程可以通过向这个通道对象发送请求来得到当前通道的状态。
对于底层的D-Bus协议,即libdbus API,并不理会这些native object,它们使用的是一个叫做对象路径(object path)的概念。通过object path,高层编程可以为对象实例进行命名,并允许远程应用引用它们。这些名字看起来像是文件系统路径,例如一个对象可能叫做 “/org/kde/kspread/sheets/3/cells/4/5”。
3.5 接口(Interface)
每一个对象支持一个或者多个接口,接口是一组方法和信号,接口定义一个对象实体的类型。D-Bus对接口的命名方式,类似org.freedesktop.Introspectable。开发人员通常将使用编程语言类的的名字作为接口名字。
与JAVA 类似,接口就是成员声明的集合。接口的实现就等于说这个接口提供了其所有方法及信号。所有成员必须按照接口声明的那样来提供服务。当客户进程要执行一个对象方法或等待信号时,它必须指明要使用的对象及其对象成员。除此以外,客户可能还要指明对象成员所在的接口,有时这是必须的步骤。比如,当对象同时在其两个接口都实现foo 方法时,若客户不指明要执行的是哪个接口上的foo 方法,则会产生歧义。D-Bus 的实现也可能会直接拒绝这类歧义请求。同样地,如果在订阅信号时不指明接口,则也有可能会收到不同接口发来的相同名字的信号。
3.6 方法(Methods)
客户向某对象发送一个请求的过程,看起来就像在这个对象上执行了一个方法:对象被请求执行一个明确的,有名称的动作。如果客户请求执行一个目标对象未提供的方法,将会产生一个错误。方法的定义中可以支持输入参数。对于每个请求,都有一个包含请求结果以及结果数据(输出参数)
的响应返回给请求者。当请求无法完成时,响应中将包含异常信息,其中至少有异常名称以及错误信息。
大多数语言都将这些封装在自身的语言机制中,比如将参数包装进消息包,将异常信息转换成语言自身的异常等等。在这些实现中,向远程对象传递一个字符串参数就好像是在本地执行一个字符串参数的函数一样简单。此时不再需要数据类型转换、数据复制等繁琐工作,语言本身封装了一切低层实现。
3.7 信号(Signals)
信号,这种通信形式依然遵从面向对象概念,它是从对象发出但没有特定目的地址的单向数据广播。客户进程可以预先注册其感兴趣的信号,如特定名称的信号或从某个对象发出的信号等。当对象发出信号后,所有订阅了该信号的客户将收到此信号的复本。接收端可能有多种情况出现,或者有一个客户,或者有多个客户,或者根本没有客户对这个信号感兴趣。对于信号来说没有响应消息,发出信号的对象不会知道是不是有客户在接收,有多少客户接收,以及从客户端收到任何反馈。
与方法类似的是,信号可以有参数。由于信号是单向通信,因此其不可能像方法一样具有输入输出参数。D-Bus 的最新版本允许客户通过参数比对过滤其需要的信号。
信号一般用来广播一些客户可能会感兴趣的事件,比如某个其他的客户与通道的连接断开等。这些信号来自通道对象,因此从信号中客户可以分辨断线是由于正常退出、被杀掉或者程序崩溃。
通过上面的描述,我们可以获得下面的视图:
Address –> [Bus Name] –> Path –> Interface –> Method
bus name不是必要的,它只在daemon的情况下用于路由,点对点的直接连接是不需要的。
简单地说 :Address是D-Bus中server用来监听client的地址,当一个client连接上D-Bus,这个client就有了一个Bus Name。其他应用可以根据消息中所带的Bus Name,来判断和哪个应用相关。消息在通道中传递的时候,传递到应用中,再根据object path,送至应用中具体的对象实例中,也就是是应用中根据Interface创建的对象。这些Interface有method和singal两种,用来发送、接收、响应消息。
3.8 代理(Proxies)
通道上的对象一般通过代理来访问。通道上的对象位于客户进程以外,而客户可以调用的本地接口与对象通信,此时,本地接口充当了代理的角色。当我们触发了代理对象的方法时,将会在D-Bus上发送一个method_call的消息,并等待答复返回,就象使用一个本地对象一样。
一些语言的代理支持“断线重连”。比如你所连接的对象在某段时间里暂时断开了与通道的连接,代理会自动重连到相同的连接名并重新找到对象,你的程序甚至不会知道目标对象有段时间不可用。并不是所有的语言都支持这一特性,在GLib中的两种代理中的一种支持。
比如不用代理时的代码如下:
Message message = new Message("/remote/object/path", "MethodName", arg1, arg2);
Connection connection = getBusConnection();
connection.send(message);
Message reply = connection.waitForReply(message);
if (reply.isError()) {
}
else {
Object returnValue = reply.getReturnValue();
}
采用代理时对应的代码则是:
Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
Object returnValue = proxy.MethodName(arg1, arg2);
4 消息和消息通道
D-Bus通信机制是通过进程间发送消息实现的,最基本的D-Bus协议是一对一的通信协议。与直接使用socket 不同,D-Bus是面向消息的协议。但如果你是使用高层的绑定接口的话,那你就不会直接接触到D-Bus的消息,为了进一步认识D-Bus在本文档中将对消息和消息通道进行介绍。
D-Bus 有四种类型的消息:
Ø method_call 方法调用
Ø method_return 方法返回
Ø error 错误
Ø signal 信号
代理中的远程对象调用就涉及到了消息通道和method_call和method_return两类消息。
4.1 消息
一个消息有消息头(header)和消息体(body)。消息头包含消息体的路由信息,消息体就是净荷,里面通常包含的是参数。消息头通常包含有发送进程的连接名(Bus Name)、方法或者信号名等等,其中有一字段是用于描述消息体中的参数的类型的,例如“i”标识32位整数,“ii”表示净荷为2个32位整数。
为了对消息有一个实在的感觉,接下来对发送method_call和发送signal的场景进行描述:
4.1.1 调用method
进程A要调用进程B的一个method,进程A发送method_call消息到进程B,进程B回复method_return消息。在发送消息时,发送方会在消息中添加不同的序列号,同样,回复消息中也会含有序列号,以便对应。
Ø 在发送消息时,如果使用了代理,进程A要调用进程B的某方法,不用构造消息,只需调用代理的本地方法,代理会生成消息发送到消息通道上去。
Ø 如果使用底层API的话,进程A需要构造一个方法调用消息。不管怎样,方法调用消息都包含有对应进程B的连接名、方法名、方法所需参数、进程B中的对象路径和进程B中声明此方法的接口。
Ø 将消息发送到消息通道。
Ø 信息通道检查消息头中的目的连接名,当找到一个进程与此连接名对应时发送消息到该进程。当找不到一个进程与此连接名对应时,返回给进程A一个error消息。
Ø 进程B解析消息,如果是采用底层API方式,则直接调用方法直接发送方法返回消息到信息通道。如果是采用代理,则先进行对象路径、接口、方法名等的检查,然后调用本地对应方法,再把本地方法的返回值封装成方法返回消息发送到消息通道。
Ø 消息通道将方法返回消息发送到进程A。
消息通道不对通道上的消息进行重排序,
4.1.2 发送signal
发送信号是单向广播的,信号的发送者不知道对这个信号作响应的有哪些进程,所以信号发送是没有返回值的。信号接收者通过想消息通道注册匹配规则来表示对某信号感兴趣,而匹配规则通常包含信号的发出者和信号名。
Ø 发送本地信号消息到消息通道。(通常信号消息包含有声明该信号的接口名、信号名、所在进程对应的连接名和相关参数)
Ø 连接到消息通道上的进程如果对某个信号感兴趣,则注册相应的匹配规则。消息通道保持有匹配规则表。
Ø 消息通道根据匹配规则表,将信号发送到对该信号感兴趣的进程去。
Ø 各感兴趣进程对信号做出响应。
4.2 dbus-send和dbus-monitor
D-Bus提供了两个小工具:dbus-send 和dbus-monitor。我们可以用dbus-send 发送消息。用dbus-monitor 监视通道上流动的消息。
先给出dbus-send和dbus-monitor的用法:
dbus-send
功能 |
用于发送一个消息到消息通道上 |
语法 |
dbus-send [--system | --session] --type=TYPE --print-reply --dest=连接名对象路径接口名.方法名参数类型:参数值参数类型:参数值 |
使用示例 |
dbus-send --session --type=method_call --print-reply --dest=连接名对象路径接口名.方法名参数类型:参数值参数类型:参数值 |
dbus-send 支持的参数类型包括:string, int32, uint32, double, byte, boolean。
dbus-monitor
功能 |
打印消息通道上的消息 |
语法 |
dbus-monitor [--system | --session | --address ADDRESS] [--profile | --monitor] [watch expressions] |
使用示例 |
dbus-monitor "type='signal', sender='org.gnome.TypingMonitor', interface= 'org.gnome.TypingMonitor'" |
4.3 消息通道上的方法和信号
消息通道是一个特殊的应用,下面将对消息通道进行介绍,主要关于消息通道上的方法和信号。
4.3.1 Introspection
消息通道上有org.freedesktop.DBus.Introspectable接口,该接口中声明了一个叫Introspect 名的方法。Introspect方法不需要参数,返回XML字符串,XML字符串描述对象的接口、方法和信号。
4.3.2 消息通道上的方法和信号
我们可以通过向连接“org.freedesktop.DBus ”上对象“/”发送消息来调用消息通道提供的方法。消息通道对象支持标准接口"org.freedesktop.DBus.Introspectable",我们可以调用org.freedesktop.DBus.Introspectable.Introspect方法查看消息通道对象支持的接口。
dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect
输出为:
method return sender=org.freedesktop.DBus -> dest=:1.20 reply_serial=2
string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" direction="out" type="s"/>
</method>
</interface>
......
用户通道对象支持标准接口“org.freedesktop.DBus.Introspectable”和接口 “org.freedesktop.DBus”。接口“org.freedesktop.DBus”有16个方法和3个信号。下表列出了 “org.freedesktop.DBus”的12个方法的简要说明:
org.freedesktop.DBus.RequestName (in STRING name, in UINT32 flags, out UINT32 reply) |
请求公众名。其中flag定义如下: DBUS_NAME_FLAG_ALLOW_REPLACEMENT 1 DBUS_NAME_FLAG_REPLACE_EXISTING 2 DBUS_NAME_FLAG_DO_NOT_QUEUE 4 返回值reply定义如下: DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 DBUS_REQUEST_NAME_REPLY_EXISTS 3 DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 |
org.freedesktop.DBus.ReleaseName (in STRING name, out UINT32 reply) |
释放公众名。返回值reply定义如下: DBUS_RELEASE_NAME_REPLY_RELEASED 1 DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 |
org.freedesktop.DBus.Hello (out STRING unique_name) |
一个应用在通过消息通道向其它应用发消息前必须先调用Hello获取自己这个连接的唯一名。返回值就是连接的唯一名。dbus没有定义专门的切断连接命令,关闭socket就是切断连接。 在1.2节的dbus-monitor输出中可以看到dbus-send调用消息通道的Hello方法。 |
org.freedesktop.DBus.ListNames (out ARRAY of STRING bus_names) |
返回消息通道上已连接的所有连接名,包括所有公共名和唯一名。例如连接“org.fmddlmyy.Test”同时有公共名“org.fmddlmyy.Test”和唯一名“:1.21”,这两个名称都会被返回。 |
org.freedesktop.DBus.ListActivatableNames (out ARRAY of STRING bus_names) |
返回所有可以启动的服务名。dbus支持按需启动服务,即根据应用程序的请求启动服务。 |
org.freedesktop.DBus.NameHasOwner (in STRING name, out BOOLEAN has_owner) |
检查是否有连接拥有指定名称。 |
org.freedesktop.DBus.StartServiceByName (in STRING name, in UINT32 flags, out UINT32 ret_val) |
按名称启动服务。参数flags暂未使用。返回值ret_val定义如下: 1 服务被成功启动 2 已经有连接拥有要启动的服务名 |
org.freedesktop.DBus.GetNameOwner (in STRING name, out STRING unique_connection_name) |
返回拥有指定公众名的连接的唯一名。 |
org.freedesktop.DBus.GetConnectionUnixUser (in STRING connection_name, out UINT32 unix_user_id) |
返回指定连接对应的服务器进程的Unix用户id。 |
org.freedesktop.DBus.AddMatch (in STRING rule) |
为当前连接增加匹配规则。 |
org.freedesktop.DBus.RemoveMatch (in STRING rule) |
为当前连接去掉指定匹配规则。 |
org.freedesktop.DBus.GetId (out STRING id) |
返回消息通道的ID。这个ID在消息通道的生命期内是唯一的。 |
接口“org.freedesktop.DBus”的3个信号是:
org.freedesktop.DBus.NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner) |
指定名称的拥有者发生了变化。 |
org.freedesktop.DBus.NameLost (STRING name) |
通知应用失去了指定名称的拥有权。 |
org.freedesktop.DBus.NameAcquired (STRING name) |
通知应用获得了指定名称的拥有权。 |
5 D-Bus使用(dbus-glib)
使用D-Bus可以同步调用远程方法,也可以异步调用远程方法。本文档中利用dbus-glib对使用D-Bus同步调用远程方法和异步调用远程方法各举例进行说明。以及对信号的发送和使用进行说明。
5.1 D-Bus类型和GType的映射
类型映射是使用D-Bus的glib绑定的基础。在D-Bus的glib绑定中包含基本类型的映射和container类型的映射。下面分别给出基本类型的映射和container类型的映射。
基本类型的映射:
D-Bus basic type |
GType |
Free function |
Notes |
BYTE |
G_TYPE_UCHAR |
|
|
BOOLEAN |
G_TYPE_BOOLEAN |
|
|
INT16 |
G_TYPE_INT |
|
Will be changed to a G_TYPE_INT16 once GLib has it |
UINT16 |
G_TYPE_UINT |
|
Will be changed to a G_TYPE_UINT16 once GLib has it |
INT32 |
G_TYPE_INT |
|
Will be changed to a G_TYPE_INT32 once GLib has it |
UINT32 |
G_TYPE_UINT |
|
Will be changed to a G_TYPE_UINT32 once GLib has it |
INT64 |
G_TYPE_GINT64 |
|
|
UINT64 |
G_TYPE_GUINT64 |
|
|
DOUBLE |
G_TYPE_DOUBLE |
|
|
STRING |
G_TYPE_STRING |
g_free |
|
OBJECT_PATH |
DBUS_TYPE_G_PROXY |
g_object_unref |
The returned proxy does not have an interface set; use dbus_g_proxy_set_interface to invoke methods |
Container类型的映射
D-Bus type signature |
Description |
GType |
C typedef |
Free function |
Notes |
|
as |
Array of strings |
G_TYPE_STRV |
char ** |
g_strfreev |
|
|
v |
Generic value container |
G_TYPE_VALUE |
GValue * |
g_value_unset |
The calling conventions for values expect that method callers have allocated return values; see below. |
|
ay |
Array of bytes |
DBUS_TYPE_G_BYTE_ARRAY |
GArray * |
g_array_free |
|
|
au |
Array of uint |
DBUS_TYPE_G_UINT_ARRAY |
GArray * |
g_array_free |
|
|
ai |
Array of int |
DBUS_TYPE_G_INT_ARRAY |
GArray * |
g_array_free |
|
|
ax |
Array of int64 |
DBUS_TYPE_G_INT64_ARRAY |
GArray * |
g_array_free |
|
|
at |
Array of uint64 |
DBUS_TYPE_G_UINT64_ARRAY |
GArray * |
g_array_free |
|
|
ad |
Array of double |
DBUS_TYPE_G_DOUBLE_ARRAY |
GArray * |
g_array_free |
|
|
ab |
Array of boolean |
DBUS_TYPE_G_BOOLEAN_ARRAY |
GArray * |
g_array_free |
|
|
a{ss} |
Dictionary mapping strings to strings |
DBUS_TYPE_G_STRING_STRING_HASHTABLE |
GHashTable * |
g_hash_table_destroy |
|
5.2 使用dbus-glib的makefile参考
下面是一个使用了dbus-x、dbus-glib-x、glib-2.0的makefile参考:
CC = gcc
CFLAGS += -Wall -g `pkg-config dbus-x dbus-glib-x glib-2.0 --cflags`
LIBS += -Wall -g `pkg-config dbus-x dbus-glib-x glib-2.0 --libs`
TARGET = sample
OBJ = $(TARGET).o
all: $(TARGET)
%o: %c
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJ)
$(CC) $(LIBS) -o $(TARGET) $(OBJ)
5.3 同步调用远程方法
同步调用远程方法,需要先创建一个原创对象的代理,然后用阻塞型函数dbu_g_proxy_call函数来同步调用远程方法,获得返回值。
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus-glib.h>
int main( int argc , char ** argv)
{
GError * error;
DBusGConnection * connection;
DBusGProxy * proxy;
char * string;
/* GType初始化 */
g_type_init();
error = NULL;
/* dbus_g_bus_get用来建立连接,这里和session bus连接,也可以通过DBUS_BUS_SYSTEM与系统通道连接*/
connection = dbus_g_bus_get (DBUS_BUS_SESSION, & error);
if(connection == NULL){
g_printerr ("Failed to open connection to bus : %s/n",error->message);
g_error_free( error);
exit( 1 );
}
/* Create a proxy object用来代表远端org.freedesktop.Notifications是系统带有的,可以使用DBUS_INTERFACE_INTROSPECTABLE等定义来标识它*/
proxy = dbus_g_proxy_new_for_name (connection,
"org.freedesktop.Notifications" /* service */ ,
"/" /* path */ ,
"org.freedesktop.Notifications" /* interface */ );
error = NULL;
/* 采用同步方式*/
if( !dbus_g_proxy_call (proxy,"Introspect",&error, G_TYPE_INVALID,G_TYPE_STRING, &string, G_TYPE_INVALID) ){
if(error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
g_printerr("Caught remote method exception %s:%s", dbus_g_error_get_name(error), error->message);
else
g_printerr("Error : %s/n", error->message);
g_error_free(error);
exit (1);
}
g_print("Message Method return from bus:/n%s/n",string);
g_free(string);
g_object_unref(proxy);
return 0;
}
上述代码中,实现同步调用的关键是使用了dbus-g-proxy_call函数,函数的第一个参数是远程接口,第二个参数是要调用的远程接口中的方法,第三个参数是GError的指针的指针,接下来是参数类型和参数的列表、返回值类型和返回值参数,之间用G_TYPE_INVALID作间隔。
5.4 异步调用远程方法
很多时候,程序执行时不需要等函数执行结果的返回,而是继续执行,在函数执行完时触发一个回调函数进程处理,这也就是我们程序中异步调用的需求。下面就dbus-glib异步调用远程方法进行举例说明。
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus-glib.h>
static GMainLoop * main_loop;
/* 回调函数定义 */
static void my_callback_func (DBusGProxy *proxy, DBusGProxyCall *call_id, void *user_data)
{
GError * error = NULL;
gchar * string = NULL;
/* 结束一个消息的收发,处理收到的消息,获取返回值或者error信息 */
dbus_g_proxy_end_call (proxy, call_id,&error, G_TYPE_STRING, &string,G_TYPE_INVALID);
if(error != NULL){
g_print("Error in method call : %s/n", error->message);
g_error_free(error);
}else{
g_print("SUCCESS,it is now %s/n",string);
}
g_main_loop_quit(main_loop);
}
int main( int argc , char ** argv)
{
GError * error = NULL;
DBusGConnection * connection;
DBusGProxy * proxy;
g_type_init();
main_loop = g_main_loop_new(NULL,TRUE);
connection = dbus_g_bus_get (DBUS_BUS_SESSION, & error);
if(connection == NULL){
g_printerr ("Failed to open connection to bus : %s/n",
error->message);
g_error_free( error);
exit( 1 );
}
/* Create a proxy object for the 'bus driver' named org.freedesktop.DBus */
proxy = dbus_g_proxy_new_for_name (connection, "org.freedesktop.Notifications", "/", DBUS_INTERFACE_INTROSPECTABLE);
/* 异步触发,也可以带上一个超时的时间限制,使用dbus_g_proxy_call_with_timeout 。这里的参数只需带上输入的情况。第四个参数为携带到回调函数的user_data,第五个参数标识释放user_data的函数,例如g_free等*/
dbus_g_proxy_begin_call (proxy, "Introspect", my_callback_func, NULL, NULL, G_TYPE_INVALID);
g_main_loop_run(main_loop);
return 0;
}
异步调用远程方法是使用函数对dbus_g_proxy_begin_call和dbus_g_proxy_end_call来实现的,dbus_g_proxy_begin_call函数带上一个超时的时间限制,使用dbus_g_proxy_call_with_timeout来代替。函数的具体使用参见附录。
5.5 信号的发送和处理
由前面章节对信号发送的情景描述我们可知,连接到消息通道上的应用会向消息通道注册一些自己感兴趣的信号,也就是向消息通道注册一些信号匹配规则。消息通道维持着一个匹配规则表。当有应用发送信号到消息通道上时,消息通道通过查询匹配规则表,将信号消息发送到对该信号感兴趣的进程上去。下面是一个发送和一个感兴趣的接收处理代码实例。
发送信号:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>
int send_a_signal( char * sigvalue)
{
DBusError err;
DBusConnection * connection;
DBusMessage * msg;
DBusMessageIter arg;
dbus_uint32_t serial = 0;
int ret;
//步骤1:建立与D-Bus后台的连接
/* initialise the erroes */
dbus_error_init(&err);
/* Connect to Bus*/
connection = dbus_bus_get(DBUS_BUS_SESSION , &err );
if(dbus_error_is_set(&err)){
fprintf(stderr,"Connection Err : %s/n",err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return -1;
//步骤2:给连接名分配一个well-known的名字作为Bus name,这个步骤不是必须的。
ret = dbus_bus_request_name (connection, "test.singal.source", DBUS_NAME_FLAG_REPLACE_EXISTING, &err );
if(dbus_error_is_set(&err)){
fprintf(stderr,"Name Err : %s/n",err.message);
dbus_error_free(&err);
}
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return -1;
//步骤3:发送一个信号
if((msg = dbus_message_new_signal ("/test/signal/Object","test.signal.Type","Test")) == NULL){
fprintf(stderr,"Message NULL/n");
return -1;
}
//给这个信号(messge)具体的内容
dbus_message_iter_init_append (msg,&arg);
if(!dbus_message_iter_append_basic (&arg,DBUS_TYPE_STRING,&sigvalue)){
fprintf(stderr,"Out Of Memory!/n");
return -1;
}
//步骤4: 将信号从连接中发送
if( !dbus_connection_send (connection,msg,&serial)){
fprintf(stderr,"Out of Memory!/n");
return -1;
}
dbus_connection_flush (connection);
printf("Signal Send/n");
//步骤5: 释放相关的分配的内存。
dbus_message_unref(msg );
return 0;
}
int main( int argc , char ** argv){
send_a_signal("Hello,world!");
return 0;
}
对该信号感兴趣的接收端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>
void listen_signal()
{
DBusMessage * msg;
DBusMessageIter arg;
DBusConnection * connection;
DBusError err;
int ret;
char * sigvalue;
//步骤1:建立与D-Bus后台的连接
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr,"Connection Error %s/n",err.message);
dbus_error_free(&err);
}
if(connection == NULL)
return;
//步骤2:给连接名分配一个可记忆名字test.singal.dest作为Bus name,这个步骤不是必须的,但推荐这样处理
ret = dbus_bus_request_name (connection, "test.singal.dest", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)){
fprintf(stderr,"Name Error %s/n",err.message);
dbus_error_free(&err);
}
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return;
//步骤3:通知D-Bus daemon,希望监听来行接口test.signal.Type的信号
dbus_bus_add_match(connection,"type='signal',interface='test.signal.Type'",&err);
//实际需要发送东西给daemon来通知希望监听的内容,所以需要flush
dbus_connection_flush(connection);
if(dbus_error_is_set(&err)){
fprintf(stderr,"Match Error %s/n",err.message);
dbus_error_free(&err);
}
//步骤4:在循环中监听,每隔开1秒,就去试图自己的连接中获取这个信号。这里给出的是中连接中获取任何消息的方式,所以获取后去检查一下这个消息是否我们期望的信号,并获取内容。我们也可以通过这个方式来获取method call消息。
while(1){
dbus_connection_read_write(connection,0);
msg = dbus_connection_pop_message (connection);
if(msg == NULL){
sleep(1);
continue;
}
if(dbus_message_is_signal(msg,"test.signal.Type","Test") ){
if(!dbus_message_iter_init(msg,&arg) )
fprintf(stderr,"Message Has no Param");
else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
g_printerr("Param is not string");
else
dbus_message_iter_get_basic(&arg,&sigvalue);
printf("Got Singal with value : %s/n",sigvalue);
}
dbus_message_unref(msg);
}//End of while
}
int main( int argc , char ** argv){
listen_signal();
return 0;
}
上面便是发送和接收处理信号的过程。
6 Reference
感谢freedesktop官网提供的资料,感谢下面参考文章中的作者,感谢网友freeworkzz的IntroductionToDBus中文意译。本文档实属整理类文档。
[1] IntroductionToDBus, freedesktop.org
[2] D-Bus Tutorial, freedesktop.org
[3] dbus-specification, freedesktop.org
[4] 吕杰, D-Bus实例讲解, csdn.net
[5] 张泽, D-Bus学习, csdn.net
[6] D-Bus - Wikipedia, Wikipedia.org
[7] D-Bus Glib bindings - Reference Ranual, freedesktop.org
来源:CSDN
作者:nero_cie
链接:https://blog.csdn.net/nero_cie/article/details/6153804