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
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    
#include    
#include    
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

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都有它自己的信号。

你可能感兴趣的:(glib)