The Official GNOME 2 Developer's Guide GLib (七)
2011年10月09日 09:28
1.4.8 异常处理错误报告
在上一节里面我们我们诊断贺消除程序运行中的严重错误。然而,那些非致命的错误上一节的做法就太激进了。你的程序自己对待一些特殊方式有克服、忽视或者处理等办法。例如,如果一个图形应用程序不能打开一个弹出的文件浏览器选择了一个文件,你通常不希望中止整个应用程序。相反,你可能想找出是什么问题,然后进行处理,可以弹出出了一个对话框或者让你点击文件浏览器中的“取消”按钮。
人们往往希望在不同错误类型的时候用异常处理来进行补救。(GLib的在线手册使用的术语的错误报告)传统的C风格的函数,在测试调用结束以后返回特殊的错误代码,一些功能提供了额外的细节(例如,通过errno这个全局变量)。而C++和Java这类高级语言提供了一种特殊的语法来进行异常处理,像try{}, throw(), catch(){}.
GLib中的异常处理没有像那样复杂的功能,因为GLib用的是C。但是它确实提供了一个称为GError系统,比自己重新动手写一个C的方法要容易使用的多。GError的数据结构是核心:你如何使用这种数据结构和它的实施是同样重要的。
GError和GError函数
使用GError的函数都把GError指针的地址作为最后一个参数。如果你想用一个GError的变量err要定义为GError *err,传递的时候用&err。此外你要设置指针的值为0。你还可以指定这个参数为NULL,这时该函数将禁用错误报告。
一个GError结构体有以下字段:
domain(GQuark类型):一个标签代表模块或者子系统错误,域名或者类的错误。每一个错误域必须有一个宏定义的格式 PREFIX_MODULE_ERROR(例如,G_FILE_ERROR)。宏扩展到夸克的数值的形式返回。
code(gint类型):错误代码;这就是错误域内的特定错误。每一个可能的错误代码需要一个相应的符号称为PrefixModuleError枚举类型的形式PREFIX_MODULE_ERROR_CODE(例如,在GFileError G_FILE_ERROR_TYPE)。
message(gchar*类型):用通俗易懂的语言完整描述一个错误。
下面的代码片段演示了如何从一个函数的错误条件(do_something())使用GError:
GError *error = NULL; /* use this GError variable as last argument */ do_something(arg1, arg2, &error); /* was there an error? */ if (error != NULL) { /* report the message */ g_printerr("Error when doing something: %s\n", error->message); /* free error structure */ g_error_free(error); }
你可以看到这个代码需要你完成后用g_error_free()释放GError。因此,如果你提供一个GError参数的函数,你应该经常检查,否则,你的内存有泄漏的危险。
如果你想要做报告错误以外的东西,你可能想知道错误的domain和code。而不是用手工去检查,你应该使用g_error_matches()函数匹配错误中的domain和code。第一个参数是GError结构,第二个是错误的domain,第三个是一个特定的错误code。如果错误匹配domain和code,该函数返回TRUE,否则返回FALSE。下面是一个例子:
GError *error = NULL; gchar *filename; BluesGuitar *fender, *bender; << .. >> filename = blues_improvise(fender, BLUES_A_MAJOR, &error); if (error != NULL) { /* see if the expensive guitar is broken */ if (g_error_matches(error, BLUES_GUITAR_ERROR, BLUES_GUITAR_ERROR_BROKEN)) { /* if so, try the cheap guitar */ g_clear_error(&error); filename = blues_improvise(bender, BLUES_A_MAJOR, &error); } } /* if nothing's working, default to Clapton */ if (error != NULL) { filename = g_strdup("clapton-1966.wav"); g_error_free(error); } blues_play(filename);
在这个例子中,函数blues_improvise()运行没有问题的情况下后返回一个文件名。如果出现错误,程序就检查错误code BLUES_GUITAR_ERROR_BROKEN在domain BLUES_GUITAR_ERROR里么。如果是这个问题,程序就尝试不同时间的不同参数。试图在此之前,它会调用g_clear_error()来清除错误,这个函数释放GError结构体并且把指针重置为NULL。
如果有错误的东西,这个第二次尝试后,显示的东西仍然无法正常工作的权利,该方案放弃了。而不是试图再blues_improvise()调用,它使用默认的文件名(“clapton-1966.wav”),所以blues_play()可以做它的事。
警告:您使用GError*结构后,应该立即释放并复位指针。 GError启用的功能可以不使用相同的指针,但是在同一时间的几个错误,只有一个错误的空间。如前所述,如果你不释放GError内存,你的程序将有内存泄漏。
定义自己的错误条件
要在自己的函数里面使用GError系统来报告错误,要做到以下几点:
1.创造一个适当命名的宏,扩展到一个唯一的GQuark值定义错误domain。
2.定义一个枚举类型的所有错误code。
3.在你的函数里面增加一个GError**类型的参数(也就是说,这个参数是一个指针到GError结构的指针)。如果该函数使用可变参数,这个参数要在va_args(...)之前.
4.在你的函数检测到一个错误的地方,创建一个新的GError结构,并填写在相应的情况。
这里是一个错误的域名和一些代码的定义:
/* define the error domain */ #define MAWA_DOSOMETHING_ERROR (mawa_dosomething_error_quark()) GQuark mawa_dosomething_error_quark(void) { static GQuark q = 0; if (q == 0) { q = g_quark_from_static_string("mawa-dosomething-error"); } return(q); } /* and the error codes */ typedef enum { MAWA_DOSOMETHING_ERROR_PANIC, MAWA_DOSOMETHING_ERROR_NO_INPUT, MAWA_DOSOMETHING_ERROR_INPUT_TOO_BORING, MAWA_DOSOMETHING_ERROR_FAILED /* abort code */ }
前面这个例子要注意的是mawa_dosomething_error_quark()。它创建新的错误域夸克如果不存在,但一个静态变量q的结果就没有改变,所以,它并没有执行任何额外的计算连续调用。
这个片段说明了如何使用新的域名和代码:
void mawa_dosomething_simple(GError **error) { gint i; gboolean it_worked; << do something that sets it_worked to TRUE or FALSE >> if (!it_worked) { g_set_error(error, MAWA_DOSOMETHING_ERROR, MAWA_DOSOMETHING_ERROR_PANIC, "Panic in do_something_simple(), i = %d", i); } }
这个函数“做了一些事情”如果失败。调用g_set_error()来设置返回的错误结果。此函数作为第一个参数错误指针的地址,如果不为NULL,则设置了一个新分配的GError结构的指针。 g_set_error()函数填充这个结构的第三个和第四个参数(错误域和码),其余的参数的printf()格式字符串和参数列表,成为GError的消息字段。
如果你想从另一个函数使用错误代码,您需要特别小心:
void mawa_dosomething_nested(GError **error) { gint i; gboolean it_worked; GError *simple_error = NULL; << do something >> if (!it_worked) { g_set_error(error, MAWA_DOSOMETHING_ERROR, MAWA_DOSOMETHING_ERROR_PANIC, "Panic in do_something_nested(), i = %d", i); return; } do_something_simple(&simple_error); if (simple_error != NULL) { << additional error handling >> g_propagate_error(error, simple_error); } }
mawa_dosomething_nested()中,如果该函数的第一部分失效,那么类似的错误初始化会出现。然而,如果第一部分正常工作的话,这个函数会继续下去并调用do_something_simple()。因为该函数能够设置一个错误条件,使发送给最初调用者的错误条件有意义。为了做到这一点,这个函数首先收集do_something_simple()条件到simple_error,然后调用g_propagate_error()从simple_error到error传递GError结构体。
警告:不能将作为参数得到的GError **作为指针传递给任何其他函数。如果GError **为NULL,当你试图引用它时,你的程序将会崩溃。
如果要发送从别的函数得到的错误到一些其他地方,使用g_propagate_error(error_dest, error_src)在这里,error_dest是GError **的错误的目的地(destination),error_src是GError **的源(source)。如果目的地(destination)不为NULL,该函数会简单地将源(source)复制给目的地(destination)。然而,如果目的地(destination)实际为NULL,该函数会释放源错误(source error)。
你现在可能已经注意到,GError力图通过对NULL的重视实现透明度,所以当你不关心错误的特定性质时,你不必担心内存泄露和额外的GError指针。另外,如果你的函数中的一个遇见作为错误条件的NULL,你可以将“用户不希望任何特定的错误处理,也许是想该函数可以尽可能的修补问题”作为一个提示。
你需要时刻记住,仅当你坚持规则时,GError是一种相当可行的处理异常的工具