使用Glib封装过的D-Bus
这部分的内容:
- GObject介绍
- 使用XML文件定义D-Bus接口
- 自动生成proxy/stub代码
- 创建一个简单的D-Bus对象
- 通过D-Bus发布一个GType类型
- 客户端如何使用Glib封装过的D-Bus
- D-Bus的自省功能
GObject介绍
为了在运行时把GTK+ widgets绑定到解释语言,一些牛人就用C语言实现了相对难以理解的面向对象的机制。根据你个人的爱好,你可以把这种面向对象称之为GObject或者GType。GType是GObject的底层基础。GObject/GType是Glib的一部分,并且单独编译成一个库:libgobject,对于GObject的详细介绍,大家可以到网上google一下,另外还要仔细阅读其代码,非常巧妙的实现!
这里的例子:实现一个非继承的类,类的接口去访问和修改两个私有的成员变量:value1和vaule2, value1是32位的整数,value2是一个gdouble类型的数据。
我们需要实现类构造函数和对象构造函数。这里,这两个构造函数都比较简短。如果你了解C++,你会发现这里怎么有两个构造函数呢?C++只有一个啊!不错,这就是GObject别扭的地方。慢慢你就会习惯的,当你很喜欢它后,你可能觉得C++是个另类。因为你了解C++的构造函数比较早,这个晚了些时候。
使用XML文件定义D-Bus接口:
由于我们主要的目的是做一个可以在D-Bus中使用的对象,因此我们从一个最简单的地方入手,通过dbus-binding-tool工具,这个工具会自动由XML文件生成client和server端的代码。我们就是把需要的接口安装XML语法定义在一个XML文件中就行了。
定义“方法”,即函数:用method来包含,其name是指定这个函数的名字,这个指定的名字会拷贝到生成的stub代码里面;函数method带一个参数:new_value, 用<arg />包含。其中对于参数类型(type)的的形式,需要参考D-Bus的参数类型定义。
<!-- setvalue1(int newValue): sets value1 -->
<method name="setvalue1">
<arg type="i" name="new_value" direction="in"/>
</method>
// direction表示入口参数还是出口参数,in:入口; out: 出口参数,如果不指定in或者out, 默认为in;
// type 的指定要参考D-Bus的类型定义,见下表;
其中,最难搞的就是要严格指定参数(arg)的类型(type),正是这个type来定义参数的数据类型,D-Bus定义的参数类型如下:
|
Conventional Name
|
Code
|
Description
|
|
INVALID
|
0 (ASCII NUL)
|
Not a valid type code, used to terminate signatures
|
|
BYTE
|
121 (ASCII 'y')
|
8-bit unsigned integer
|
|
BOOLEAN
|
98 (ASCII 'b')
|
Boolean value, 0 is FALSE and 1 is TRUE. Everything else is invalid.
|
|
INT16
|
110 (ASCII 'n')
|
16-bit signed integer
|
|
UINT16
|
113 (ASCII 'q')
|
16-bit unsigned integer
|
|
INT32
|
105 (ASCII 'i')
|
32-bit signed integer
|
|
UINT32
|
117 (ASCII 'u')
|
32-bit unsigned integer
|
|
INT64
|
120 (ASCII 'x')
|
64-bit signed integer
|
|
UINT64
|
116 (ASCII 't')
|
64-bit unsigned integer
|
|
DOUBLE
|
100 (ASCII 'd')
|
IEEE 754 double
|
|
STRING
|
115 (ASCII 's')
|
UTF-8 string (must be valid UTF-8). Must be nul terminated and contain no other nul bytes.
|
|
OBJECT_PATH
|
111 (ASCII 'o')
|
Name of an object instance
|
|
SIGNATURE
|
103 (ASCII 'g')
|
A type signature
|
|
ARRAY
|
97 (ASCII 'a')
|
Array
|
|
STRUCT
|
114 (ASCII 'r'), 40 (ASCII '('), 41 (ASCII ')')
|
Struct
|
|
VARIANT
|
118 (ASCII 'v')
|
Variant type (the type of the value is part of the value itself)
|
|
DICT_ENTRY
|
101 (ASCII 'e'), 123 (ASCII '{'), 125 (ASCII '}')
|
Entry in a dict or map (array of key-value pairs)
|
我用蓝色标出的是比较常用的,你要根据你参数的需要,把上述第二列的类型码写到XML文件中去。开始你可能不习惯,慢慢就好了。
另外,D-Bus本身并不限制返回参数的个数,但是C语言只支持一个返回参数,因此如果你需要把其它需要携带回来的参数当作出口参数处理。其它的一些高级语言并不像C语言这样有限制。
下面是D-Bus函数所能支持的参数类型:(括号中是Glib对应的类型):
- b: boolean (gboolean)
- y: 8-bit unsigned integer (guint8)
- q/n: 16-bit unsigned/signed integer (guint16/gint16)
- u/i: 32-bit unsigned/signed integer (guint32/gint32)
- t/x: 64-bit unsigned/signed integer (guint64/gint64)
- d: IEEE 754 double precision floating point number (gdouble)
- s: UTF-8 encoded text string with NUL termination (only one NUL allowed) (gchar* with additional restrictions)
- a: Array of the following type specification (case-dependent)
- o/g/r/(/)/v/e/{/}: Complex types, please see the official D-Bus documentation on type signatures.
从这个列表可以看出,我们上面定义的函数:setvalue1有一个32位整形参数(new_value).这里定义的参数名称:new_value将会影响到所生成的stub代码,对于生成文档和D-Bus自省是非常有用的。
下面我们再定义另外一个函数:getvalue1, 这个函数用于返回当前对象的整数成员的值,没有入口参数,只有出口参数: cur_value。具体定义见下:
<!-- getvalue1(): returns the first value (int) -->
<method name="getvalue1">
<arg type="i" name="cur_value" direction="out"/>
</method>
我们已经知道,D-Bus的method是隶属于interface的,就说一个interface可以有N个method, 在XML中,我们把这些method元素包含在interface元素中,借以表达这种隶属关系。这个interface的名字属性是可选的,你可以指定,也可以不指定,我们强烈推荐你写上这个名字,一是防止各个模块的interface重名,另外一个重要的功用是为了introspection.
再进一步,method隶属于interface, 那么interface又属于谁呢?隶属于object, 一个object可以有N个interface. 我们把interface元素包含在node元素内。在XML中,node是最顶层的元素了。我们这个例子里面,只实现了一个interface(binding tool会自动增加introspection接口的,因此不必在XML文件中指定),到此,我们就写完了一个最基本的XML文件,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<node>
<interface name="org.maemo.Value">
<!-- getvalue1(): returns the first value (int) -->
<method name="getvalue1">
<arg type="i" name="cur_value" direction="out"/>
</method>
<!-- setvalue1(int newValue): sets value1 -->
<method name="setvalue1">
<arg type="i" name="new_value" direction="in"/>
</method>
</interface>
</node>
再对上面的最基本的XML接口定义文件做些扩充:增加DTD扫描,其实这个可以不用。另外还有就是增加了2个函数定义:get_value2, set_value2。
如果你需要写XML文件,强烈建议你在一个模板基础上写,就是拿个没有基本语法错误的模板,然后增加修改你的接口,这样容易成功,不要白手起家,不要在这个XML文件上面做过多的纠缠。
<?xml version="1.0" encoding="UTF-8" ?>
<!-- This maemo code example is licensed under a MIT-style license,
that can be found in the file called "License" in the same
directory as this file.
Copyright (c) 2007 Nokia Corporation. All rights reserved. -->
<!-- If you keep the following DOCTYPE tag in your interface
specification, xmllint can fetch the DTD over the Internet
for validation automatically.//自动对你定义的接口、方法、参数进行扫描,检查其合法性,如果你没有联网,就不要加这个了 -->
<!DOCTYPE node PUBLIC
"-//freedesktop//DTD D-Bus Object Introspection 1.0//EN"
"http://standards.freedesktop.org/dbus/1.0/introspect.dtd">
<!-- This file defines the D-Bus interface for a simple object, that
will hold a simple state consisting of two values (one a 32-bit
integer, the other a double).
The interface name is "org.maemo.Value".
One known reference implementation is provided for it by the
"/GlobalValue" object found via a well-known name of
"org.maemo.Platdev_ex". -->
<node>
<interface name="org.maemo.Value">
<!-- Method definitions -->
<!-- getvalue1(): returns the first value (int) -->
<method name="getvalue1">
<!-- NOTE Naming arguments is not mandatory, but is recommended
so that D-Bus introspection tools are more useful.
Otherwise the arguments will be automatically named
"arg0", "arg1" and so on. -->
<arg type="i" name="cur_value" direction="out"/>
</method>
<!-- getvalue2(): returns the second value (double) -->
<method name="getvalue2">
<arg type="d" name="cur_value" direction="out"/>
</method>
<!-- setvalue1(int newValue): sets value1 -->
<method name="setvalue1">
<arg type="i" name="new_value" direction="in"/>
</method>
<!-- setvalue2(double newValue): sets value2 -->
<method name="setvalue2">
<arg type="d" name="new_value" direction="in"/>
</method>
</interface>
</node>
写完XML文件后,下面我们是不是可以使用D-Bus工具来生成代码了?别急!我们还要对XML文件做些自动检查。主要检查两个方面:是否符合XML1.0规范;验证XML结构(即:元素是否成对匹配)。结构验证的规则是由DTD(Document Type Definition)文档规定的。D-Bus规定的XML格式如下:
|
<!-- DTD for D-BUS Introspection data -->
<!-- (C) 2005-02-02 David A. Wheeler; released under the D-BUS licenses,
GNU GPL version 2 (or greater) and AFL 1.1 (or greater) -->
<!-- see D-BUS specification for documentation -->
<!ELEMENT node (interface*,node*)>
<!ATTLIST node name CDATA #REQUIRED>
<!ELEMENT interface (annotation*,method*,signal*,property*)>
<!ATTLIST interface name CDATA #REQUIRED>
<!ELEMENT method (annotation*,arg*)>
<!ATTLIST method name CDATA #REQUIRED>
<!ELEMENT arg EMPTY>
<!ATTLIST arg name CDATA #IMPLIED>
<!ATTLIST arg type CDATA #REQUIRED>
<!-- Method arguments SHOULD include "direction",
while signal and error arguments SHOULD not (since there's no point).
The DTD format can't express that subtlety. -->
<!ATTLIST arg direction (in|out) "in">
<!ELEMENT signal (arg,annotation)>
<!ATTLIST signal name CDATA #REQUIRED>
<!ELEMENT property (annotation)> <!-- AKA "attribute" -->
<!ATTLIST property name CDATA #REQUIRED>
<!ATTLIST property type CDATA #REQUIRED>
<!ATTLIST property access (read|write|readwrite) #REQUIRED>
<!ELEMENT annotation EMPTY> <!-- Generic metadata -->
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
|
然后根据这个模板对你定义的XML文档进行规则检查。
仅仅检查了DTD有效性,还不完美:因为DTD类型检查只是对语法做检查,并不能对语义/含义做检查。
为了检查语义,我们使用另外一个工具:checkxml, 把它写到你的makefile中:
# One extra target (which requires xmllint, from package libxml2-utils)
# is available to verify the well-formedness and the structure of the
# interface definition xml file.
#
# Use the 'checkxml' target to run the interface XML through xmllint
# verification. You'll need to be connected to the Internet in order
# for xmllint to retrieve the DTD from fd.o (unless you setup local
# catalogs, which are not covered here).
# ... Listing cut for brevity ...
# Interface XML name (used in multiple targets)
interface_xml := value-dbus-interface.xml
# ... Listing cut for brevity ...
# Special target to run DTD validation on the interface XML. Not run
# automatically (since xmllint isn't always available and also needs
# Internet connectivity).
checkxml: $(interface_xml)
@xmllint --valid --noout $<
@echo $< checks out ok
检查语义:
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > make checkxml
value-dbus-interface.xml checks out ok
为了验证DTD/checkxml能否起到作用,我们简单修改一下上面的XML文件,增加一个DTD中没有规定的节点</invalidElement> ,并且去掉一个method开始标签,看看效果:
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > make checkxml
value-dbus-interface.xml:36: element invalidElement: validity error :
No declaration for element invalidElement
</invalidElement>
^
value-dbus-interface.xml:53: parser error :
Opening and ending tag mismatch: method line 39 and interface
</interface>
^
value-dbus-interface.xml:54: parser error :
Opening and ending tag mismatch: interface line 22 and node
</node>
^
value-dbus-interface.xml:55: parser error :
Premature end of data in tag node line 21
^
make: *** [checkxml] Error 1
上面,第一个被发现的错误(验证错误),是由于XML文件不符合DTD的规定导致的。第二个(解析错误)则是没有正确的书写XML。
完成XML文件的两方面的检查后,我们开始生成代码。这个生成的代码我们叫它“glue”代码,什么意思呢?glue的本意是粘合,就是通过glue代码完成从Glib到D-Bus的映射关系:
图:
由XML文件,运行dbus-binding-tool命令,辅以不同的参数,可以生成client侧和sever侧的stub代码,生成的client stub和server stub代码正好构成了Client/Server结构。由这两个stub来完成Glib与D-Bus的互通,由于应用程序使用Glib,进而完成应用程序与D-Bus的互通。我们把makefile改写一下:
# Define a list of generated files so that they can be cleaned as well
cleanfiles := value-client-stub.h \
value-server-stub.h
# ... Listing cut for brevity ...
# If the interface XML changes, the respective stub interfaces will be
# automatically regenerated. Normally this would also mean that your
# builds would fail after this since you'd be missing implementation
# code.
# 这里的—prefix前缀会反映到你的stub代码中,会在生成的结构变量和函数名称中添加这个前缀,这个前缀大家不要省略,因为这里仅仅是生成头文件,实际的实现代码需要我们自己写,但是我们需要拷贝这里的声明到.c文件中,如果不指定这个前缀,并且有多个模块的话,会冲突的;
# mode指定为glib-server就生成server stub代码;指定为glib-client
# 则生成client stub代码
# 生成sever stub代码
value-server-stub.h: $(interface_xml)
dbus-binding-tool --prefix=value_object --mode=glib-server \
$< > $@
# 生成client stub代码
value-client-stub.h: $(interface_xml)
dbus-binding-tool --prefix=value_object --mode=glib-client \
$< > $@
# ... Listing cut for brevity ...
clean:
$(RM) $(targets) $(cleanfiles) *.o
这里生成的client侧的代码是想通过Glib去访问对象的实现,而server侧代码则是实现对象。
生成两个头文件:
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > make value-server-stub.h value-client-stub.h
dbus-binding-tool --prefix=value_object --mode=glib-server \
value-dbus-interface.xml > value-server-stub.h
dbus-binding-tool --prefix=value_object --mode=glib-client \
value-dbus-interface.xml > value-client-stub.h
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > ls -la value*stub.h
-rw-rw-r-- 1 user user 5184 Nov 21 14:02 value-client-stub.h
-rw-rw-r-- 1 user user 10603 Nov 21 14:02 value-server-stub.h
在开始实现实际的对象之前,我们先来瞄一眼生成的代码,看看到底什么妖蛾子。先看看server侧的stub文件:
/* Generated by dbus-binding-tool; do not edit! */
/*... Listing cut for brevity ...*/
#include <dbus/dbus-glib.h>
static const DBusGMethodInfo dbus_glib_value_object_methods[] = {
{ (GCallback) value_object_getvalue1,
dbus_glib_marshal_value_object_BOOLEAN__POINTER_POINTER, 0 },
{ (GCallback) value_object_getvalue2,
dbus_glib_marshal_value_object_BOOLEAN__POINTER_POINTER, 47 },
{ (GCallback) value_object_setvalue1,
dbus_glib_marshal_value_object_BOOLEAN__INT_POINTER, 94 },
{ (GCallback) value_object_setvalue2,
dbus_glib_marshal_value_object_BOOLEAN__DOUBLE_POINTER, 137 },
};
const DBusGObjectInfo dbus_glib_value_object_object_info = {
0,
dbus_glib_value_object_methods,
4,
"org.maemo.Value\0getvalue1\0S\0cur_value\0O\0F\0N\0