『从双向链表的设计开始』彷佛将问题搞的有些复杂了。可能由于它是 4 年前写的,那时我喜欢将简单的问题复杂化……这篇文章尝试将 GObject 与 C++ 代码作一次『映射』,借助 C++ 来理解 GObject 的基本编程框架,而后借助代码生成器保护咱们的手指。ios
先观察如下 C++ 代码:c++
#include <iostream> class MyObject { public: MyObject() {std::cout << "对象初始化" << std::endl;} }; int main() { MyObject my_obj; return 0; }
只要具有一点 C++ 类的知识,上述 C++ 代码应该不难理解。下面用 GObject 对其进行逐步模拟。编程
如下 C++ 代码segmentfault
class MyObject;
用 GObject 可表述为:api
#include <glib-object.h> typedef struct _MyObjectClass { GObjectClass parent_class; } MyObjectClass;
也就是说,GObject 中的『类结构体』的做用是面向 GObject 类型系统声明一个『类』的类型。bash
须要注意一点,C++ 中的类能够不须要从其余类派生,而 GObject 的『类结构体』一般须要从一个叫作 GObjectClass
的『类结构体』派生而成。框架
如下 C++ 代码编程语言
class MyObject { };
用 GObject 可表述为:函数
#include <glib-object.h> typedef struct _MyObject{ GObject parent_instance; } MyObject; typedef struct _MyObjectClass { GObjectClass parent_class; } MyObjectClass; G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);
这就是说,GObject 中的『实例结构体』模拟的是 C++ 中的『类』类型的定义。ui
须要注意一点,C++ 中的类能够不须要从其余类派生,而 GObject 中的类,其『实例结构体』一般须要从一个叫作 GObject
的『实例结构体』派生而成。
C++ 中的数据类型是静态的,在代码编译阶段由被编译器肯定。尽管 C++ 提供了 RTTI(运行时类型识别),可是 RTTI 依然是 C++ 编译器实现的。
Python、Ruby、Lua 之类的动态语言,它们的数据类型是由解释器肯定的。
GObject 中的数据类型是动态的,在程序运行阶段由 GObject 类型系统肯定。若是对上一节的 GObject 代码仔细推敲,不难发现其数据类型是动态的,即『类』的类型是在程序运行时在 GObject 类型系统中注册的。
看下面的代码:
G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);
若是使用
$ gcc $(pkg-config --cflags gobject-2.0) -E your-source.c
那么即可将 G_DEFINE_TYPE
展开为下面的 C 代码:
static void my_object_init(MyObject * self); static void my_object_class_init(MyObjectClass * klass); static gpointer my_object_parent_class = ((void *) 0); static gint MyObject_private_offset; static void my_object_class_intern_init(gpointer klass) { my_object_parent_class = g_type_class_peek_parent(klass); if (MyObject_private_offset != 0) g_type_class_adjust_private_offset(klass, &MyObject_private_offset); my_object_class_init((MyObjectClass *) klass); } __attribute__ ((__unused__)) static inline gpointer my_object_get_instance_private(const MyObject * self) { return (((gpointer) ((guint8 *) (self) + (glong) (MyObject_private_offset)))); } GType my_object_get_type(void) { static volatile gsize g_define_type_id__volatile = 0; if (g_once_init_enter(&g_define_type_id__volatile)) { GType g_define_type_id = g_type_register_static_simple(((GType) ((20) << (2))), g_intern_static_string("MyObject"), sizeof(MyObjectClass), (GClassInitFunc) my_object_class_intern_init, sizeof(MyObject), (GInstanceInitFunc) my_object_init, (GTypeFlags) 0); } return g_define_type_id__volatile; };
上述代码中的 my_object_get_type
函数的定义被我简化了一下,只保留其大意,主要是由于实际的代码难以卒读。
GObject 类型系统之因此可以接受 MyObject 这个『类』的类型,彻底拜 my_object_get_type
函数所赐。由于 my_object_get_type
函数调用了 g_type_register_static_simple
函数,后者由 GObject 类型系统提供,其主要职责就是为 GObject 类型系统扩充人马。my_object_get_type
向 g_type_register_static_simple
汇报:『我手里有个 MyObject 类,它由 GObject 类派生,它的姓名、三围、籍贯、民族分别为 ……@#$%^&*(……blab……blab……
』,而后 g_type_register_static_simple
就为 MyObject 登记造册,今后 GObject 类型系统中就有了 MyObject 这号人物了。
my_object_get_type
函数的定义利用了 static
变量实现了如下功能:
my_object_get_type
第一次被调用时,会向 GObject 类型系统注册 MyObject 类型,而后对 MyOject 类进行实例化,产生对象,最后返回对象的数据类型的 ID;
从 my_object_get_type
第二次被调用开始,它就只进行 MyOject 类的实例化,不会再重复向 GObject 类型系统注册类型。
那么 my_object_get_type
会被谁调用?它会被 g_object_new
调用,全部 GObject 类派生的类型,皆可由 g_object_new
函数进行实例化,例如:
MyObject *my_obj = g_object_new(my_object_get_type(), NULL);
死啦死啦说,不拉屎会憋死咱们,不吃饭活七八天,不喝水活五六天,不睡觉活四五天,杂事养咱们也要咱们的命……因此 G_DEFINE_TYPE
宏就出现了,它悄悄的执行着这些杂事……因此,C++ 们就出现了,class
们悄悄的执行着这些杂事……
除了向 GObject 类型注册新类型的相关信息,G_DEFINE_TYPE
宏还为咱们声明了『类』的类型与『类』的实例的初始化函数:
static void my_object_init(MyObject * self); static void my_object_class_init(MyObjectClass * klass);
my_object_class_init
是『类』的类型初始化函数,它的做用是使得用户可以在『类』的类型初始化阶段插入一些本身须要的功能。这个函数是任何一种支持面向对象的编程语言都不须要的,由于这些语言的编译器(解释器)认为用户没有必要在『类』这种类型的初始化阶段执行本身的一些任务。可是 GObject 的类型管理系统须要这个东西,由于在这个阶段,用户能够『类』的类结构体(例如 MyObjectClas
)中的数据进行一些符合本身需求的修改。请记住这一点,由于很快就能够看到咱们有必要去修改『类』的类结构体。
my_object_init
是『类』的实例的初始化函数,能够将它理解为 C++ 对象的构造函数。
如下 C+ 代码,
#include <iostream> class MyObject { public: MyObject() {std::cout << "对象初始化" << std::endl;} };
用 GObject 可描述为:
#include <stdio.h> #include <glib-object.h> typedef struct _MyObject{ GObject parent_instance; } MyObject; typedef struct _MyObjectClass { GObjectClass parent_class; } MyObjectClass; G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT); static void my_object_class_init(MyObjectClass * klass) { } static void my_object_init(MyObject * self) { printf("对象初始化\n"); }
C++ 的对象除了有构造函数,还有析构函数。GObject 也有析构函数,可是这篇文章先放它一马,由于这是一个很是有趣的主题,最好是为它单独写一篇文章。
咱们的程序已经在 GObject 数据类型系统中创造出来 MyObject 类型,那么用这种类型就能够产生无数个实例,它们就是类型为 MyObject 的对象。一个程序在运行时创造了一个 MyObject 类型的对象,那么这个程序知道这个对象的类型是 MyObject 类型,知道它所属的 MyObject 类型是派生自 GObject 类型么?这就是所谓的『RTTI(运行时类型识别)』,有的语言则将其称为『自省』。
事实上如今并无任何一个程序可以具有自省能力,咱们人类自身也没法回答『我是谁』这些的哲学问题。『我是谁』此类问题的答案,只有创造『我』的家伙才知道。
GObject 类型系统创造了『类』的类型,而后由『类』的类型创造出对象。所以,它知道任何一个它由所创造的对象的类型。例如,对于任何由 GObject 类派生出来的类,它的对象可以使得如下条件成立:
MyObject *my_obj = g_object_new(MY_TYPE_OBJECT, NULL); if (G_IS_OBJECT(my_obj)) { printf("My type is GObject!\n"); }
也能够检查 my_obj
的类型是否为 MyObject 类:
if (G_TYPE_CHECK_INSTANCE_TYPE(my_obj, my_object_get_type())) { printf("My type is MyObject!\n"); }
为了方便,能够为 MyObject 类定义一个与 G_IS_OBJECT
类似的宏:
#define MY_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE(my_obj, my_object_get_type()))
而后,能够像下面这样检查 my_obj
的类型:
if (MY_IS_OBJECT(my_obj)) { printf("My type is MyObject!\n"); }
虽然 GObject 的代码看上去较为繁琐,可是这些代码具备必定的模式。为了节省字符输入,能够考虑为它写一个代码生成器。
我在 Emacs 里写了一个简单的生成器:
(defun make-gobject-h (project class parent) "Insert some macroes about gobject" (interactive "sWhat's the project name? \nsWhat's the class name? \nsWho's the parent of this class? ") (let ((P (upcase project)) (O (upcase class)) (p (downcase project)) (o (downcase class)) (pp (capitalize project)) (oo (capitalize class))) (insert (format "#ifndef %s_%s_HEAD\n" P O)) (insert (format "#define %s_%s_HEAD\n\n" P O)) (insert (format "#include <glib-object.h>\n\n")) (insert (format "#define %s_TYPE_%s (%s_%s_get_type ())\n" P O p o)) (insert (format "#define %s_%s(object) (G_TYPE_CHECK_INSTANCE_CAST((object), %s_TYPE_%s, %s%s))\n" P O P O pp oo)) (insert (format "#define %s_%s_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), %s_TYPE_%s, %s%sClass))\n" P O P O pp oo)) (insert (format "#define %s_IS_%s(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), %s_TYPE_%s))\n" P O P O)) (insert (format "#define %s_IS_%s_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), %s_TYPE_%s))\n" P O P O)) (insert (format "#define %s_%s_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), %s_TYPE_%s, %s%sClass))\n\n" P O P O pp oo)) (insert (format "typedef struct _%s%s %s%s;\n" pp oo pp oo)) (insert (format "typedef struct _%s%s {\n" pp oo)) (insert (format "\t%s parent_instance;\n" parent)) (insert (format "};\n")) (insert (format "typedef struct _%s%sClass %s%sClass;\n" pp oo pp oo)) (insert (format "typedef struct _%s%sClass {\n" pp oo)) (insert (format "\t%sClass parent_class;\n" parent)) (insert (format "};\n\n")) (insert (format "GType %s_%s_get_type(void);\n\n" p o )) (insert (format "#endif\n"))))
这个生成器,在 Emacs 里的用法是执行 M-x make-gobject-h
命令,而后像下面这样回答三个问题:
Q:What's the project name? A:My Q:What's the class name? A:Object Q:Who's the parent of this class? A:GObject
而后便可在 Emacs 当前缓冲区中生成如下代码:
#ifndef MY_OBJECT_HEAD #define MY_OBJECT_HEAD #include <glib-object.h> #define MY_TYPE_OBJECT (my_object_get_type ()) #define MY_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST((object), MY_TYPE_OBJECT, MyObject)) #define MY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), MY_TYPE_OBJECT, MyObjectClass)) #define MY_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), MY_TYPE_OBJECT)) #define MY_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MY_TYPE_OBJECT)) #define MY_OBJECT_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), MY_TYPE_OBJECT, MyObjectClass)) typedef struct _MyObject MyObject; typedef struct _MyObject { GObject parent_instance; }; typedef struct _MyObjectClass MyObjectClass; typedef struct _MyObjectClass { GObjectClass parent_class; }; GType my_object_get_type(void); #endif
上述代码,能够保存为 my-object.h 文件,而后再新建一份 my-object.c 文件,在其中放置如下内容:
#include "my-object.h" G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT); static void my_object_init(MyObject *self) { printf("对象初始化\n"); } static void my_object_class_init(MyObjectClass *klass) { }
这样就完成了一个完整的 GObject 版本 MyObject 类的定义。
my-object.c 中的代码,也能够写一个代码生成器来生成:
(defun make-gobject-c (project class parent-type) "Insert some macroes about gobject" (interactive "sWhat's the project name? \nsWhat's the class name? \nsWhat's the parent type of this class? ") (let ((p (downcase project)) (o (downcase class)) (pp (capitalize project)) (oo (capitalize class))) (insert (format "#include \"%s-%s.h\"\n\n" p o)) (insert (format "G_DEFINE_TYPE(%s%s, %s_%s, %s)\n\n" pp oo p o parent-type)) (insert (format "static void\n")) (insert (format "%s_%s_init(%s%s *self)\n{\n\n}\n\n" p o pp oo)) (insert (format "static void\n")) (insert (format "%s_%s_class_init(%s%sClass *klass)\n{\n\n}\n\n" p o pp oo))))
在 Emacs 里执行 M-x make-gobject-c
命令,而后像下面这样回答三个问题:
Q:What's the project name? A:My Q:What's the class name? A:Object Q:What's the parent type of this class? A:G_TYPE_OBJECT
而后便可生成上述的 my-object.c 文件中的主要内容。
GObject 的数据类型系统最大的特色是数据类型的动态性,虽然是不得已而为之,可是却别有一番情趣。基于这种数据类型系统,咱们可以在程序的运行过程当中创造新的数据类型,可以由一种数据类型派生出新的数据类型,还可以识别对象的类型。
虽然 GObject 的这一切看上去很是繁琐,可是经过代码生成器就能够明白,若是遵照一些约定,繁琐的代码能够基于 4 个参数生成。
GObject 的类型系统既然是动态的,它支持程序在运行中生成数据类型,也支持程序在运行中销毁数据类型。有关数据类型的销毁,这个功能比较偏僻,通常状况下用不着,若对这个主题感兴趣,可参考:http://www.gonwan.com/?p=50