您的位置:首页 > 运维架构 > Linux

Maemo Linux手机平台系列分析:11 Maemo平台开发之 异步GConf

2008-02-18 22:47 627 查看
 
 
异步GConf
用GConf来监视变化

当监视对象发生变化时,用GConf给通知

[align=left] [/align]
[align=left]GConf来监视对象的改变[/align]
[align=left]在Maemo中,GConf是主要的用于存储一些设置参数的底层软件。下面,我们将会看到如何去扩展GConf,使它能够更适合于异步方式工作,特别是实现一些服务时,如果把握GConf的使用。[/align]
[align=left] [/align]
[align=left]这些情况下需要使用GConf: 1 你的服务程序有些比较简单的参数设置,并且需要能够实时地对参数的变化进行响应;2 人们也想偷懒,不想自己写一个配置文件解析程序(尽管Glib已经自带了一个这样的解析程序);我们的例子程序就是模仿第一种情况。[/align]
[align=left] [/align]
[align=left]例子程序主要关注两个字符串:一个是设置设备的通信(connection), 另外一个是设置通信参数(connectionparams)。这里我们主要关心gconf值变化的通知,这里会省略一些简单的东西,所以你需要预先给那两个值设置初始值。用gconftool-2来设置。写到makefile中:[/align]

[align=left]# Define a variable for this so that the GConf root may be changed[/align]
[align=left]gconf_root := /apps/Maemo/platdev_ex[/align]
[align=left] [/align]
[align=left]# ... Listing cut for brevity ...[/align]
[align=left] [/align]
[align=left]# This will setup the keys into default values.[/align]
[align=left]# It will first do a clear to remove any existing keys.[/align]
[align=left]primekeys: clearkeys[/align]
[align=left]      gconftool-2 --set --type string /[/align]
[align=left]                  $(gconf_root)/connection btcomm0[/align]
[align=left]      gconftool-2 --set --type string /[/align]
[align=left]                  $(gconf_root)/connectionparams 9600,8,N,1[/align]
[align=left] [/align]
[align=left]# Remove all application keys[/align]
[align=left]clearkeys:[/align]
[align=left]      @gconftool-2 --recursive-unset $(gconf_root)[/align]
[align=left] [/align]
[align=left]# Dump all application keys[/align]
[align=left]dumpkeys:[/align]
[align=left]      @echo Keys under $(gconf_root):[/align]
[align=left]      @gconftool-2 --recursive-list $(gconf_root)[/align]

[align=left]运行两个命令设置主键,并且查看是否设置OK:[/align]

[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > make primekeys[/align]
[align=left]gconftool-2 --set --type string /[/align]
[align=left]            /apps/Maemo/platdev_ex/connection btcomm0[/align]
[align=left]gconftool-2 --set --type string /[/align]
[align=left]            /apps/Maemo/platdev_ex/connectionparams 9600,8,N,1[/align]
[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > make dumpkeys[/align]
[align=left]Keys under /apps/Maemo/platdev_ex:[/align]
[align=left] connectionparams = 9600,8,N,1[/align]
[align=left] connection = btcomm0[/align]

[align=left] [/align]
[align=left]当监视对象发生变化时,用GConf给通知[/align]
[align=left]定义一个根健和两个主键,即所谓的名字空间(name space):[/align]

[align=left]#include <glib.h>[/align]
[align=left]#include <gconf/gconf-client.h>[/align]
[align=left]#include <string.h> /* strcmp */[/align]
[align=left] [/align]
[align=left]/* As per maemo Coding Style and Guidelines document, we use the[/align]
[align=left]   /apps/Maemo/ -prefix.[/align]
[align=left]   NOTE: There is no central registry (as of this moment) that you[/align]
[align=left]         could check that your application name doesn't collide with[/align]
[align=left]         other application names, so caution is advised! */[/align]
[align=left]#define SERVICE_GCONF_ROOT "/apps/Maemo/platdev_ex"[/align]
[align=left] [/align]
[align=left]/* We define the names of the keys symbolically so that we may change[/align]
[align=left]   them later if necessary, and so that the GConf "root directory" for[/align]
[align=left]   our application will be automatically prefixed to the paths. */[/align]
[align=left]#define SERVICE_KEY_CONNECTION /[/align]
[align=left]        SERVICE_GCONF_ROOT "/connection"[/align]
[align=left]#define SERVICE_KEY_CONNECTIONPARAMS /[/align]
[align=left]        SERVICE_GCONF_ROOT "/connectionparams"[/align]

[align=left]主函数创建一个GConf client对象(就是通过这个对象链接到Gconf 后台daemon程序的),然后显示前面设置的值:[/align]

[align=left]int main (int argc, char** argv) {[/align]
[align=left] /* Will hold reference to the GConfClient object. */[/align]
[align=left] GConfClient* client = NULL;[/align]
[align=left] /* Initialize this to NULL so that we'll know whether an error[/align]
[align=left]     occurred or not (and we don't have an existing GError object[/align]
[align=left]     anyway at this point). */[/align]
[align=left] GError* error = NULL;[/align]
[align=left] /* This will hold a reference to the mainloop object. */[/align]
[align=left] GMainLoop* mainloop = NULL;[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ":main Starting./n");[/align]
[align=left] [/align]
[align=left] /* Must be called to initialize GType system. The API reference for[/align]
[align=left]     gconf_client_get_default() insists.[/align]
[align=left]     NOTE: Using gconf_init() is deprecated! */ [/align]
[align=left]//已经废弃gconf_init()[/align]
[align=left] g_type_init();[/align]
[align=left] [/align]
[align=left] /* Create a new mainloop structure that we'll use. Use default[/align]
[align=left]     context (NULL) and set the 'running' flag to FALSE. */[/align]
[align=left] mainloop = g_main_loop_new(NULL, FALSE);[/align]
[align=left] if (mainloop == NULL) {[/align]
[align=left]    g_error(PROGNAME ": Failed to create mainloop!/n");[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] /* Create a new GConfClient object using the default settings. */[/align]
[align=left] client = gconf_client_get_default();[/align]
[align=left] if (client == NULL) {[/align]
[align=left]    g_error(PROGNAME ": Failed to create GConfClient!/n");[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ":main GType and GConfClient initialized./n");[/align]
[align=left] [/align]
[align=left] /* Display the starting values for the two keys. */[/align]
[align=left] dispStringKey(client, SERVICE_KEY_CONNECTION);[/align]
[align=left] dispStringKey(client, SERVICE_KEY_CONNECTIONPARAMS);[/align]

[align=left]函数dispStringKey非常简单,主要是提取和显示一个字符串:[/align]

[align=left]/**[/align]
[align=left] * Utility to retrieve a string key and display it.[/align]
[align=left] * (Just as a small refresher on the API.)[/align]
[align=left] */[/align]
[align=left]static void dispStringKey(GConfClient* client,[/align]
[align=left]                          const gchar* keyname) {[/align]
[align=left] [/align]
[align=left] /* This will hold the string value of the key. It will be[/align]
[align=left]     dynamically allocated for us, so we need to release it ourselves[/align]
[align=left]     when done (before returning). */[/align]
[align=left] gchar* valueStr = NULL;[/align]
[align=left] [/align]
[align=left] /* We're not interested in the errors themselves (the last[/align]
[align=left]     parameter), but the function will return NULL if there is one,[/align]
[align=left]     so we just end in that case. */[/align]
[align=left] valueStr = gconf_client_get_string(client, keyname, NULL);[/align]
[align=left] [/align]
[align=left] if (valueStr == NULL) {[/align]
[align=left]    g_error(PROGNAME ": No string value for %s. Quitting/n", keyname);[/align]
[align=left]    /* Application terminates. */[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ": Value for key '%s' is set to '%s'/n",[/align]
[align=left]          keyname, valueStr);[/align]
[align=left] [/align]
[align=left] /* Normally one would want to use the value for something beyond[/align]
[align=left]     just displaying it, but since this code doesn't, we release the[/align]
[align=left]    allocated value string. */[/align]
[align=left] g_free(valueStr);[/align]
[align=left]}[/align]

[align=left]下面我们吧GConf client绑定到特定的路径(我们将操作它,并监视它的改变):[/align]

[align=left] /**[/align]
[align=left]   * 把要监视的路径注册给GConf client, 当这个路径中的内容发生改变时,会发射信号“value-changed”出来,[/align]
[align=left]   * Register directory to watch for changes. This will then tell[/align]
[align=left]   * GConf to watch for changes in this namespace, and cause the[/align]
[align=left]   * "value-changed"-signal to be emitted. We won't be using that[/align]
[align=left]   * mechanism, but will opt to a more modern (and potentially more[/align]
[align=left]   * scalable solution). The directory needs to be added to the[/align]
[align=left]   * watch list in either case.[/align]
[align=left]   *[/align]
[align=left]   * GConfClient可以预加载一些键值,[/align]
[align=left]  * When adding directories, you can sometimes optimize your program[/align]
[align=left]   * performance by asking GConfClient to preload some (or all) keys[/align]
[align=left]   * under a specific directory. This is done via the preload_type[/align]
[align=left]   * parameter (we use GCONF_CLIENT_PRELOAD_NONE below). Since our[/align]
[align=left]   * program will only listen for changes, we don't want to use extra[/align]
[align=left]   * memory to keep the keys cached.[/align]
[align=left]   *[/align]
[align=left]   * Parameters:[/align]
[align=left]   * - client: GConf-client object[/align]
[align=left]   * - SERVICEPATH: the name of the GConf namespace to follow[/align]
[align=left]   * - GCONF_CLIENT_PRELOAD_NONE: do not preload any of contents[/align]
[align=left]   * - error: where to store the pointer to allocated GError on[/align]
[align=left]   *          errors.[/align]
[align=left]   */[/align]
[align=left] gconf_client_add_dir(client,[/align]
[align=left]                       SERVICE_GCONF_ROOT,//要监听的路径[/align]
[align=left]                       GCONF_CLIENT_PRELOAD_NONE,[/align]
[align=left]                       &error);[/align]
[align=left] [/align]
[align=left] if (error != NULL) {[/align]
[align=left]    g_error(PROGNAME ": Failed to add a watch to GCClient: %s/n",[/align]
[align=left]            error->message);[/align]
[align=left]    /* Normally we'd also release the allocated GError, but since[/align]
[align=left]       this program will terminate on g_error, we won't do that.[/align]
[align=left]       Hence the next line is commented. */[/align]
[align=left]    /* g_error_free(error); */[/align]
[align=left] [/align]
[align=left]    /* When you want to release the error if it has been allocated,[/align]
[align=left]       or just continue if not, use g_clear_error(&error); */[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]  g_print(PROGNAME ":main Added " SERVICE_GCONF_ROOT "./n");[/align]

[align=left]接下来,我们需要注册回调函数,这个回调函数在监视对象发生改变时,被调用:[/align]

[align=left] /* Register our interest (in the form of a callback function) for[/align]
[align=left]     any changes under the namespace that we just added.[/align]
[align=left] [/align]
[align=left]     Parameters:[/align]
[align=left]     - client: GConfClient object.[/align]
[align=left]     - SERVICEPATH: namespace under which we can get notified for[/align]
[align=left]                    changes.[/align]
[align=left]     - gconf_notify_func: callback that will be called on changes.[/align]
[align=left]     - NULL: user-data pointer (not used here).[/align]
[align=left]     - NULL: function to call on user-data when notify is removed or[/align]
[align=left]             GConfClient destroyed. NULL for none (since we don't[/align]
[align=left]             have user-data anyway).[/align]
[align=left]     - error: return location for an allocated GError.[/align]
[align=left] [/align]
[align=left]     Returns:[/align]
[align=left]     guint: an ID for this notification so that we could remove it[/align]
[align=left]            later with gconf_client_notify_remove(). We're not going[/align]
[align=left]            to use it so we don't store it anywhere. */[/align]
[align=left] gconf_client_notify_add(client, SERVICE_GCONF_ROOT,[/align]
[align=left]                          keyChangeCallback, NULL, NULL, &error);[/align]
[align=left] if (error != NULL) {[/align]
[align=left]    g_error(PROGNAME ": Failed to add register the callback: %s/n",[/align]
[align=left]            error->message);[/align]
[align=left]    /* Program terminates. */[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ":main CB registered & starting main loop/n");[/align]

[align=left]当我们处理桌面软件时,你可能使用多个回调函数,每个回调去跟踪一个键值。其实这样做不好,我们可以使用一个回调处理多个键值的跟踪:[/align]

[align=left]/**[/align]
[align=left] * 当监视路径发生改变时,会调用回调函数,并且这个回调函数要和GConfClientNotifyFunc原型一致;[/align]
[align=left] * Callback called when a key in watched directory changes.[/align]
[align=left] * Prototype for the callback must be compatible with[/align]
[align=left] * GConfClientNotifyFunc (for ref).[/align]
[align=left] *[/align]
[align=left] * It will find out which key changed (using strcmp, since the same[/align]
[align=left] * callback is used to track both keys) and the display the new value[/align]
[align=left] * of the key.[/align]
[align=left] *[/align]
[align=left] * 通常情况下,userData会把改变的值携带给回调函数,这样我们才能根据改变的值修改界面,不过这里仅仅是个示例,就不这样搞了。[/align]
[align=left] * The changed key/value pair will be communicated in the entry[/align]
[align=left] * parameter. userData will be NULL (can be set on notify_add [in[/align]
[align=left] * main]). Normally the application state would be carried within the[/align]
[align=left] * userData parameter, so that this callback could then modify the[/align]
[align=left] * view based on the change. Since this program does not have a state,[/align]
[align=left] * there is little that we can do within the function (it will abort[/align]
[align=left] * the program on errors though).[/align]
[align=left] */[/align]
[align=left]static void keyChangeCallback(GConfClient* client,[/align]
[align=left]                              guint        cnxn_id,[/align]
[align=left]                              GConfEntry* entry,[/align]
[align=left]                              gpointer     userData) {[/align]
[align=left] [/align]
[align=left] /* This will hold the pointer to the value. */[/align]
[align=left] const GConfValue* value = NULL;[/align]
[align=left] /* This will hold a pointer to the name of the key that changed. */[/align]
[align=left] const gchar* keyname = NULL;[/align]
[align=left] /* This will hold a dynamically allocated human-readable[/align]
[align=left]     representation of the changed value. */[/align]
[align=left] gchar* strValue = NULL;[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ": keyChangeCallback invoked./n");[/align]
[align=left] [/align]
[align=left] /* Get a pointer to the key (this is not a copy). */[/align]
[align=left] keyname = gconf_entry_get_key(entry);[/align]
[align=left] [/align]
[align=left] /* It will be quite fatal if after change we cannot retrieve even[/align]
[align=left]     the name for the gconf entry, so we error out here. */[/align]
[align=left] if (keyname == NULL) {[/align]
[align=left]    g_error(PROGNAME ": Couldn't get the key name!/n");[/align]
[align=left]    /* Application terminates. */[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] /* Get a pointer to the value from changed entry. */[/align]
[align=left] value = gconf_entry_get_value(entry);[/align]
[align=left] [/align]
[align=left] /* If we get a NULL as the value, it means that the value either has[/align]
[align=left]     not been set, or is at default. As a precaution we assume that[/align]
[align=left]     this cannot ever happen, and will abort if it does.[/align]
[align=left]     NOTE: A real program should be more resilient in this case, but[/align]
[align=left]           the problem is: what is the correct action in this case?[/align]
[align=left]           This is not always simple to decide.[/align]
[align=left]     NOTE: You can trip this assert with 'make primekeys', since that[/align]
[align=left]           will first remove all the keys (which causes the CB to[/align]
[align=left]           be invoked, and abort here). */[/align]
[align=left] g_assert(value != NULL);[/align]
[align=left] [/align]
[align=left] /* Check that it looks like a valid type for the value. */[/align]
[align=left] if (!GCONF_VALUE_TYPE_VALID(value->type)) {[/align]
[align=left]    g_error(PROGNAME ": Invalid type for gconfvalue!/n");[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] /* Create a human readable representation of the value. Since this[/align]
[align=left]     will be a new string created just for us, we'll need to be[/align]
[align=left]     careful and free it later. */[/align]
[align=left] strValue = gconf_value_to_string(value);[/align]
[align=left] [/align]
[align=left] /* Print out a message (depending on which of the tracked keys[/align]
[align=left]     change. */[/align]
[align=left] if (strcmp(keyname, SERVICE_KEY_CONNECTION) == 0) {[/align]
[align=left]    g_print(PROGNAME ": Connection type setting changed: [%s]/n",[/align]
[align=left]            strValue);[/align]
[align=left] } else if (strcmp(keyname, SERVICE_KEY_CONNECTIONPARAMS) == 0) {[/align]
[align=left]    g_print(PROGNAME ": Connection params setting changed: [%s]/n",[/align]
[align=left]            strValue);[/align]
[align=left] } else {[/align]
[align=left]    g_print(PROGNAME ":Unknown key: %s (value: [%s])/n", keyname,[/align]
[align=left]            strValue);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] /* Free the string representation of the value. */[/align]
[align=left] g_free(strValue);[/align]
[align=left] [/align]
[align=left] g_print(PROGNAME ": keyChangeCallback done./n");[/align]
[align=left]}[/align]

[align=left]上面的代码有点复杂,原因是GConf使用GValue作为变量来传递数据:GValue可以携带任何简单的数据类型。由于我们不能完全相信GConf会返回正确的值,我们需要格外小心:对返回值的类型做仔细的检查,防止NULL引起的程序异常和其它类型不匹配错误。[/align]
[align=left] [/align]
[align=left]好了,下面我们编译并测试该程序:在后台启动这个程序,然后使用gconftool-2修改键值,看看监视函数能不能对键值的改变做出及时的响应:[/align]

[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > make[/align]
[align=left]cc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gconf/2 /[/align]
[align=left]   -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -Wall -g /[/align]
[align=left]   -DPROGNAME=/"gconf-key-watch/" gconf-key-watch.c -o gconf-key-watch /[/align]
[align=left]   -lgconf-2 -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0[/align]
[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > run-standalone.sh ./gconf-key-watch & //后台运行[/align]
[align=left][2] 21385[/align]
[align=left]gconf-key-watch:main Starting.[/align]
[align=left]gconf-key-watch:main GType and GConfClient initialized.[/align]
[align=left]gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connection'[/align]
[align=left] is set to 'btcomm0'[/align]
[align=left]gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connectionparams'[/align]
[align=left] is set to '9600,8,N,1'[/align]
[align=left]gconf-key-watch:main Added /apps/Maemo/platdev_ex.[/align]
[align=left]gconf-key-watch:main CB registered & starting main loop[/align]
[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值[/align]
[align=left] /apps/Maemo/platdev_ex/connection ttyS0[/align]
[align=left]gconf-key-watch: keyChangeCallback invoked.[/align]
[align=left]gconf-key-watch: Connection type setting changed: [ttyS0] //监视函数及时检测到键值的修改[/align]
[align=left]gconf-key-watch: keyChangeCallback done.[/align]
[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值[/align]
[align=left] /apps/Maemo/platdev_ex/connectionparams ''[/align]
[align=left]gconf-key-watch: keyChangeCallback invoked.[/align]
[align=left]gconf-key-watch: Connection params setting changed: [] //监视函数及时检测到键值的修改[/align]
[align=left]gconf-key-watch: keyChangeCallback done.[/align]

[align=left]後一个修改是有问题的:修改成空值了。由于GConf本身并不提供语法和语义的检查,因此,对于修改键值的检查也是你需要仔细考虑的。[/align]
[align=left] [/align]
[align=left]当发现键值被修改为非法键值时,你可以把该键值复位为初始值。不过,这种复位操作可能会影响到其它关联的程序,比较冒险,一般不这样做。另外:由于特定的环境没有办法告诉用户你的程序已经退出,所以当遇到非法键值时,程序退出也不是一个好的办法。[/align]
[align=left] [/align]
[align=left]另外一个问题:一个动作可能影响到多个键值。你需要处理这个问题。上面的例子也可以验证这个问题:当connection-key键值改变时,server应该重新初始化这个connection吗?或者如果键值”同时”改变了,则connection会被重新初始化两次的。这是很严重的问题。Gconf并不支持原子操作。这是实际工作中必须面对的问题。[/align]
[align=left] [/align]
[align=left]下面我们再测试一些异常的问题:[/align]

[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type int /[/align]
[align=left] /apps/Maemo/platdev_ex/connectionparams 5 //设置的类型不对,不是字符串[/align]
[align=left]gconf-key-watch: keyChangeCallback invoked.[/align]
[align=left]gconf-key-watch: Connection params setting changed: [5][/align]
[align=left]gconf-key-watch: keyChangeCallback done.[/align]
[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type boolean /[/align]
[align=left] /apps/Maemo/platdev_ex/connectionparams true[/align]
[align=left]gconf-key-watch: keyChangeCallback invoked.[/align]
[align=left]gconf-key-watch: Connection params setting changed: [true][/align]
[align=left]gconf-key-watch: keyChangeCallback done.[/align]

[align=left]对于前面设置为空的键值,清除时发生了崩溃![/align]

[align=left][sbox-CHINOOK_X86: ~/gconf-listener] > make clearkeys[/align]
[align=left]gconf-key-watch: keyChangeCallback invoked.[/align]
[align=left]gconf-key-watch[21403]: GLIB ERROR ** default - [/align]
[align=left] file gconf-key-watch.c: line 129 (keyChangeCallback):[/align]
[align=left] assertion failed: (value != NULL)[/align]
[align=left]aborting...[/align]
[align=left]/usr/bin/run-standalone.sh: line 11: 21403 Aborted (core dumped) "$@"[/align]
[align=left][1]+ Exit 134 run-standalone.sh ./gconf-key-watch[/align]

[align=left]在回调函数里面,我们检查了非NULL值,对于NULL的值,我们退出程序,所以上面的异常在我们的意料之内。[/align]
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息