异步
GConf
- 用GConf来监视变化
- 当监视对象发生变化时,用GConf给通知
用
GConf
来监视对象的改变
在
Maemo
中,
GConf
是主要的用于存储一些设置参数的底层软件。下面,我们将会看到如何去扩展
GConf
,使它能够更适合于异步方式工作,特别是实现一些服务时,如果把握
GConf
的使用。
这些情况下需要使用
GConf: 1
你的服务程序有些比较简单的参数设置,并且需要能够实时地对参数的变化进行响应;
2
人们也想偷懒,不想自己写一个配置文件解析程序(尽管
Glib
已经自带了一个这样的解析程序);我们的例子程序就是模仿第一种情况。
例子程序主要关注两个字符串:一个是设置设备的通信
(connection),
另外一个是设置通信参数
(connectionparams)
。这里我们主要关心
gconf
值变化的通知,这里会省略一些简单的东西,所以你需要预先给那两个值设置初始值。用
gconftool-2
来设置。
写到
makefile
中:
# Define a variable for this so that the GConf root may be changed
gconf_root :=
/apps/Maemo/platdev_ex
# ... Listing cut for brevity ...
# This will setup the keys into default values.
# It will first do a clear to remove any existing keys.
primekeys:
clearkeys
gconftool-2 --set --type string /
$(gconf_root)/connection btcomm0
gconftool-2 --set --type string /
$(gconf_root)/connectionparams 9600,8,N,1
# Remove all application keys
clearkeys:
@gconftool-2 --recursive-unset $(gconf_root)
# Dump all application keys
dumpkeys:
@echo Keys under $(gconf_root):
@gconftool-2 --recursive-list $(gconf_root)
运行两个命令设置主键,并且查看是否设置
OK:
[sbox-CHINOOK_X86: ~/gconf-listener] > make primekeys
gconftool-2 --set --type string /
/apps/Maemo/platdev_ex/connection btcomm0
gconftool-2 --set --type string /
/apps/Maemo/platdev_ex/connectionparams 9600,8,N,1
[sbox-CHINOOK_X86: ~/gconf-listener] > make dumpkeys
Keys under /apps/Maemo/platdev_ex:
connectionparams = 9600,8,N,1
connection = btcomm0
当监视对象发生变化时,用
GConf
给通知
定义一个根健和两个主键,即所谓的名字空间
(name space):
#include
<glib.h>
#include
<gconf/gconf-client.h>
#include
<string.h> /* strcmp */
/* As per maemo Coding Style and Guidelines document, we use the
/apps/Maemo/ -prefix.
NOTE: There is no central registry (as of this moment) that you
could check that your application name doesn't collide with
other application names, so caution is advised! */
#define
SERVICE_GCONF_ROOT "/apps/Maemo/platdev_ex"
/* We define the names of the keys symbolically so that we may change
them later if necessary, and so that the GConf "root directory" for
our application will be automatically prefixed to the paths. */
#define
SERVICE_KEY_CONNECTION /
SERVICE_GCONF_ROOT "/connection"
#define
SERVICE_KEY_CONNECTIONPARAMS /
SERVICE_GCONF_ROOT "/connectionparams"
主函数创建一个
GConf client
对象
(
就是通过这个对象链接到
Gconf
后台
daemon
程序的
)
,然后显示前面设置的值:
int
main (int argc, char** argv) {
/* Will hold reference to the GConfClient object. */
GConfClient* client = NULL;
/* Initialize this to NULL so that we'll know whether an error
occurred or not (and we don't have an existing GError object
anyway at this point). */
GError* error = NULL;
/* This will hold a reference to the mainloop object. */
GMainLoop* mainloop = NULL;
g_print(PROGNAME ":main Starting./n");
/* Must be called to initialize GType system. The API reference for
gconf_client_get_default() insists.
NOTE: Using gconf_init() is deprecated! */
//已经废弃gconf_init()
g_type_init();
/* Create a new mainloop structure that we'll use. Use default
context (NULL) and set the 'running' flag to FALSE. */
mainloop = g_main_loop_new(NULL, FALSE);
if (mainloop == NULL) {
g_error(PROGNAME ": Failed to create mainloop!/n");
}
/* Create a new GConfClient object using the default settings. */
client = gconf_client_get_default();
if (client == NULL) {
g_error(PROGNAME ": Failed to create GConfClient!/n");
}
g_print(PROGNAME ":main GType and GConfClient initialized./n");
/* Display the starting values for the two keys. */
dispStringKey(client, SERVICE_KEY_CONNECTION);
dispStringKey(client, SERVICE_KEY_CONNECTIONPARAMS);
函数
dispStringKey
非常简单,主要是提取和显示一个字符串:
/**
* Utility to retrieve a string key and display it.
* (Just as a small refresher on the API.)
*/
static
void dispStringKey(GConfClient* client,
const gchar* keyname) {
/* This will hold the string value of the key. It will be
dynamically allocated for us, so we need to release it ourselves
when done (before returning). */
gchar* valueStr = NULL;
/* We're not interested in the errors themselves (the last
parameter), but the function will return NULL if there is one,
so we just end in that case. */
valueStr = gconf_client_get_string(client, keyname, NULL);
if (valueStr == NULL) {
g_error(PROGNAME ": No string value for %s. Quitting/n", keyname);
/* Application terminates. */
}
g_print(PROGNAME ": Value for key '%s' is set to '%s'/n",
keyname, valueStr);
/* Normally one would want to use the value for something beyond
just displaying it, but since this code doesn't, we release the
allocated value string. */
g_free(valueStr);
}
下面我们吧
GConf client
绑定到特定的路径(我们将操作它,并监视它的改变):
/**
* 把要监视的路径注册给GConf client, 当这个路径中的内容发生改变时,会发射信号“value-changed”出来,
* Register directory to watch for changes. This will then tell
* GConf to watch for changes in this namespace, and cause the
* "value-changed"-signal to be emitted. We won't be using that
* mechanism, but will opt to a more modern (and potentially more
* scalable solution). The directory needs to be added to the
* watch list in either case.
*
*
GConfClient可以预加载一些键值,
* When adding directories, you can sometimes optimize your program
* performance by asking GConfClient to preload some (or all) keys
* under a specific directory. This is done via the preload_type
* parameter (we use GCONF_CLIENT_PRELOAD_NONE below). Since our
* program will only listen for changes, we don't want to use extra
* memory to keep the keys cached.
*
* Parameters:
* - client: GConf-client object
* - SERVICEPATH: the name of the GConf namespace to follow
* - GCONF_CLIENT_PRELOAD_NONE: do not preload any of contents
* - error: where to store the pointer to allocated GError on
* errors.
*/
gconf_client_add_dir(client,
SERVICE_GCONF_ROOT,//要监听的路径
GCONF_CLIENT_PRELOAD_NONE,
&error);
if (error != NULL) {
g_error(PROGNAME ": Failed to add a watch to GCClient: %s/n",
error->message);
/* Normally we'd also release the allocated GError, but since
this program will terminate on g_error, we won't do that.
Hence the next line is commented. */
/* g_error_free(error); */
/* When you want to release the error if it has been allocated,
or just continue if not, use g_clear_error(&error); */
}
g_print(PROGNAME ":main Added " SERVICE_GCONF_ROOT "./n");
接下来,我们需要注册回调函数,这个回调函数在监视对象发生改变时,被调用:
/* Register our interest (in the form of a callback function) for
any changes under the namespace that we just added.
Parameters:
- client: GConfClient object.
- SERVICEPATH: namespace under which we can get notified for
changes.
- gconf_notify_func: callback that will be called on changes.
- NULL: user-data pointer (not used here).
- NULL: function to call on user-data when notify is removed or
GConfClient destroyed. NULL for none (since we don't
have user-data anyway).
- error: return location for an allocated GError.
Returns:
guint: an ID for this notification so that we could remove it
later with gconf_client_notify_remove(). We're not going
to use it so we don't store it anywhere. */
gconf_client_notify_add(client, SERVICE_GCONF_ROOT,
keyChangeCallback, NULL, NULL, &error);
if (error != NULL) {
g_error(PROGNAME ": Failed to add register the callback: %s/n",
error->message);
/* Program terminates. */
}
g_print(PROGNAME ":main CB registered & starting main loop/n");
当我们处理桌面软件时,你可能使用多个回调函数,每个回调去跟踪一个键值。其实这样做不好,我们可以使用一个回调处理多个键值的跟踪:
/**
* 当监视路径发生改变时,会调用回调函数,并且这个回调函数要和GConfClientNotifyFunc原型一致;
* Callback called when a key in watched directory changes.
* Prototype for the callback must be compatible with
* GConfClientNotifyFunc (for ref).
*
* It will find out which key changed (using strcmp, since the same
* callback is used to track both keys) and the display the new value
* of the key.
*
* 通常情况下,userData会把改变的值携带给回调函数,这样我们才能根据改变的值修改界面,不过这里仅仅是个示例,就不这样搞了。
* The changed key/value pair will be communicated in the entry
* parameter. userData will be NULL (can be set on notify_add [in
* main]). Normally the application state would be carried within the
* userData parameter, so that this callback could then modify the
* view based on the change. Since this program does not have a state,
* there is little that we can do within the function (it will abort
* the program on errors though).
*/
static
void keyChangeCallback(GConfClient* client,
guint cnxn_id,
GConfEntry* entry,
gpointer userData) {
/* This will hold the pointer to the value. */
const GConfValue* value = NULL;
/* This will hold a pointer to the name of the key that changed. */
const gchar* keyname = NULL;
/* This will hold a dynamically allocated human-readable
representation of the changed value. */
gchar* strValue = NULL;
g_print(PROGNAME ": keyChangeCallback invoked./n");
/* Get a pointer to the key (this is not a copy). */
keyname = gconf_entry_get_key(entry);
/* It will be quite fatal if after change we cannot retrieve even
the name for the gconf entry, so we error out here. */
if (keyname == NULL) {
g_error(PROGNAME ": Couldn't get the key name!/n");
/* Application terminates. */
}
/* Get a pointer to the value from changed entry. */
value = gconf_entry_get_value(entry);
/* If we get a NULL as the value, it means that the value either has
not been set, or is at default. As a precaution we assume that
this cannot ever happen, and will abort if it does.
NOTE: A real program should be more resilient in this case, but
the problem is: what is the correct action in this case?
This is not always simple to decide.
NOTE: You can trip this assert with 'make primekeys', since that
will first remove all the keys (which causes the CB to
be invoked, and abort here). */
g_assert(value != NULL);
/* Check that it looks like a valid type for the value. */
if (!GCONF_VALUE_TYPE_VALID(value->type)) {
g_error(PROGNAME ": Invalid type for gconfvalue!/n");
}
/* Create a human readable representation of the value. Since this
will be a new string created just for us, we'll need to be
careful and free it later. */
strValue = gconf_value_to_string(value);
/* Print out a message (depending on which of the tracked keys
change. */
if (strcmp(keyname, SERVICE_KEY_CONNECTION) == 0) {
g_print(PROGNAME ": Connection type setting changed: [%s]/n",
strValue);
} else if (strcmp(keyname, SERVICE_KEY_CONNECTIONPARAMS) == 0) {
g_print(PROGNAME ": Connection params setting changed: [%s]/n",
strValue);
} else {
g_print(PROGNAME ":Unknown key: %s (value: [%s])/n", keyname,
strValue);
}
/* Free the string representation of the value. */
g_free(strValue);
g_print(PROGNAME ": keyChangeCallback done./n");
}
上面的代码有点复杂,原因是
GConf
使用
GValue
作为变量来传递数据:
GValue
可以携带任何简单的数据类型。由于我们不能完全相信
GConf
会返回正确的值,我们需要格外小心:对返回值的类型做仔细的检查,防止
NULL
引起的程序异常和其它类型不匹配错误。
好了,下面我们编译并测试该程序:在后台启动这个程序,然后使用
gconftool-2
修改键值,看看监视函数能不能对键值的改变做出及时的响应:
[sbox-CHINOOK_X86: ~/gconf-listener] > make
cc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gconf/2 /
-I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -Wall -g /
-DPROGNAME=/"gconf-key-watch/" gconf-key-watch.c -o gconf-key-watch /
-lgconf-2 -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
[sbox-CHINOOK_X86: ~/gconf-listener] > run-standalone.sh ./gconf-key-watch & //后台运行
[2] 21385
gconf-key-watch:main Starting.
gconf-key-watch:main GType and GConfClient initialized.
gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connection'
is set to 'btcomm0'
gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connectionparams'
is set to '9600,8,N,1'
gconf-key-watch:main Added /apps/Maemo/platdev_ex.
gconf-key-watch:main CB registered & starting main loop
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值
/apps/Maemo/platdev_ex/connection ttyS0
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection type setting changed: [ttyS0] //监视函数及时检测到键值的修改
gconf-key-watch: keyChangeCallback done.
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值
/apps/Maemo/platdev_ex/connectionparams ''
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [] //监视函数及时检测到键值的修改
gconf-key-watch: keyChangeCallback done.
後一个修改是有问题的:修改成空值了。由于
GConf
本身并不提供语法和语义的检查,因此,对于修改键值的检查也是你需要仔细考虑的。
当发现键值被修改为非法键值时,你可以把该键值复位为初始值。不过,这种复位操作可能会影响到其它关联的程序,比较冒险,一般不这样做。另外:由于特定的环境没有办法告诉用户你的程序已经退出,所以当遇到非法键值时,程序退出也不是一个好的办法。
另外一个问题:一个动作可能影响到多个键值。你需要处理这个问题。上面的例子也可以验证这个问题:当
connection-key
键值改变时,
server
应该重新初始化这个
connection
吗?或者如果键值
”
同时
”
改变了,则
connection
会被重新初始化两次的。这是很严重的问题。
Gconf
并不支持原子操作。这是实际工作中必须面对的问题。
下面我们再测试一些异常的问题:
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type int /
/apps/Maemo/platdev_ex/connectionparams 5 //设置的类型不对,不是字符串
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [5]
gconf-key-watch: keyChangeCallback done.
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type boolean /
/apps/Maemo/platdev_ex/connectionparams true
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [true]
gconf-key-watch: keyChangeCallback done.
对于前面设置为空的键值,清除时发生了崩溃!
[sbox-CHINOOK_X86: ~/gconf-listener] > make clearkeys
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch[21403]: GLIB ERROR ** default -
file gconf-key-watch.c: line 129 (keyChangeCallback):
assertion failed: (value != NULL)
aborting...
/usr/bin/run-standalone.sh: line 11: 21403 Aborted (core dumped) "$@"
[1]+ Exit 134 run-standalone.sh ./gconf-key-watch
在回调函数里面,我们检查了非
NULL
值,对于
NULL
的值,我们退出程序,所以上面的异常在我们的意料之内。