GLib学习笔记

 Glib笔记1

以前主要是做C++,最近有个机会转到嵌入式的C,大部分用的是Glib/Gobject系统,前一周的样子把相关内容大致浏览了一遍,主要以reference Menu为主。我看英文书的一个习惯是看一章,总结一章,这样学得快,记得牢。以前整理过很多书籍,不过都是本地的doc文档。在学Glib过程中,从网上学到很多知识,所以我也放到博客上吧。仅供自己总结,风格比较随意。

 这一部分主要是Glib的基础

1.1 版本信息

注意,自己的程序里边都要包含<glib.h>

下面这些宏:用于版本信息,主要用于configure时候的版本检测,一般应用程序不太用这个

#define             GLIB_MAJOR_VERSION

#define             GLIB_MINOR_VERSION
#define
             GLIB_MICRO_VERSION
#define
             GLIB_CHECK_VERSION             (major,minor,micro)
最后一个宏,返回布尔值,应用程序可用他判断是否为正确的版本。

[这一小节没什么东西]

 

1.2 基本数据类型

Glib提供了一套可移植的原始数据类型,例如gint等,知道这个就行了,以后写Glib的程序,尽量用它定义的数据类型

有几个值得注意:

l         gboolean:返回TRUEFALSE

l         代表返回void*指针的--gpointer,以及一个const void*  gconstpointer

l         返回可表示范围的,例如G_MINSHORT,G_MAXSHORT

l         提供给printscan函数的宏,如G_GINT16_MODIFIER/G_GINT16_FORMAT

l         64位整数,有两个红,G_GINT64_CONSTANT,G_GUINT64_CONSTANT

l         sizeof(struct)返回gsize类型,goffsset=gint64

[没什么难度这一节]

 

1.3 基本数据类型的表示范围

上一节也提到过,例如G_MAXINT,G_MININT

1.4 标准宏

 #define             G_STRUCT_MEMBER(member_type, struct_p, struct_offset)

member_type:成员类型

strutc_p:指向该结构的指针

struct_offset:偏移

估计里边的实现是 (member_type*((gchar*)struct_p + struct_offset)

类似的还有:

#define             G_STRUCT_MEMBER_P(struct_p, struct_offset)

没有前面的强制类型转换了

#define             G_STRUCT_OFFSET(struct_type, member)

返回member在结构中的偏移字节。具体怎么实现有点忘记了,但是内核代码中常见这样的内容。

1.5 类型转换宏

#define             GINT_TO_POINTER                     (i)

#define             GPOINTER_TO_INT                     (p)

#define             GUINT_TO_POINTER                    (u)

#define             GPOINTER_TO_UINT                    (p)

#define             GSIZE_TO_POINTER                    (s)

#define             GPOINTER_TO_SIZE                    (p)

目的其实很简单,就是在指针类型中存储整数类型。我们倒是经常在Windows下用DWORD存储指针类型的数据。

1.6 字节序宏

转换字节序的一些辅助宏和函数,例如g_htons,g_ntohs等。

l         本机是怎样的字节序?查看宏定义G_BYTE_ORDER

没什么意思

1.7 数值定义

定义一些数值常量。

G_PI,定义Pi常量。

没什么意思

1.8 其他杂项

l         定义一个多行的宏,G_STMT_STARTG_STMT_END,其实就是do/while{0}的组合

l         G_BEGIN_DECLS,G_END_DECLS,头文件中用的开头和结尾

有些有用,到时候再查就知道了。

1.9 原子操作函数

很多啊

gint                g_atomic_int_get                    ();

void                g_atomic_int_set                    ();

void                g_atomic_int_add                    ();

gint                g_atomic_int_exchange_and_add       ();

gboolean            g_atomic_int_compare_and_exchange   ();

gpointer            g_atomic_pointer_get                ();

void                g_atomic_pointer_set                ();

gboolean            g_atomic_pointer_compare_and_exchange

                                                        ();

void                g_atomic_int_inc                    (gint *atomic);

gboolean            g_atomic_int_dec_and_test           (gint *atomic);

manual做的也真是不到位,竟然连参数都没有写进来。

看这些东西,最好装一个source insight。直接看源码。

2.1 MainLoop

main loopGlib中一个非常重要的部分,其主要用途类似于Windows的消息循环。所以它是一个循环,不停得从某个地方取得“消息”,然后派发到消息处理函数,交给他们处理。

从执行者角度看,派发到消息处理函数实际还是消息循环所在的线程调用对应的函数。

Glib中,消息循环对应的就是这个mainLoop。消息队列没有对应物。但是消息的产地由GSource标示,例如一个文件描述符,一个socket都是某种sourceGlib提供了GMainContext来管理这些source。而mainLoop只要管理Context即可管理很多source

其实很简单,仔细比较下windows下的线程函数就可知道。Windows下的线程函数一般会循环等待一些事件,例如

while(TRUE)

{

WaitForMultiObjects(count,arry,...);

其中arry是一个事件数组。

}

仔细想想,mainLoop是上面那个while循环的对象封装,contextarry数组的对象封装,而arry里边的成员就是source的对象封装。

通过这种方式的对象封装,在不同线程里边直接使用mainLoop对象,或者切换context对象就可以做到非常方便简洁。

循环退出,必须有个事件处理中调用g_main_loop_quit才可以。

如果你能这么理解MainLoop的话,就非常容易从WIN-API转到GLib中去了。

实际上,MainLoop的整个系统比较精巧,涉及到定时源,空闲源,优先级等。

我看了下Glib的实行代码,内部对应WaitForMultiObjects的是poll函数。可想,如果有很多事件的话,由poll本身造成的效率损失将比较大。另外它是在一个线程中执行处理函数的,所以会影响后续事件的处理。

下面将详细讲述和比较。

1.迭代

其实就是强制调用一次WaitForMultiObjects以检查发生了什么事件。Glibcontext分了好几种状态。

 

 Context状态

状态只有context才有,想想和Wait函数的那个比较。待会再解释那几个状态以及对应函数的原型说明。

从这个图以及源代码看,一次iteration调用包括先prepare下,然后调用poll函数,然后调用check—>dispatch。具体干了什么,下面再说。其实就是讨论wait函数的内部实现

这些个状态的确定是需要调用对应源的函数来确定的。而源函数由GSourceFuncs来确定。

typedef struct {

  gboolean (*prepare)  (GSource    *source,

                         gint       *timeout_);

  gboolean (*check)    (GSource    *source);

  gboolean (*dispatch) (GSource    *source,

                        GSourceFunc callback,

                         gpointer    user_data);

  void     (*finalize) (GSource    *source); /* Can be NULL */

 

  /* For use by g_source_set_closure */

  GSourceFunc     closure_callback;      

  GSourceDummyMarshal closure_marshal; /* Really is of type GClosureMarshal */

} GSourceFuncs;

prepare函数:将调用每个source的这个函数以确定哪些已经准备好了,source在这些函数里边可以判断下自己的真正的源(比如socket)是否已经准备好,timeout是表示需要等待的超时时间。最终的超时时间将是所有源的最小时间。如果确定已经准备好了,则返回TRUE,表明后续就不用在poll中等待它了。

check函数:这个函数的存成,主要是prepare返回FALSE,而且poll也没等到,所以只能在Poll后再由程序自己去确定是否发生了啥时间。

dispatch函数:处理preparecheck函数返回TRUE的事件。这两个函数有一个返回TRUE就表明有东西要处理了。

应该查看代码确定下,是不是prepare返回TRUE的话,就不调用pollcheck了。

经查,check只对非READY状态的source进行调用。

prepare本身没干什么,好像还是会调用poll

最终调用dispatch的就是那些返回READY的句柄

 

 

 

2.2函数API

l         main_loop相关函数,主线程有一个默认的context

l         main_context相关函数,中断wait—g_main_context_wakeup

(1) context的所有权,只有线程得到context的所有权后,才能处理它。想想也是。

(2 )g_main_context_push_thread_default系列函数,干嘛使的?看了下实现,才知道每个线程都有一个私有数据队列,这个队列stack存的就是context。而g_main_context_default返回的是主线程的那个context(其实是一个全局变量的context。个人感觉一般不要操作这个mainloop。或者只由主线程去操作它。)

l         事件优先级define

l         空闲事件源,得先创建一个,然后插入到context中去。回调函数的返回值决定下次是否继续调用。

l         定时事件源。同上。

另外,关于source的创建及其使用,感觉怪怪的。只能attach,没有detach,而且只有destroy后不能再attachwhy

另外,g_source_new这个函数特别奇怪,返回的是GSource*,为何又要我传入GSource结构的大小?难度自己不知道吗?看了看实现,也没什么区别呀。或许可以从GSource中派生?

一个source里边含一个fd队列,所以new出来这个source对象实际是一个简单的封装,后续还是要把真正的fd加入到这个队列中去。

这么看来,如果要使用source干点活的话,还是得直接从I/O通道中创建source

2.3 线程和线程池,异步队列

要在glib中使用线程,必须先调用g_thread_init来初始化线程库。

这个要求挺奇怪,里边肯定是有什么东西是全局的,所以要先初始化一下。

线程及同步结构没什么太多神奇的地方,一定是对POSIX同步结构进行了一下封装罢了。有些静态对象的使用,不需要初始化线程库也行。关键是用法要习惯。

线程池是一个有点奇怪的地方,主要是它的用法和参数上。

l         先创建一个线程池对象,里边会启动线程(可能)。叫任务池可能更好一点。

l         任务参数比较奇怪,有两个,一个是任务本身,另外一个是user数据。后来查看了下代码,通过g_thread_pool_push等加入的是任务,而用户数据则是在创建pool的时候传入的,所以对一个任务执行来说,有两个参数。其实里边就是一个list,把任务数据保存在list中罢了

异步队列,是一个比较实际的东西。原理很简单,有线程往里边加东西,另外有线程往里边取数据,这里围绕队列进行了同步处理。所以叫异步队列。

2.4 动态加载

动态加载需要包含gmodule.h,编译的时候也需要指定gmodule-2.0

动态加载对这个路径分隔和模块后缀进行了宏定义,在不同平台下用一个宏即可。

有一个比较有意思的地方,在UNIX平台下,就是动态加载时候的CheckInitFuncUnloadfunc。这两个函数是在加载动态库前和卸载前调用的最后一个函数。其实就是模仿Windows下的Dllmain函数。

l         内部处理经过勘察代码发现,CheckInitFunc是实际已经加载了,然后再调这个函数(所以模块必须实现一个叫g_module_check_init的函数)。这个典型的模仿DllMain

l         UnLoadFunc是一样的道理。

G_MODULE_IMPORTG_MODULE_EXPORTWin下有意义。

2.5 内存分配和释放

这节的内容可能是经常要用到的。glib对一些C库函数进行了下封装吧应该。

g_malloc,g_malloc0,g_new,g_new0好像都没什么区别。

注意:一旦分配没成功,程序将直接exit。这点和库函数不一样。

晕,如果运行时候,出现这种问题也够可以的了。

2.6 I/O通道                                                             

分出来一个GIOChannel结构。实际代表一个FD,但是和source不太一样,source是事件意义上的源,而IO则是对FD的封装,这两个还是有本质区别的。

GIOChannel可以转换成一个source,也可以加入到context中,但只能是默认的全局context

g_io_add_watcher,这个函数照实奇怪,为何不把context指针传进去啊,这样我就能加入到我想加入的context中去了。其实内部就是调用g_source_attach。非常奇怪。

 

  

 工具

这部分内容主要是提供了一些公共的API或者是类,帮助完成各种不同的功能。

3.1 字符串操作函数

g_strxxx等系列函数,方便完成字符串操作,有点像C/C++里边的函数。

这个和CString还不是同一类东西。CString是字符串类,可能更加方便。

3.2 字符集转换函数

包括UTF8UNICODE等转换的函数。

值得注意的是文件名的字符集转换函数,有:

l           g_filename_from_uri,g_filename_to_utf8

因为glib自己用的是UTF8类型,而OS用的可能不同。

3.3 UNICODE转换/Base64字符编码

不说了。

3.4 校验和

GLIB确实很全,竟然提供了MD5等校验和的封装。

这几个函数的使用有要注意的:

l         一旦调用g_checksum_get_string/digest后,校验和对象就不能再使用了,只能重设后才能再度校验。

3.5 国际化函数

不知所云,从没接触过类似“应用场景”。

3.6 日期和时间函数

FT,这个类别超多。不知道为啥会整这么多东西出来。有需要再研究吧。

3.7 随机数

生成随机数用的API

3.8 钩子函数管理

不知道为啥需要这个,难道在LINUX很多这么做的吗?

用一个GHookList管理钩子函数对象的集合,钩子函数对象用GHook表示。

终于明白Marshaller(列集函数)的意思了,实际和GObject有关。它的目的很简单,在不同语言中,统一用GValue来表示数据类型,不同语言的调用都统一到GValue中来做接口,Marshaller函数就是把相对于特定语言的类型转换成GVALUE类型,然后在函数中传递。这么说的话,一定还有一个解列函数。

3.9 混杂函数

这个混杂函数集合其实包含一些很有用的函数。

比如取app_name,user_name,setenv,getenv

3.10 文本扫描/自动完成

不说了。

3.11 时钟函数

GTimer对象。类似GetTickCount之流。

3.12 子进程创建

         不说了

3.13 文件操作

不是IO操作,而是创建文件,文件夹,获取文件大小,删除文件等函数。

3.14 URI/主机名/shell相关

不说了。

其中shell相关的还真是没搞明白想干什么。

3.15 命令行选项分析器

这个比较方便,自己写得话会非常麻烦。

GOptionContext等。

3.16 模式匹配/Perl相关/XML相关

不说了。

3.17 -值解析

类似.ini解析的函数封装。非常好。

3.18 书签/测试框架/Windows兼容函数

不说了。

 

 数据结构

这部分非常重要。

4.1 内存片管理

memory slices是一个高效的分配和管理等尺寸的类。

这个和普通的new/delete相比有什么特别之处?

似乎更高效,有分布式内存管理“之嫌“。建议替代g_malloc系列。

没看出什么特别之处啊,暂时用malloc

包括一个过时的gmemchunk类,这里不叙述了。

4.2 /单向链表

GList是双向链表。仔细看看和STL相比提供了哪些不同之处。

l         GList内部单元都是从slice allocator中分配得来的,所以应该很高效。

l         分配GList* p = NULL

l         插入用,append,prepend,insert,insert_sorted

l         删除:remove

l         迭代:first,nth

l         遍历:foreach

l         查找index(这个估计是GList特别提供的)index()

注意:GList没有一个单独的对象来表示整个队列集合,不像STL就用list来表示整个集合。内部单元是单独的一个东西。所以使用GList一定要注意了,GList本身就是单元,所以一定要维护GList链表的头单元。

另外,GList是单元,内部有指针指向内存块,GList本身是可以从slice中分配得来的。所以free Glist的时候是将单元本身返回slice中,而内部的执行内存块需要自己去释放。

它这个find函数很有特点,find的对比参数是单元中的data指针。好像STL中的也是这样的哈。要是有个函数指针参数就好了。这就得用到find_custom函数了

单向链表用GSList就行了。

其他好像差不多,没有提到过操作函数的效率问题,因为不是泛型编程,所以没有哪个API是通用的。

4.3 双端队列

双端队列用GQueue表示,这个和STL类似。

GQueue有静态分配的,而上述的list没有。这点真的很奇怪。

 看了GQueue的内部结构,发现里边是两个GList

clear函数了。这里有xxx_link函数,link的参数是一个GList。前面的insert等是一个数据data,内部会分配一个GList单元的。

4.4 序列

序列实际是一个内部采用平衡树来实现的链表。

看来就是一个set吧,内部是排序的。

GSequence代表序列,需要用GSequenceIter来迭代,它这个beginlast位置是否和STL中的一致呢?last是最后一个的后边。begin是第一个的前边,看来就这个begin不一样,双方都是全开空间。另外,不像STL中的迭代子,GSIter在整个操作过程中不会无效(除非删掉该元素),这一点确实比较不一样。

怎么解引用GSIter?也是一个指针的指针吗?应该不是。

l         获取begin等位置:g_seqx_get_begin_iter,end_iter

l         append,prepend等返回iter

l         序列最重要的是find函数,这里叫search

l         迭代前后,iter_next,iter_prev

l         解引用iter,用g_sequence_get/set等,set最好不要直接去设置,否则就不会排序了。

有点麻烦,因为即使最简单的int类型都得提供一个比较函数,难道没有些默认的吗?

 

4.5 TrashStack

GTrashStack,垃圾栈?目的是什么?用于回收不用的内存块。不知道有什么用。一般要是内存块不用的话直接就free了。

从提供的API来看,似乎就是一个保存free内存块的地方,只不过用stack结构来存储罢了。

 

4.6 Hash

GHashTable表实际非常有用,但是STL并没有提供对应的对象。GLIBhash结构需要自己提供hash函数,这个比较麻烦,有没有默认的hash函数呢?GLIB提供了字符串哈希函数,整型hash函数等。极大方便使用了。注意以下:

l         GLIB的哈希并不copy一份keyvalue,所以得自己保存。这个确实也是,因为C中没有运算符重载之说,也没有拷贝构造函数。

越来越感觉GLIBAPI的封装与从并行开发中学到的API编写非常类似,到处是函数指针。

GHashTableIter,一般在栈上分配,然后用g_hash_table_iter_init来初始化并使用之。

在使用中如果修改了HASH表,将使得iter无效。

 

4.7 String对象

1. GString

GString类似CString,但使用上肯定不如对象封装的C++方便。

GString内部数据指针由str指示。

2. GStringChunk

一个string的集合,难道类似CStringArray?当许多string需要操作的时候,用这个比g_strdup要方便,因为它不会频繁得去malloc内存。

有几个函数需要特别注意:

l         insert_constinsert,这里内部好像会区分是怎么加入到chunk中的,而且搜索的时候会区分开来类型。

4.8 Array

g_array_free这个函数比较让人思考。如果free_segmentFALSE的话将返回内部element-data的指针。难道内部是一整块内存吗?从GArray的结构来看好像确实是。

1. GPtrArray

存放指针的array。与上面的差不多,不过可以设置element_destroy_func

2. ByteArray

存放Bytearray

4.9 平衡二叉树

GTree提供平衡二叉树的功能。这个其实就是搜索二叉树有keyvalue之分。

对应STL中的map,用得是红黑树。

GTree提供遍历接口,GTraverseType,和树完全一样,如中序,前序,后序,层级遍历。

 

4.10 N序树

这个用得很少,GNode表示一个节点,没有单独的表示集合的数据结构。

不说了,关键还是看应用需要。

 

4.11 Quarks

GQuark。不知道这个怎么翻译,但实际作用就是将一个字符串与一个GQuark对象对应。姑且可以看做是某种意义上的Stringhash

注意,这个不是一个集合。GQuark应该是一个单元,这个单元的值由一个string计算得来,看来还有使用GQuark作为单元的集合,后续会用到。

内部GQuark就是一个整数。看API,难道内部有一个巨大的集合来存储string,这样可以避免同一个内容的string来产生一样的quark

看了下实现,果真里边有一个全局的HashTable

 

4.12 带关键值的数据列表Keyed Data List

GData提供了通过CQuark方式来快速访问的接口。实际就是Hash操作,为啥会有这种需求?大概用在GObject这种大量使用字符串来表示key的地方。

 

4.13 数据集Data Sets

用法很奇特。目的比较明确。

哪儿肯定有全局的东西在保存着。dataset没有对应的数据结构。

怎么用这个啊?难道每次都要先搞一个location出来吗?

非常奇特。但好像很有用。

4.14 关系和元组 Relations and Tuples

GRelation/GTuples,难道类似Erlang中的吗?

GRelation是一个表,里边的itemGTuples,每个GTuple含多个列(目前限2列)。

有点像列表控件。

 

4.15 Cache

GlibCache机制还进行了封装,难得....,具体设计上也用都了key/value,这个是必须的。

API使用有特点,如果插入的key不存在,则通过回调方式来创建value,然后就返回这个value。这个和我们以前想的先创建keyvalue,然后再加进去的方法完全不一样。

4.16 过时的内存分配

不再多说。

 Glib工具

glib-gettextize,干嘛使的?国际化用的,参考gettextize。用于系统的国际化和本地化,可以在编译程序的时候使用本国语言支持(NLS),可以使程序的输出使用用户设置的语言而不是英文。

gtester:单元测试程序。并将结果格式化输出成XML/HTML

gtester-report:格式化XMLHTML的工具。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值