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的媒体格式变更了,现在我们需要做以下的事情:
- 定义这个信号: 信号的名称 返回类型 传递的参数及类型
我们声明一个字符串常量作为信号的名称(#define FORMAT_CHANGED "format_changed");为了简便,我们定义返回类型为void, 传递参数为0. - 在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格式变化的功能就实现了。
网友评论