Gobject C语言库 VI 信号通信

GObject库有一个重要的功能,信号通信。 面向语言的基础功能是没有类似功能的,GUI库才会看到消息通知的功能。
在对象与对象通信的方式中,通常有这么几种:

  • A对象在自己的方法中调用 B对象的方法
    这种方式A和B通过方法紧密的关联着。
  • A 对象在方法里调用一个或多个传递的回调函数,回调函数中包含B或其它对象的信息。
    这种方式虽然有一定隔离性,但是也会受到回调函数个数的限制,并且使用起来不是很直观。
  • A 在执行的方法中发送一个消息给系统,系统根据注册的消息处理器,传递给消息处理器执行。

GObject 正是第三种方法,这样A不需要知道B,它仅需要在需要发消息的地方,将消息传送出去。 消息的处理器通过注册函数来完成,所以也没有个数的限制。使用Gobject的信号通信机制通常包含4个步骤:

  • 注册信号, 一个信号来源于一个对象,信号的注册应该在类的初始化函数中完成。
  • 编写信号处理器。 当信号发生后,一个信号处理器可以处理这个消息。
  • 连接信号和信号处理器。 这样处系统就知道某个信号应该被哪个处理器处理了。
  • 发射信号。 系统会处理信后的接受,并调用相应的一个或多个处理器进行处理。

信号的注册
我们通常使用g_signal_newv(), g_signal_new_valist() or g_signal_new() 三个函数来注册处理器,它们需要在产生信号的类型的类初始化方法中完成:

guint
g_signal_newv (const gchar        *signal_name,   //一个用于唯一标志这个信号的字符串
               GType               itype,                            // 可以产生这个消息的实例类型
               GSignalFlags        signal_flags,           //用于定义各种处理器被执行顺序的标志符
               GClosure           *class_closure,          //default 处理器,一个对象可以提供一个默认处理器,它回合用户定义处理器一起被执行
               GSignalAccumulator  accumulator,    // 所有处理器被激活后调用的函数
               gpointer            accu_data,                // 传给accumulator的数据
               GSignalCMarshaller  c_marshaller,   //  默认的C marshaller
               GType               return_type,             //  信号的返回类型
               guint               n_params,                  // 信号传递的参数的个数
               GType              *param_types);        //  一个Gtype数组,用来描述每一个参数的类型

从上面的定义来看,消息的注册实际就是描述一个消息和消息处理器应该遵从的要求。
我们在VedioMedia里创建一个Signal : FORMAT_CHANGED 通知感兴趣的处理器VedioMedia的媒体格式变更了,现在我们需要做以下的事情:

  1. 定义这个信号: 信号的名称 返回类型 传递的参数及类型
    我们声明一个字符串常量作为信号的名称(#define FORMAT_CHANGED "format_changed");为了简便,我们定义返回类型为void, 传递参数为0.
  2. 在class_init函数中注册这个信号
static guint format_change;

static void
vcam_vediomedia_class_init(VcamVedioMediaClass * kclass) {
    //注册私有属性
    g_type_class_add_private(kclass, sizeof(VcamVedioMediaPriv));

    /* override base object methods */
    GObjectClass* base_class = G_OBJECT_CLASS(kclass);
    base_class->set_property = vcam_vediomedia_set_property;
    base_class->get_property = vcam_vediomedia_get_property;
    /* install properties */
    g_object_class_install_property(base_class, VCAM_MEDIA_FORMAT,
        g_param_spec_object("format", "vedio format", "desscribe the vedio format", VCAM_TYPE_VEDIOMEDIA,G_PARAM_READWRITE));

    format_change = g_signal_new(FORMAT_CHANGED,
                                          G_TYPE_FROM_CLASS(kclass),
                                          G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                                          0 /* class offset.Subclass cannot override the class handler (default handler). */,
                                          NULL /* accumulator */,
                                          NULL /* accumulator data */,
                                          NULL /* C marshaller. g_cclosure_marshal_generic() will be used */,
                                          G_TYPE_NONE /* return_type */,
                                          0     /* n_params */
                                          );
}

这里我们换用了g_signal_new函数,g_signal_new的第三个函数为默认处理器在类型中的offset, 我们设置为0即可。

发射信号
当我们在VcamVedioMedia类型中注册了一个信号,他的名字是“format_changed”, 标识档期媒体实例的格式信息发生了变化。通常我们希望在发生变化后通知感兴趣的处理器来处理,所以这个消息将在引起格式信息变化的函数里被发送出去:
我们在VcamVedioMediaClass中新定义一个setfmt的函数指针,以及一个帮助方法:

#ifndef VCAM_VEDIOMEDIA_H
#define VCAM_VEDIOMEDIA_H

#include <glib-object.h>

#define VCAM_TYPE_VEDIOMEDIA (vcam_vediomedia_get_type())
#define FORMAT_CHANGED  "format_changed"
G_DECLARE_DERIVABLE_TYPE(VcamVedioMedia, vcam_vediomedia, VCAM, VEDIOMEDIA, GObject)

typedef struct _VcamVedioMediaPriv VcamVedioMediaPriv;
struct  _VcamVedioMedia {
    GObject parent_instance;
    VcamVedioMediaPriv priv;
    void (*setfmt)(Format *fmtptr)
};


struct _VcamVedioMediaClass {
    GObjectClass parent_class;


};

void vcam_vediomedia_setfmt(VcamVedioMedia* self, Format* fmtptr);
#endif /* __VCAM_VEDIOMEDIA_H__ */

同时在.c文件中绑定setfmt的实际执行方法,实现vcam_vediomedia_setfmt逻辑,并在该逻辑中发送格式改变的信号:

//默认的修改格式方法
void setfmt_default(Format* fmtptr) {
  //set format
}
//修改格式后,发送信号
void vcam_vediomedia_setfmt(VcamVedioMedia* self, Format* fmtptr) {

    VcamVedioMediaClass* klass;

    g_return_if_fail(VCAM_IS_VEDIOMEDIA(self));

    klass = VCAM_SOURCE_GET_CLASS(self);
    g_return_if_fail(klass->setfmt != NULL);

    klass->setfmt(self, fmtptr);
    //发送信号
    g_signal_emit(self, format_change, 0);

}

static void
vcam_vediomedia_class_init(VcamVedioMediaClass * kclass) {
    //注册私有属性
    g_type_class_add_private(kclass, sizeof(VcamVedioMediaPriv));
    kclass->setfmt = setfmt_default;
    /* override base object methods */
    GObjectClass* base_class = G_OBJECT_CLASS(kclass);
    base_class->set_property = vcam_vediomedia_set_property;
    base_class->get_property = vcam_vediomedia_get_property;
    /* install properties */
    g_object_class_install_property(base_class, VCAM_MEDIA_FORMAT,
        g_param_spec_object("format", "vedio format", "desscribe the vedio format", VCAM_TYPE_VEDIOMEDIA,G_PARAM_READWRITE));

    format_change = g_signal_new(FORMAT_CHANGED,
                                          G_TYPE_FROM_CLASS(kclass),
                                          G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                                          0 /* class offset.Subclass cannot override the class handler (default handler). */,
                                          NULL /* accumulator */,
                                          NULL /* accumulator data */,
                                          NULL /* C marshaller. g_cclosure_marshal_generic() will be used */,
                                          G_TYPE_NONE /* return_type */,
                                          0     /* n_params */
                                          );


}

在发送消息中我们使用了g_signal_emit,他又三个参数:
第一个参数是发送当前信号的实例
第二个参数是信号的id,当前信号把存在format_change变量里(static guint format_change;

另外一个使用g_signal_emit_by_name等发送信号。

信号处理器
我们有一个类型叫做VcamSource, 它拥有一个VcamVedioMedia的属性,当VcamVedioMedia的格式放生变更时,VcamSource需要做一些事情。
信号处理器一般有两个参数:

  • 第一个参数标识发送信号的对象
  • 第二参数是一个指向用户数据(注册信号时传递的)的指针。
    如果信号本身有回传参数,这些参数需要在第一个和第二个参数之间,例如下面的处理器response_id就是信号传给处理器的参数。
    void user_function (GtkDialog *dialog, int response_id, gpointer user_data);
    我们在VcamVedioMedia的.c文件中定义这个处理器:
  void
     div_by_zero_cb(VcamVedioMedia * d, gpointer user_data) {
     g_print("\nthis is a signal handler\n\n");
 }

我们在连接的时候如果不传用户数据,第二个参数就可以省略掉

信号连接:
在实例化VcamVedioMedia的时候,我们将创建Format 对象,所以在哪个是偶,我会将这个format对象的消息与处理器连接:

 static void
vcam_source_init(VcamSource *d) {
      vedio = g_object_new(VCAM_TYPE_VEDIOMEDIA,NULL);
      g_signal_connect(vedio, FORMAT_CHANGED, G_CALLBACK(media_format_changed), NULL);

}

g_signal_connect有四个参数:
vedio 时发送信号的实例,也就是这个连接的源头.
FORMAT_CHANGED 时vediomedia.h文件中定义的字符串,标识信号的名称。
第三个参数media_format_changed就是信号处理器,注册时需要被映射为G_CALLBACK.
第四个参数是用户数据,可以传递给信号,null标识不需要用户数据。

这样利用信号通信来响应vediomedia格式变化的功能就实现了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容