淺析Glib

以下轉系 N年前的 一篇宋老師的文章。

原鏈接地址:http://www.ibm.com/developerworks/cn/linux/l-glib/index.html

現摘錄如下,對本文作者表示感謝~~~~~

------------------------------------------------------------------------------


簡介: GLib是GTK+和GNOME工程的基礎底層核心程序庫,是一個綜合用途的實用的輕量級的C程序庫,它提供C語言的常用的數據結構的定義、相關的處理函數,有趣而實用的宏,可移植的封裝和一些運行時機能,如事件循環、線程、動態調用、對象系統等的API。它能夠在類UNIX的操作系統平臺(如LINUX, HP-UNIX等),WINDOWS,OS2和BeOS等操作系統臺上運行。



GLib需要一個支持線程的操作系統和一個字符集間轉換函數iconv的支持,事實上大多現代的操作系統都有以上兩項功能。

GLib由基礎類型、對核心應用的支持、實用功能、數據類型和對象系統五個部分組成的。

GLib的最新版本是GLib2.2.1,可以到www.gtk.org網站下載其源代碼。使用GLib2.0編寫的應用程序,在編譯時應該在編譯命令中加入`pkg-config -cflags -libs glib-2.0`,如編譯一個名爲hello.c的程序,輸出名爲hello的可執行文件,則命令爲:

gcc `pkg-config -cflags -libs glib-2.0`  hello.c -o hello


在GLIB中將線程(gthread),插件(gmoudle)和對象系統(gobject)這三個子系統區別對待,編譯時要注意加入相應的參數。

如程序中用到對象系統,編譯時就應加入:

`pkg-config --cflags --libs gobject-2.0`


用到線程,編譯時則加入:

`pkg-config --cflags --libs gthread-2.0`


用到插件,編譯時則加入:

`pkg-config --cflags --libs gmoudle-2.0`


基礎類型

GLib的基礎是由基礎類型、範圍限定宏、標準宏、類型轉換宏、字節次序變換宏、數學常數定義和雜項宏等各項組成的。這裏主要介紹基礎類型,因爲它們遍及與GLIB相關的各種程序庫和軟件包中,如GTK+,GNOME,MONO等大的開源項目。

基礎類型又稱標準類型,GLib將C語言中的數據類型統一封裝成自己的數據類型,均以小寫字母'g'開頭,如:gpointer是指針類型(void *)、guint是無符號整型(unsigned int)等,其中有一些是修飾性的,如:gint、gchar等,它們和C語言中的int、char是完全相同的。這些數據類型使用起來和C語言中的數據類型完全一樣,當你熟悉了以後會發現它們的使用方法更靈活,更直觀也更易於理解一些。當然你可以把C語言中的數據類型直接拿來使用,這絲毫不影響你編寫程序的編譯。

另外範圍限定宏和類型轉換宏也較常用,如G_MAXINT表示最大的int型值,用宏GINT_TO_POINTER(i)將整型變量i轉換爲指針型,宏GPOINTER_TO_INT(p)將指針型變量p轉換爲整型。

邏輯類型gboolean的值TRUE和FALSE是在常數宏中定義的,另外還包括G_E表示自然對數,G_PI表示圓周率,G_PI_2表示圓周率的1/2等一些數學常數。



對核心應用的支持

GLib對核心應用的支持包括事件循環、內存操作、線程操作、動態鏈接庫的操作和出錯處理與日誌等。

下面代碼演示了事件循環、內存操作、線程這三種功能的簡單應用:

#include <glib.h>
static GMutex *mutex = NULL;
static gboolean t1_end = FALSE;
static gboolean t2_end = FALSE;
typedef struct _Arg Arg;
struct _Arg
{
    GMainLoop* loop;
    gint max;
};
void    run_1(Arg *arg)
{
    int i ;
    
    for(i=0; i<arg->max; i++)
    {
        if(g_mutex_trylock(mutex) == FALSE)
        {
            //g_print("%d : thread 2 locked the mutex \n", i);
            g_print("%d :線程2鎖定了互斥對象\n", i);
            g_mutex_unlock(mutex);
        }
        else
        {
            g_usleep(10);
        }
    }
    t1_end = TRUE;
}
void    run_2(Arg *arg)
{
    int i;
    for(i=0; i<arg->max; i++)
    {
        if(g_mutex_trylock(mutex) == FALSE)
        {
            //g_print("%d : thread 1 locked mutex \n", i);
            g_print("%d :線程1鎖定了互斥對象\n", i);
            g_mutex_unlock(mutex);
        }
        else
        {
            g_usleep(10);
        }
    }
    t2_end = TRUE;
}
void run_3(Arg *arg)
{
    for(;;)
    {
        if(t1_end && t2_end)
        {
            g_main_loop_quit(arg->loop);
            break;
        }
    }
}
int    main(int argc, char *argv[])
{
    GMainLoop *mloop;
    Arg *arg;
    
    if(!g_thread_supported())
        g_thread_init(NULL);
    mloop = g_main_loop_new(NULL, FALSE);
    
    arg = g_new(Arg, 1);
    arg->loop = mloop;
    arg->max = 11;
        
    mutex = g_mutex_new();
    g_thread_create(run_1, arg, TRUE, NULL);
    g_thread_create(run_2, arg, TRUE, NULL);
    g_thread_create(run_3, arg, TRUE, NULL);
    
    g_main_loop_run(mloop);
    g_print("線程3退出事件循環\n");
    g_mutex_free(mutex);
    g_print("釋放互斥對象\n");
    g_free(arg);
    g_print("釋放參數所用的內存\n");
}


Makefile文件如下:

CC = gcc
all:
    $(CC) `pkg-config --cflags --libs glib-2.0 gthread-2.0` loop.c -o loop
    


下面爲輸出結果:

0 :線程1鎖定了互斥對象
1 :線程2鎖定了互斥對象
2 :線程1鎖定了互斥對象
3 :線程2鎖定了互斥對象
4 :線程1鎖定了互斥對象
5 :線程2鎖定了互斥對象
6 :線程1鎖定了互斥對象
7 :線程2鎖定了互斥對象
8 :線程1鎖定了互斥對象
9 :線程2鎖定了互斥對象
10 :線程1鎖定了互斥對象
線程3退出事件循環
釋放互斥對象
釋放參數所用的內存

以上例程創建了三個線程,其中run_1和run_2操作互斥對象,run_3檢索前兩個線程是否結束,如結束的話,則執行g_main_loop_quit退出事件循環。由於線程的運行是不確定的,所以不一定每次都是這一輸出結果。

首先定義一個結構類型來保存創建的事件循環的對象指針和線程運行時的最多循環次數,一般情況下,如果爲此數據結構來分配內存的話,用Arg *arg = (Arg *)malloc(sizeof(Arg));,釋放時用free(arg);,這種傳統的做法曾經讓很多C語言的初學者頭痛,尤其是需要多次操作的時候,GLib中提供了類似的函數g_malloc和g_free,最好用的方法是其將g_malloc函數封裝成了宏g_new,這個宏有兩個參數,第一個是結構類型,第二個是要分配結構的數量,這段代碼中只用到了一個Arg數據結構,所以是g_new(Arg, 1)。在程序結束時用g_free來釋放。

在線程初始化時,首先是判斷線程是否初始化的函數g_thread_supported,如果其返回FALSE則表明線程並未初始化,這時必須用g_thread_init來初始化,這是較明智的做法。

事件循環GMainLoop在用g_main_loop_new創建之後並不馬上運行,用g_main_loop_run運行後,還要用g_main_loop_quit退出,否則循環將一直運行下去,這兩個函數的參數都是GMainLoop型的指針,在主函數中並未直接運行g_main_loop_quit,而是把它放在線程的函數中了,這一點需讀者注意。



實用功能

GLib中包含了近二十種實用功能,從簡單的字符處理到初學者很難理解的XML解析功能,這裏介紹兩種較簡單的:隨機數和計時。

下面代碼演示如何產生1-100之間的隨機整數和演示如何計算30000000次累加在計算時用的時間:

/* until.c 用來測試實用功能 */
#include <glib.h>
int    main(int argc, char *argv[])
{
    GRand *rand;
    GTimer *timer;
    
    gint n;
    gint i, j;
    gint x = 0;
    rand = g_rand_new();    //創建隨機數對象
    for(n=0; n<20; n++)
    {    //產生隨機數並顯示出來
        g_print("%d\t",g_rand_int_range(rand,1,100));
    }
    g_print("\n");
    g_rand_free(rand);    //釋放隨機數對象
    //創建計時器
    timer = g_timer_new();
    g_timer_start(timer);//開始計時
    for(i=0; i<10000; i++)
        for(j=0; j<3000; j++)
            x++;//累計
    g_timer_stop(timer);//計時結束
    //輸出計時結果
    g_print("%ld\tall:%.2f seconds was used!\n",x,g_timer_elapsed(timer,NULL));
}


Makefile文件內容如下:

CC = gcc all: $(CC) `pkg-config --cflags --libs glib-2.0 ` until.c -o until

輸出結果:

48 95 95 99 90 24 90 29 78 4 53 87 1 86 7 93 57 88 75 4
30000000 all:1.47 seconds was used!


GLIB中的每個對象幾乎都有一個或多個*_new函數來創建,計時器GTimer和隨機器GRand也一樣,也都有相對應的函數來結束對象的使用,如GTimer的g_timer_stop和GRand的g_rand_free。

這可能是GLIB實用功能中最簡單的兩種了,許多朋友會一目瞭然。我們還應注意到GLIB的代碼風格和封裝技巧是具有獨到之處的,這種風格和技巧足以讓一些自稱簡潔實用的SDK汗顏,學習掌握這一風格可能會讓我們受益匪淺。



數據類型

GLib中定義了十幾種常用的數據結構類型和它們的相關操作函數,下面是關於字符串類型的簡單示例:

#include <glib.h>
int    main(int argc, char *argv[])
{
    GString *s;
    s = g_string_new("Hello");
    g_print("%s\n", s->str);
    s = g_string_append(s," World!");
    g_print("%s\n",s->str);
    s = g_string_erase(s,0,6);
    g_print("%s\n",s->str);
    s = g_string_prepend(s,"Also a ");
    g_print("%s\n",s->str);
    
    s = g_string_insert(s,6," Nice");
    g_print("%s\n",s->str);
}


Makefile文件如下:

    CC = gcc
all:
    $(CC) `pkg-config --cflags --libs glib-2.0 ` string.c -o str
    


下面是輸出結果:

Hello
Hello World!
World!
Also a World!
Also a Nice World!


字符串在編程中出現頻率之高,即使是初學者也很清楚,追加、刪除和插入等常用操作理解後,還可以進一步瞭解掌握其它更復雜的操作。

GLib提供了一種內存塊(GMemChunk)數據類型,它爲分配等大的內存區提供了一種非常好用的操作方式,下面程序演示了內存塊數據類型的簡單用法:

#include <glib.h>
int    main(int argc, char *argv[])
{
    GMemChunk *chunk;    //定義內存塊
    gchar *mem[10];    //定義指向原子的指針數組
    gint i, j;
    //創建內存塊
    chunk = g_mem_chunk_new("Test MemChunk", 5, 50, G_ALLOC_AND_FREE);
                //名稱,原子的長度, 內存塊的長度,類型
    for(i=0; i<10; i++)
    {
        //創建對象
        //mem[i] = g_chunk_new(gchar, chunk);
        mem[i] = (gchar*)g_mem_chunk_alloc(chunk);
        for(j=0; j<5; j++)
        {
            mem[i][j] = 'A' + j;//爲內存塊中的指針賦值
        }
    }
    
    g_mem_chunk_print(chunk);    //顯示內存塊信息
    for(i=0; i<10; i++)
    {
        g_print("%s\t",mem[i]);//顯示內存塊中的內容
    }
    
    for(i=0; i<10; i++)
    {
        g_mem_chunk_free(chunk,mem[i]); //釋放所有分配的內存
    }
    g_mem_chunk_destroy(chunk);
}


Makefile文件件如下:

CC = gcc
all:
    $(CC) `pkg-config --cflags --libs glib-2.0` data1.c -o data1
    


以下爲輸出結果:

GLib-INFO: Test MemChunk: 80 bytes using 2 mem areas
ABCDE    ABCDE    ABCDE    ABCDE    ABCDE    ABCDE    ABCDE    ABCDE    ABCDE    ABCDE


這裏說明這一數據類型的原因是通過它可以他細體會內存分配這一運行時處理環節的應用之妙。

我們在程序中分配的是50字節的空間,而實際用的是80字節,由此可以看出其在分配內存時本身用到了部分內存空間。

從上面的示例代碼中可以看出,在GLib中幾乎所有的對象都是C語言的結構類型,一般命名以大寫字母G開頭的單詞,如GList表示雙向鏈表,所有與之相關的操作函數都以小寫的字母g加下劃線加小寫的單詞加下劃線開頭,如以g_list_*開頭的函數都是與這相關的操作函數,而且這些函數中的第一個參數多數是此對象的指針。

GLIB中的數據類型在GLIB本身,尤其是GTK+中頻繁用到,瞭解掌握這些數據類據類型的用法是非常必要的,這對進一步靈活開發GTK+程序來說是關鍵一環,而且是對大學中的《數據結構》一科的很好回顧。

在下一篇GOBJECT對象系統中將詳細介紹GLIB中最重要的組成部分GOBJECT系統,希望這一由C語言組建的單繼承的對象系統會幫助你走進GTK+的世界。

參考資料

    GLib的在線文檔: http://developer.gnome.org/doc/API/2.0/glib/index.html


發佈了33 篇原創文章 · 獲贊 1 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章