glib--消息分發(gmainloop,gsource),信號

每一個管道默認包含一個總線,所以應用程序不需要再創建總線。
應用程序只需要在總線上設置一個類似於對象的信號處理器的消息處理器。
當主循環運行的時候,總線將會輪詢這個消息處理器是否有新的消息,當消息被採集到後,總線將呼叫相應的回調函數來完成任務。

 GLib對核心應用的支持包括事件循環、內存操作、線程操作、動態鏈接庫的操作和出錯處理與日誌等。基本上,所有需要異步操作的地方都可以用事件循環。像文件、管道、設備、套接字、定時器、idle和其他自定義的事件都可以產生事件。

    今天,讓我們簡單的瞭解下GMainLoop, GMainContext和GSource。

要讓GMainLoop能夠處理事件,首先就必須把它們加到GMainLoop去。
    首先我們需要了解事件循環的三個基本結構:GMainLoop, GMainContext和GSource。
    它們之間的關係是這樣的:
    GMainLoop -> GMainContext -> {GSource1, GSource2, GSource3......}

    每個GmainLoop都包含一個GMainContext成員,而這個GMainContext成員可以裝各種各樣的GSource,GSource則是具體的各種Event處理邏輯了。在這裏,可以把GMainContext理解爲GSource的容器。(不過它的用處不只是裝GSource)
    創建GMainLoop使用函數g_main_loop_new, 它的第一個參數就是需要關聯的GMainContext,如果這個值爲空,程序會分配一個默認的Context給GMainLoop。把GSource加到GMainContext呢,則使用函數g_source_attach。

我們先看一段簡單的示例代碼:

    GMainContext* main_context = NULL;

    main_context = g_main_context_new();
    if(!main_context)
    {
       return SYS_FAILED;
    }
 
    main_loop = g_main_loop_new(main_context,FALSE);
   /*unref main ctx here so that main ctx is freed when mainloop was freed*/
    g_main_context_unref(main_context);
    if(!main_loop)
    {
      return SYS_FAILED;
    }

    g_main_loop_run(main_loop);

    g_main_loop_new創建一個main loop對象,一個main loop對象只能被一個線程使用,但一個線程可以有多個main loop對象。

g_main_loop_quit則是用於退出主循環,相當於Win32下的PostQuitMessage函數。

GMainLoop的主要部件是GMainContext,GMainContext可以在多個GMainLoop間共享,但要求這些GMainLoop都在同一個線程中運行,前面提到的模態對話框就屬於這一類。GMainContext通常由多個GSource組成,GSource是事件源的抽象,任何事件源,只要實現GSource規定的接口,都可以掛到GMainContext中來。

    Gsource的定義如下:

struct _GSource
{
  /*< private >*/
  gpointer callback_data;
  GSourceCallbackFuncs *callback_funcs;

  GSourceFuncs *source_funcs;----gsource的接口函數
  guint ref_count;

  GMainContext *context;

  gint priority;
  guint flags;
  guint source_id;

  GSList *poll_fds;
 
  GSource *prev;
  GSource *next;

  char    *name;

  GSourcePrivate *priv;
};

GSource的接口函數有:

struct _GSourceFuncs
{
  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 */

}

下面我們看看幾個內置Source的實現機制:

Idle 它主要用實現異步事件,功能類似於Win32下的PostMessage。但它還支持重複執行的特性,根據用戶註冊的回調函數的返回值而定。

  1. g_idle_prepare把超時設置爲0,也就是即時喚醒,不進入睡眠狀態。
  2. g_idle_check 始終返回TRUE,表示準備好了。
  3. g_idle_dispatch 調用用戶註冊的回調函數。

Timeout 它主要用於實現定時器,支持一次定時和重複定時,根據用戶註冊的回調函數的返回值而定。

  1. g_timeout_prepare 計算下一次的超時時間。
  2. g_timeout_check 檢查超時時間是否到了,如果到了就返回TRUE,否則返回FALSE。
  3. g_timeout_dispatch調用用戶註冊的回調函數。

線程可以向自己的mainloop中增加Source,也可以向其它線程的mainloop增加 Source。向自己的mainloop中增加Source時,mainloop已經喚醒了,所以不會存在什麼問題。而向其它線程的mainloop增加Source時,對方線程可能正掛在poll 裏睡眠,所以要想法喚醒它,否則Source可能來不及處理。在Linux下,這是通過wake_up_pipe管道實現的,mainloop在poll時,它除了等待所有的Source外,還會等待wake_up_pipe管道。要喚醒poll,調用 g_main_context_wakeup_unlocked向wake_up_pipe裏寫入字母A就行了。

GLib 內部實現了三種類型的事件源,分別是 Timeout, Idle, Child Watch。

同時也支持創建自定義的事件源——也就是添加child watch

1. Timeout事件源

//mainloop1.c
#include<glib.h>
GMainLoop* loop;

gint counter = 10;
gboolean callback(gpointer arg)
{
    g_print(".");
    if(--counter ==0){
        g_print("\n");
        //退出循環
        g_main_loop_quit(loop);
        //註銷定時器
        return FALSE;
    }
    //定時器繼續運行
    return TRUE;
}

int main(int argc, char* argv[])
{
    if(g_thread_supported() == 0)
        g_thread_init(NULL);
    g_print("g_main_loop_new\n");
    loop = g_main_loop_new(NULL, FALSE);
    //增加一個定時器,100毫秒運行一次callback
    g_timeout_add(100,callback,NULL);
    g_print("g_main_loop_run\n");
    g_main_loop_run(loop);
    g_print("g_main_loop_unref\n");
    g_main_loop_unref(loop);
    return 0;
}
編譯運行:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop1.c -o mainloop1

 

2. Idle事件源

已經在另一篇博客中介紹了

 

3. Child Watch——系統定義的事件源

#include <glib.h>   
#include <stdio.h>   
#include <strings.h>   
GMainLoop* loop;   
//當stdin有數據可讀時被GSource調用的回調函數   
gboolean callback(GIOChannel *channel)   
{   
    gchar* str;   
    gsize len;   
    //從stdin讀取一行字符串   
    g_io_channel_read_line(channel, &str, &len, NULL, NULL);   
    //去掉回車鍵()   
    while(len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))   
        str[--len]='\0';   
    //反轉字符串   
    for(;len;len--)   
        g_print("%c",str[len-1]);   
    g_print("\n");   
    //判斷結束符   
    if(strcasecmp(str, "q") == 0){   
        g_main_loop_quit(loop);   
    }   
    g_free(str);   
}   
void add_source(GMainContext *context)   
{   
    GIOChannel* channel;   
    GSource* source;   
    //這裏我們監視stdin是否可讀, stdin的fd默認等於1   
    channel = g_io_channel_unix_new(1);   
    //g_io_create_watch創建一個默認的io監視作用的GSource,下次再研究自定義GSource。參數G_IO_IN表示監視stdin的讀取狀態。   
    source = g_io_create_watch(channel, G_IO_IN);   
    g_io_channel_unref(channel);   
    //設置stdin可讀的時候調用的回調函數   
    g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);   
    //把GSource附加到GMainContext   
    g_source_attach(source, context);   
    g_source_unref(source);   
}   
int main(int argc, char* argv[])   
{   
    GMainContext *context;   
    if(g_thread_supported() == 0)   
        g_thread_init(NULL);   
    //新建一個GMainContext   
    context = g_main_context_new();   
    //然後把GSource附到這個Context上   
    add_source(context);   
    //把Context賦給GMainLoop   
    loop = g_main_loop_new(context, FALSE);   
    g_print("input string('q' to quit)\n");   
    g_main_loop_run(loop);   
    g_main_loop_unref(loop);   
    //Context用完計數器減1   
    g_main_context_unref(context);   
    return 0;   
}

 

4. Child Watch——自定義事件源

GSource * g_source_new(GSourceFuncs * source_funcs, guint struct_size);

這個函數用於創建一個自定義事件源,新的事件源可以使用 g_source_attach() 函數加入到主循環上下文中。
source_funcs : 包含用於實現事件行爲的函數的結構
struct_size : 創建的 GSource 結構大小,不能小於 sizeof(GSource)
返回值 : 返回新創建的 GSource

創建一個新的事件源包含用於實現事件行爲的函數的結構體。
prepare : 設置檢查事件時間超時。如果返回 TRUE, check 會立刻被調用;如果返回 FALSE 並設置了 timeout , timeout 時間後 check 會被調用。
check : 檢查事件是否準備完畢。返回 TRUE 爲準備完畢, dispatch 會被立刻調用;返回 FALSE 不調用 dispatch,進入下一次事件循環。
dispatch : 分發事件。返回 TRUE 將繼續下一次操作循環;返回 FALSE 中止本事件源的事件循環。
finalize : 當事件源被移除時被調用。

#include <glib.h>

gboolean source_prepare_cb(GSource * source,
            gint * timeout)
{
    g_printf("prepare\n");
    *timeout = 1000;
    return FALSE;
}

gboolean source_check_cb(GSource * source)
{
    g_printf("check\n");
    return TRUE;
}

gboolean source_dispatch_cb(GSource * source,
            GSourceFunc callback, gpointer data)
{
    g_printf("dispatch\n");
    return TRUE;
}

void source_finalize_cb(GSource * source)
{
    g_printf("finalize\n");
}

int main(int argc, char * argv[])
{
    GMainLoop * mainloop;
    GMainContext * maincontext;
    GSource * source;
    GSourceFuncs sourcefuncs;

    sourcefuncs.prepare = source_prepare_cb;
    sourcefuncs.check = source_check_cb;
    sourcefuncs.dispatch = source_dispatch_cb;
    sourcefuncs.finalize = source_finalize_cb;

    mainloop = g_main_loop_new(NULL, FALSE);
    maincontext = g_main_loop_get_context(mainloop);
    source = g_source_new(&sourcefuncs, sizeof(GSource));
    g_source_attach(source, maincontext);

    g_main_loop_run(mainloop);

    return 0;
}

信號

 信號註冊函數:
 gulong g_signal_connect(
            gpointer instance,
            const gchar *detailed_signal,
            GCallback c_handler,
            gpointer data );

nstance:信號發出者,可以認爲我們操作的控件,如按下按鈕,這個就爲按鈕指針
detailed_signal:信號標誌,如"pressed"
c_handler:回調函數的名稱,需要用G_CALLBACK()進行轉換
data:給回調函數傳的參數,gpointer 相當於C語言的 void *
返回值:註冊函數的標誌
如:
g_signal_connect(button, "pressed",G_CALLBACK(callback), NULL);
當按下button按鈕時,就會自動調用回調函數callback(相當於處理中斷任務),回調函數callback可以是任意函數,函數名字我們根據需要自行命名,如果不是庫函數,我們還得定義這個回調函數。
GSignal是GStreamer的一個重要部分。它會讓你在你感興趣的事情發生時收到通知。信號是通過名字來區分的,每個GObject都有它自己的信號。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章