GNOME编程基础与GLib库入门
1. 编程基础要求
在开始相关编程之前,你需要具备一定的编程经验:
- 扎实的C语言编程经验,包括指针、动态分配的数据结构和函数指针的使用,同时要熟悉枚举类型和位域。
- 熟练掌握指针的指针(** 类型),了解其使用场景以及如何提取和使用指针的地址。
- 理解C宏和C预处理器。
- 对Unix有基本的了解,包括进程、库、搜索路径等。
- 具备实际的Unix使用经验,如使用文本编辑器、编写shell脚本和使用make工具。
- 作为用户对GNOME有基本的了解,知道GNOME 2.0应用程序的外观和操作方式,以及如何与图形用户元素(如控制面板和对话框)进行交互。
- 对GUI编程有一定的了解会有帮助,了解回调函数(事件处理程序)的概念和工作原理会很有好处,但这不是必需的。
不过,你不需要具备以下方面的经验:
- GTK+ 或 GNOME 编程经验。
- C 语言以外的编程语言经验。
- 面向对象编程经验。
- 模型 - 视图 - 控制器(MVC)编程经验。
2. 内容限制
相关资料的内容介于教程和参考手册之间,为避免内容过于臃肿,有以下限制:
- 不包含完整的API参考,特别是很少使用或已过时的函数和类。
- 省略某些实现细节,如纯内部的数据结构、库和函数,或仅与GNOME库进一步开发相关的内容。
- 为了给最常用的类和函数留出更多空间,包含一些不常用API组件的参考资料,但没有示例。
- 不会详细介绍如何从一个想法实现一个完整、健壮、优雅的GUI应用程序,其目标不是软件工程,编程示例主要用于演示类和函数,而非完整的应用程序。
3. 排版约定
排版风格与其他编程书籍类似:
- 文件、库、GConf键和URL使用斜体,如 gobject.h, /apps/gconfdemo/ pictures_as_words, 和 http://www.gnome.org/ 。
- 术语首次出现时使用粗斜体。
- 菜单命令使用粗体,用大于号(>)分隔,例如 File > Open 或 Help > Info。
- C代码、shell命令、函数名和变量名使用等宽字体,如GtkWidget *foo = gtk_widget_new();,可通过函数名后的括号区分函数。
- 类名使用粗体,如GtkWidget。
- 对象名使用等宽字体,常看到 “a GConfClient object” 来指代类的不确定对象。
- 参数使用等宽斜体,如G_OBJECT(object)中的object。
- 属性和信号使用等宽粗体,如changed,set−size, 和shadow−type。
- 伪代码用两组尖括号括起来,使用等宽字体,如<< save humanity >>,常看到<< ... >>表示其功能要么明显要么未定义。
- 文献引用使用方括号,如 [Wirth] 和 [Pennington] 。
4. 平台
GTK+ 和 GNOME 逐渐变得平台无关,但主要的工作环境是Unix。当Unix系统之间存在差异时,更倾向于使用具有Linux内核的GNU系统(即GNU/Linux或简称为Linux)。一般会尽量避免操作系统依赖,但为了节省空间和提高清晰度,更倾向于GNU平台,因为绝大多数GNOME安装在该平台上。
5. 编程示例
编程示例非常重要,至少应该浏览一下,了解它们的功能和基本工作原理。很多示例不是完整有效的C程序,前几章主要是简短的多行代码片段,用于演示周围文本中描述的函数调用。完整的程序可以在 http://www.nostarch.com/gnome.htm 获取。
如果遇到未声明的变量,可查看前面的页面找到其声明。当预处理指令(如#include)出现在函数调用之前,需要在源代码开头的某个位置使用该指令。
6. 函数和宏
要记住,只能调用函数。宏的应用看起来很像函数调用,但它是在编译器将源代码转换为目标代码之前进行的展开。在大多数API中,很难区分哪些是函数哪些是宏,因此不做严格区分。
7. 计数规则
在C语言中,数组和字段的索引从0开始。为避免错误,在其他领域也遵循此约定,特别是在列表中,例如GLib的列表元素索引。
8. 路径名规则
路径名系统遵循automake规则,$(prefix)指的是GNOME安装的前缀(例如,/usr 或 /opt/gnome2)。
9. 指针和地址
GTK+ 和 GNOME 广泛使用指针的指针,有时将指针的指针称为指针的地址,通常使用&ptr这样的参数,其中ptr是一个指针。
10. GLib库介绍
10.1 简介
字母G在开源软件领域无处不在,它代表GNU(Richard Stallman的 “GNU’s Not Unix” )。在很多名称中都会看到它,如GTK+, GLib, GObject, 和 GNOME ,以及其他软件包(如Ghostscript和gcc)。
为了理解后续内容,需要了解一个基础库GLib(libglib - 2.0),它为GTK+ 小部件集和GNOME应用程序提供基本的数据结构和实用函数。本章介绍GLib的架构并引入其API。
使用GNOME和GTK+ 时无法避免使用GLib,其他库(如ORBit)也使用GLib,而且很多库不依赖其他库。GLib提供的抽象和实用工具对几乎任何编程任务都很有用,并且简化了向其他平台的移植。
10.2 GLib命名约定
GLib有以下命名规则以保证一致性和可读性:
- 函数名全部小写,各部分之间用下划线分隔,且所有函数名以g_开头,如g_timer_new(),g_list_append()。
- 库中的所有函数都有一个共同的前缀,在GLib中是g_。
- 类型名不包含下划线,每个组件以大写字母开头,且以G开头,如GTimer,GList,但第1.3节中的基本类型除外。
- 如果一个函数主要操作某种类型,其前缀与该类型名对应,例如g_timer_*函数用于操作GTimer类型,g_list_*函数用于操作GList类型。
10.3 基本类型
要使用GLib,需要适应其基本类型。使用guchar而不是unsigned char,在同一平台上可能没有实际区别,但如果要在不同平台(如Windows和Unix)之间导入、导出和交互软件,GLib可以为你抽象基本数据类型。
要使用GLib及其所有类型,需要在源代码中包含glib.h头文件:
#include <glib.h>gpointer和gconstpointer类型在与GLib数据结构交互时经常出现,它们是无类型的内存指针。在GLib中,使用这些指针的函数负责验证类型,而不是程序员或编译器。这些类型在回调函数和排序、迭代中使用的相等运算符中进行类型抽象时特别有用。
GLib头文件为gboolean类型定义了常量TRUE和FALSE,但不建议使用等价运算符与这些常量进行比较,应使用if (my_gboolean),而不是if (my_gboolean == TRUE)。
GLib基本类型与C语言对应类型如下表所示:
| GLib Type | Corresponding Type in C |
| — | — |
| gchar | char |
| ugchar | unsigned char |
| gint | int |
| guint | unsigned int |
| gshort | short |
| gushort | unsigned short |
| glong | long |
| gulong | unsigned long |
| gfloat | float |
| gdouble | double |
| gint8 | int, 8 bits wide |
| guint8 | unsigned int, 8 bits wide |
| gint16 | int, 16 bits wide |
| guint16 | unsigned int, 16 bits wide |
| gint32 | int, 32 bits wide |
| guint32 | unsigned int, 32 bits wide |
| gint64 | int, 64 bits wide |
| guint64 | unsigned int, 64 bits wide |
| gpointer | void, untyped pointer |
| gconstpointer | const void, constant untyped pointer |
| gboolean | Boolean value, either TRUE or FALSE |
11. GLib基本实用工具
11.1 内存管理
使用GLib的内存管理例程可以避免一些麻烦,它提供了额外的错误检查和诊断功能。与C语言类似,操作并不复杂,以下是GLib函数与C语言对应函数的参考:
| GLib Function | Corresponding C Function |
| — | — |
| gpointer g_malloc(gulong n_bytes) | voidmalloc(size_t size) with error handling |
| gpointer g_malloc0(gulong n_bytes) | like malloc(), but initializes memory as in calloc() |
| gpointer g_try_malloc(gulong n_bytes) | like malloc() without error checking |
| gpointer g_realloc(gpointer mem, gulong n_bytes) | voidrealloc(voidptr, size_t size) with error checking |
| gpointer g_try_realloc(gpointer mem, gulong n_bytes) | realloc() without error checking |
| void g_free(gpointer mem) | void free(voidptr) |
如果需要手动检查返回代码,可以使用g_try_malloc()和g_try_realloc(),它们在失败时返回NULL。对于大多数应用程序,像g_malloc()这样的正常函数可以节省大量代码、减少挫折并节省时间。
正常做法是不直接为malloc()和g_malloc()等函数指定请求的内存块大小,而是让编译器或运行时系统根据类型大小倍数(通常使用sizeof())来确定。为了使数据类型一致,需要对malloc()的返回值进行强制类型转换。GLib提供了g_new()、g_new0()和g_renew()宏来简化操作,示例代码如下:
typedef struct _footype footype; footype *my_data; /* Allocate space for three footype structures (long version) */ my_data = (footype *) g_malloc(sizeof(footype)*3); /* The abbreviated version using g_new */ my_data = g_new(footype, 3); /* To initialize the memory to 0, use g_new0 */ my_data = g_new0(footype, 3); /* Expand this block of memory to four structures (long version) */ my_data = (footype *) g_realloc(my_data, sizeof(footype)*4); /* Shorter version */ my_data = g_renew(my_data, 4);需要注意的是,使用g_new()时必须使用类型,否则会导致编译错误,因为g_new()是一个宏。
11.2 内存块
GUI应用程序倾向于反复分配相同大小的内存块(原子),且原子的种类相对较少。GLib使用内存块(GMemChunk)机制为应用程序提供原子。一个内存块由多个原子组成,其块大小是组成原子的总字节长度,因此块大小必须是原子大小的倍数。
以下是使用g_mem_chunk_new()请求新内存块的示例:
GMemChunk my_chunk; my_chunk = g_mem_chunk_new("My Chunk", /* name */ 42, /* atom size */ 42*16, /* block size */ G_ALLOC_AND_FREE); /* access mode */g_mem_chunk_new()函数有四个参数:用于诊断的内存块名称、每个原子的大小、整体块大小(最好写成原子大小的倍数)和访问模式。返回值是指向新GMemChunk结构的指针。
访问模式有两种:
-G_ALLOC_AND_FREE允许随时将单个原子返回内存池。
-G_ALLOC_ONLY仅允许在释放整个内存块时释放原子,这种模式比G_ALLOC_AND_FREE更高效。
以下是在上述示例中创建的内存块中分配和释放内存原子的示例:
gchar *data[50000]; gint i; /* allocate 40,000 atoms */ for(i = 0; i < 40000; i++) { data[i] = g_mem_chunk_alloc(my_chunk); } /* allocate 10,000 more atoms and initialize them */ for(i = 40000; i < 50000; i++) { data[i] = g_mem_chunk_alloc0(my_chunk); } /* free one atom */ g_mem_chunk_free(my_chunk, data[42]);g_mem_chunk_alloc()和g_mem_chunk_alloc0()用于分配单个原子,它们的工作方式类似于g_malloc()和g_malloc0(),但接受GMemChunk结构作为参数而不是大小规格。g_mem_chunk_free()用于将单个原子返回未分配的内存池。
需要注意的是,只能对使用G_ALLOC_AND_FREE访问模式创建的内存块中的原子使用g_mem_chunk_free(),并且永远不要使用g_free()来释放原子,否则会导致段错误。
还有几个函数可以一次性清理和释放内存块中的原子,示例如下:
/* free up any unused atoms */ g_mem_chunk_clean(my_chunk); /* free all unused atoms in all memory chunks */ g_blow_chunks(); /* deallocate all atoms in a chunk */ g_mem_chunk_reset(my_chunk); /* deallocate a memory chunk */ g_mem_chunk_destroy(my_chunk);这些函数的作用分别是:
-g_mem_chunk_clean(chunk)检查块并释放任何未使用的内存。
-g_blow_chunks()对程序中所有未处理的内存块运行g_mem_chunk_clean()。
-g_mem_chunk_reset(chunk)释放块中的所有原子,包括正在使用的原子,使用时要小心。
-g_mem_chunk_destroy(chunk)释放块中的所有原子和内存块本身。
与一般的内存管理一样,GLib提供了一些宏来节省输入,示例如下:
typedef struct _footype footype; GMemChunk *pile_of_mem; footype *foo; /* create a memory chunk with space for 128 footype atoms */ pile_of_mem = g_mem_chunk_new("A pile of memory", sizeof(footype), sizeof(footype)*128, G_ALLOC_AND_FREE);GNOME编程基础与GLib库入门
12. 内存管理流程总结
为了更清晰地展示GLib内存管理的操作流程,下面用mermaid流程图来呈现:
graph LR classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px A([开始]):::startend --> B{选择内存分配方式}:::decision B -->|普通分配| C(使用g_malloc/g_malloc0/g_try_malloc):::process B -->|批量相同大小分配| D(使用内存块GMemChunk):::process C --> E{是否需要调整内存大小}:::decision E -->|是| F(使用g_realloc/g_try_realloc):::process E -->|否| G(继续使用内存):::process D --> H(使用g_mem_chunk_new创建内存块):::process H --> I(选择访问模式):::decision I -->|G_ALLOC_AND_FREE| J(可随时释放单个原子):::process I -->|G_ALLOC_ONLY| K(只能释放整个内存块时释放原子):::process J --> L(使用g_mem_chunk_alloc/g_mem_chunk_alloc0分配原子):::process K --> L L --> M(使用g_mem_chunk_free释放原子):::process G --> N{是否释放内存}:::decision N -->|是| O(使用g_free释放内存):::process M --> P(使用g_mem_chunk_clean/g_blow_chunks/g_mem_chunk_reset/g_mem_chunk_destroy清理或销毁内存块):::process O --> Q([结束]):::startend P --> Q这个流程图总结了GLib中内存管理的主要流程,包括普通内存分配、内存块分配以及相应的释放和清理操作。
13. GLib其他实用工具展望
除了前面介绍的内存管理,GLib还有许多其他实用工具等待我们去探索。例如,GLib提供了丰富的数据结构操作函数,如链表(GList)、双链表(GSList)、哈希表(GHashTable)等。这些数据结构可以帮助我们更高效地组织和处理数据。
以链表为例,GLib提供了一系列操作链表的函数,如g_list_append()用于在链表末尾添加元素,g_list_prepend()用于在链表头部添加元素,g_list_remove()用于移除链表中的元素等。以下是一个简单的链表操作示例:
#include <glib.h> #include <stdio.h> int main() { GList *list = NULL; list = g_list_append(list, "element1"); list = g_list_append(list, "element2"); list = g_list_append(list, "element3"); GList *current = list; while (current != NULL) { printf("%s\n", (char *)current->data); current = current->next; } g_list_free(list); return 0; }在这个示例中,我们创建了一个链表,并向其中添加了三个元素,然后遍历链表并打印出每个元素的值,最后释放链表占用的内存。
14. 总结与建议
通过前面的介绍,我们了解了GNOME编程的基础要求、GLib库的基本概念和使用方法,特别是GLib的内存管理机制。在实际编程中,我们可以根据具体需求选择合适的内存分配方式,合理使用GLib提供的函数和宏,以提高编程效率和代码的健壮性。
为了更好地掌握GLib,建议大家多阅读GLib的官方文档,深入了解其各种功能和用法。同时,多进行实践,通过编写实际的代码来巩固所学知识。在遇到问题时,可以参考GLib的文档和社区论坛,寻求帮助和解决方案。
以下是一些具体的建议:
- 对于初学者,先从简单的示例代码开始,逐步理解GLib的基本概念和操作。
- 在使用内存管理函数时,要注意错误处理,避免内存泄漏和段错误。
- 当需要处理大量相同大小的内存块时,优先考虑使用内存块机制,以提高内存分配和释放的效率。
- 多参考优秀的开源项目,学习他人的编程经验和技巧。
希望大家在GNOME编程和GLib库的学习中取得良好的进展,开发出高质量的应用程序!