看看GtkWindow如何被生出来的(gtk_window_new()的内部机制).
2009-06-05 17:42
309 查看
看看GtkWindow如何被生出来的.
用Glib/GObject/Gtk也有很长时间了,没有时间往下刨根,现在刨一刨。
1 定义GtkWindow.. 2
2 展开它:... 2
在gtype.h中定义如下宏:... 2
把定义的宏展开:... 3
再把GtkBin的定义展开:... 4
再展开GtkContainer的定义://不用展开,直接定义的... 5
GtkWidget的get_type()也是直接定义的... 6
GtkObject的get_type()也是直接定义的:... 7
看看GObject的定义:gtk_init()后,这个对象是已经建立了... 8
3 在用的时候,是直接调用一个宏:... 8
下面我们来跟着一下其流程:(步步跟踪其父类,如果父类还没有创建,则先创建父类) 8
Step 1: 程序会调用这个接口:... 8
Step 2: 接着会调用 (层层上访) 9
Step 3:所要先看GtkObject的创立过程:... 9
GtkObject对象存在了,那就看它的子类吧:GtkWidget 10
接着往下搞:GtkWidget对象也有了,在往下看子类:GtkContainer 11
Go on: GtkBin. 12
GtkBin创建后,GtkWindow对象也就创建了;... 13
这样一系列的对象存在如下:... 13
上面仅仅是一些对象,那这些对象携带的回调函数都是从哪里调用的呢?... 14
关键之处是g_object_new()里面调用的函数... 14
重点看看g_object_newv:... 14
g_type_class_ref这个就是核心:递归调用... 17
上面的递归调用会调用函数type_class_init_Wm()完成对某个类的初始化,即,分配内存同时调用class_init() 19
Base_class_init()调用的顺序:... 23
1. g_object_base_class_init 23
2 gtk_object_base_class_init 23
3 gtk_widget_base_class_init 是NULL所以不调用... 23
4 gtk_container_base_class_init 24
5 gtk_bin, gtk_windonw的base_class_init()都是NULL. 24
下面开始class_init: 24
class_init() 之前有个递归过程(图示)... 24
下面用运行代码来检查实际的过程:... 25
条件:... 25
结果:... 25
对象的出生过程:老子先出生,然后是儿子,然后是孙子…; 自然界的规律
不过这里是先提出要孙子,谁提呢?如来佛吧!
然后如来就查查这个人的父亲是谁?已经出生了吗?没有再往上找;…
这些对象都有些本领(回调函数),一般情况下,也是先把老子的钱榨干,然后用儿子的,然后用…
注意:如果某个类在递归过程中检查是已经被初始化过的类,则跳过其class_init()函数。
用Glib/GObject/Gtk也有很长时间了,没有时间往下刨根,现在刨一刨。
1 定义GtkWindow.. 2
2 展开它:... 2
在gtype.h中定义如下宏:... 2
把定义的宏展开:... 3
再把GtkBin的定义展开:... 4
再展开GtkContainer的定义://不用展开,直接定义的... 5
GtkWidget的get_type()也是直接定义的... 6
GtkObject的get_type()也是直接定义的:... 7
看看GObject的定义:gtk_init()后,这个对象是已经建立了... 8
3 在用的时候,是直接调用一个宏:... 8
下面我们来跟着一下其流程:(步步跟踪其父类,如果父类还没有创建,则先创建父类) 8
Step 1: 程序会调用这个接口:... 8
Step 2: 接着会调用 (层层上访) 9
Step 3:所要先看GtkObject的创立过程:... 9
GtkObject对象存在了,那就看它的子类吧:GtkWidget 10
接着往下搞:GtkWidget对象也有了,在往下看子类:GtkContainer 11
Go on: GtkBin. 12
GtkBin创建后,GtkWindow对象也就创建了;... 13
这样一系列的对象存在如下:... 13
上面仅仅是一些对象,那这些对象携带的回调函数都是从哪里调用的呢?... 14
关键之处是g_object_new()里面调用的函数... 14
重点看看g_object_newv:... 14
g_type_class_ref这个就是核心:递归调用... 17
上面的递归调用会调用函数type_class_init_Wm()完成对某个类的初始化,即,分配内存同时调用class_init() 19
Base_class_init()调用的顺序:... 23
1. g_object_base_class_init 23
2 gtk_object_base_class_init 23
3 gtk_widget_base_class_init 是NULL所以不调用... 23
4 gtk_container_base_class_init 24
5 gtk_bin, gtk_windonw的base_class_init()都是NULL. 24
下面开始class_init: 24
class_init() 之前有个递归过程(图示)... 24
下面用运行代码来检查实际的过程:... 25
条件:... 25
结果:... 25
1 定义GtkWindow
G_DEFINE_TYPE_WITH_CODE (GtkWindow, gtk_window, GTK_TYPE_BIN, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_window_buildable_interface_init)) |
2 展开它:
在gtype.h中定义如下宏:
#define G_DEFINE_TYPE_WITH_CODE(TN, t_n, T_P, _C_) _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, 0) {_C_;} _G_DEFINE_TYPE_EXTENDED_END() #define _G_DEFINE_TYPE_EXTENDED_BEGIN(TypeName, type_name, TYPE_PARENT, flags) / / static void type_name##_init (TypeName *self); / static void type_name##_class_init (TypeName##Class *klass); / static gpointer type_name##_parent_class = NULL; / static void type_name##_class_intern_init (gpointer klass) / { / type_name##_parent_class = g_type_class_peek_parent (klass); / type_name##_class_init ((TypeName##Class*) klass); / } / / GType / type_name##_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 (TYPE_PARENT, / g_intern_static_string (#TypeName), / sizeof (TypeName##Class), / (GClassInitFunc) type_name##_class_intern_init, / sizeof (TypeName), / (GInstanceInitFunc) type_name##_init, / (GTypeFlags) flags); / { /* custom code follows */ #define _G_DEFINE_TYPE_EXTENDED_END() / /* following custom code */ / } / g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); / } / return g_define_type_id__volatile; / } /* closes type_name##_get_type() */ |
把定义的宏展开:
static void gtk_window_init (GtkWindow *self); static void gtk_window_class_init (GtkWindowClass * klass); static void gtk_window_parent_class = NULL; static void gtk_window_class_intern_init (gpointer klass) { gtk_window_parent_class = g_type_class_peek_parent (klass); gtk_window_class_init ((GtkWindowClass*) klass); } GType gtk_window_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 (GTK_TYPE_BIN, //父亲 g_intern_static_string ("GtkWindow"), sizeof (GtkWindowClass), (GClassInitFunc) gtk_window_class_intern_init, sizeof (GtkWindow), (GInstanceInitFunc) gtk_window_init, 0); { const GInterfaceInfo g_implement_interface = { (GInterfaceInitFunc) gtk_window_buildable_interface_init, NULL, NULL }; g_type_add_interface_static (g_define_type_id, GTK_TYPE_BUILDABLE, &g_implement_interface); } g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); } return g_define_type_id__volatile; } |
再把GtkBin的定义展开:
G_DEFINE_ABSTRACT_TYPE (GtkBin, gtk_bin, GTK_TYPE_CONTAINER)static void gtk_bin_init (GtkBin *self); static void gtk_bin_class_init (GtkBinClass * klass); static void gtk_bin_parent_class = NULL; static void gtk_bin_class_intern_init (gpointer klass) { gtk_bin_parent_class = g_type_class_peek_parent (klass); gtk_bin_class_init ((GtkBinClass*) klass); } GType gtk_bin_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 (GTK_TYPE_CONTAINER, //GtkBin的父亲 g_intern_static_string ("GtkBin"), sizeof (GtkBinClass), (GClassInitFunc) gtk_bin_class_intern_init, sizeof (GtkBin), (GInstanceInitFunc) gtk_bin_init, G_TYPE_FLAG_ABSTRACT); { } g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); } return g_define_type_id__volatile; } |
再展开GtkContainer的定义://不用展开,直接定义的
#define GTK_TYPE_CONTAINER (gtk_container_get_type ())/* --- functions --- */ GType gtk_container_get_type (void) { static GType container_type = 0; if (!container_type) { const GTypeInfo container_info = { sizeof (GtkContainerClass), (GBaseInitFunc) gtk_container_base_class_init, (GBaseFinalizeFunc) gtk_container_base_class_finalize, (GClassInitFunc) gtk_container_class_init, NULL /* class_finalize */, NULL /* class_data */, sizeof (GtkContainer), 0 /* n_preallocs */, (GInstanceInitFunc) gtk_container_init, NULL, /* value_table */ }; static const GInterfaceInfo buildable_info = { (GInterfaceInitFunc) gtk_container_buildable_init, NULL, NULL }; container_type = g_type_register_static (GTK_TYPE_WIDGET, //GtkContainer的父亲 I_("GtkContainer"), &container_info, G_TYPE_FLAG_ABSTRACT); g_type_add_interface_static (container_type, GTK_TYPE_BUILDABLE, &buildable_info); } return container_type; } |
GtkWidget的get_type()也是直接定义的
/* --- functions --- */ GType gtk_widget_get_type (void) { static GType widget_type = 0; if (G_UNLIKELY (widget_type == 0)) { const GTypeInfo widget_info = { sizeof (GtkWidgetClass), NULL, /* base_init */ (GBaseFinalizeFunc) gtk_widget_base_class_finalize, (GClassInitFunc) gtk_widget_class_init, NULL, /* class_finalize */ NULL, /* class_init */ sizeof (GtkWidget), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_widget_init, NULL, /* value_table */ }; const GInterfaceInfo accessibility_info = { (GInterfaceInitFunc) gtk_widget_accessible_interface_init, (GInterfaceFinalizeFunc) NULL, NULL /* interface data */ }; const GInterfaceInfo buildable_info = { (GInterfaceInitFunc) gtk_widget_buildable_interface_init, (GInterfaceFinalizeFunc) NULL, NULL /* interface data */ }; widget_type = g_type_register_static (GTK_TYPE_OBJECT, //GtkWidget的父亲 "GtkWidget", &widget_info, G_TYPE_FLAG_ABSTRACT); g_type_add_interface_static (widget_type, ATK_TYPE_IMPLEMENTOR, &accessibility_info) ; g_type_add_interface_static (widget_type, GTK_TYPE_BUILDABLE, &buildable_info) ; } return widget_type; } |
GtkObject的get_type()也是直接定义的:
#define GTK_TYPE_OBJECT (gtk_object_get_type ()) GType gtk_object_get_type (void) { static GType object_type = 0; if (!object_type) { const GTypeInfo object_info = { sizeof (GtkObjectClass), (GBaseInitFunc) gtk_object_base_class_init, (GBaseFinalizeFunc) gtk_object_base_class_finalize, (GClassInitFunc) gtk_object_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkObject), 16, /* n_preallocs */ (GInstanceInitFunc) gtk_object_init, NULL, /* value_table */ }; object_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED, //GtkObject的父亲,就是GObject I_("GtkObject"), &object_info, G_TYPE_FLAG_ABSTRACT); } return object_type; } |
看看GObject的定义:gtk_init()后,这个对象是已经建立了
GType g_object_get_type (void) { return G_TYPE_OBJECT; } //GObject的回调函数什么时候被调用的? |
3 在用的时候,是直接调用一个宏:
#define GTK_TYPE_WINDOW (gtk_window_get_type ())下面我们来跟着一下其流程:(步步跟踪其父类,如果父类还没有创建,则先创建父类)
Step 1: 程序会调用这个接口:
GtkWidget* gtk_window_new (GtkWindowType type); 这个函数内部会调用 window = g_object_new (GTK_TYPE_WINDOW, NULL); |
Step 2: 接着会调用 (层层上访)
GType gtk_window_get_type (void) //这个函数内部又会找GtkWindows的父类:GtkBin GtkBin又找其父类GtkContainer, GtkContainer又找其父类GtkWidget GtkWidget又找其父类GtkObject GtkObject又找其父类Gobject, 这个类在gtk_init()后,就已经存在了; 所以在反过来进行 |
Step 3:所要先看GtkObject的创立过程:
GType gtk_object_get_type (void) { static GType object_type = 0; if (!object_type) { const GTypeInfo object_info = { sizeof (GtkObjectClass), (GBaseInitFunc) gtk_object_base_class_init, (GBaseFinalizeFunc) gtk_object_base_class_finalize, (GClassInitFunc) gtk_object_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkObject), 16, /* n_preallocs */ (GInstanceInitFunc) gtk_object_init, NULL, /* value_table */ }; //g_type_register_static就从GObject派生出来一个类GtkObject,并填充好以后,放到全局的数组中,以及Hash Table中,具体如下: //这个函数首先干的事情:检查名字是否合法以及在当前的Hash Table中是不是已经有了?然后再检查和指定的父类之间是否真的具有继承关系 //如果可以注册, //如果父类不存在,直接放到全局数组中,则先在static_fundamental_type_nodes[]数组中找个位置,来存放这个类型节点指针 //如果父类存在,则从父类拷贝一份内存出来,作为子类, 主要在type_node_any_new_W()中完成,并且把这个新的子类放到父类的一个子类数组中 // 然后把新类放到Hash Table中,这样方便查询 // 然后给类型数据填充一些字段,主要是注册一些回调函数,由type_data_make_W()来完成 //同时把ref_count置为1,表明这个对象活了! object_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED, I_("GtkObject"), &object_info, G_TYPE_FLAG_ABSTRACT); } return object_type; } //还有问题没有搞明白,注册的系列回调,何时调用的? |
GtkObject对象存在了,那就看它的子类吧:GtkWidget
/* --- functions --- */ GType gtk_widget_get_type (void) { static GType widget_type = 0; if (G_UNLIKELY (widget_type == 0)) { const GTypeInfo widget_info = { sizeof (GtkWidgetClass), NULL, /* base_init */ (GBaseFinalizeFunc) gtk_widget_base_class_finalize, (GClassInitFunc) gtk_widget_class_init, NULL, /* class_finalize */ NULL, /* class_init */ sizeof (GtkWidget), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_widget_init, NULL, /* value_table */ }; const GInterfaceInfo accessibility_info = { (GInterfaceInitFunc) gtk_widget_accessible_interface_init, (GInterfaceFinalizeFunc) NULL, NULL /* interface data */ }; const GInterfaceInfo buildable_info = { (GInterfaceInitFunc) gtk_widget_buildable_interface_init, (GInterfaceFinalizeFunc) NULL, NULL /* interface data */ }; //也是从GtkObject对象做一个内存拷贝,然后填充数据(注册回调),新创建的对象GtkWidget地址存放于GtkObject维护的一个子类列表里面;当然了,在Hash Table里面也是有的; widget_type = g_type_register_static (GTK_TYPE_OBJECT, "GtkWidget", &widget_info, G_TYPE_FLAG_ABSTRACT); g_type_add_interface_static (widget_type, ATK_TYPE_IMPLEMENTOR, &accessibility_info) ; g_type_add_interface_static (widget_type, GTK_TYPE_BUILDABLE, &buildable_info) ; } return widget_type; } |
接着往下搞:GtkWidget对象也有了,在往下看子类:GtkContainer
/* --- functions --- */ GType gtk_container_get_type (void) { static GType container_type = 0; if (!container_type) { const GTypeInfo container_info = { sizeof (GtkContainerClass), (GBaseInitFunc) gtk_container_base_class_init, (GBaseFinalizeFunc) gtk_container_base_class_finalize, (GClassInitFunc) gtk_container_class_init, NULL /* class_finalize */, NULL /* class_data */, sizeof (GtkContainer), 0 /* n_preallocs */, (GInstanceInitFunc) gtk_container_init, NULL, /* value_table */ }; static const GInterfaceInfo buildable_info = { (GInterfaceInitFunc) gtk_container_buildable_init, NULL, NULL }; //也是从GtkWidget对象做一个内存拷贝,然后填充数据(注册回调),新创建的对象GtkContainer地址存放于GtkWidget维护的一个子类列表里面;当然了,在Hash Table里面也是有的; container_type = g_type_register_static (GTK_TYPE_WIDGET, I_("GtkContainer"), &container_info, G_TYPE_FLAG_ABSTRACT); g_type_add_interface_static (container_type, GTK_TYPE_BUILDABLE, &buildable_info); } return container_type; } |
Go on: GtkBin
static void gtk_bin_init (GtkBin *self); static void gtk_bin_class_init (GtkBinClass * klass); static void gtk_bin_parent_class = NULL; static void gtk_bin_class_intern_init (gpointer klass) { gtk_bin_parent_class = g_type_class_peek_parent (klass); gtk_bin_class_init ((GtkBinClass*) klass); } GType gtk_bin_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 (GTK_TYPE_CONTAINER, g_intern_static_string ("GtkBin"), sizeof (GtkBinClass), (GClassInitFunc) gtk_bin_class_intern_init, sizeof (GtkBin), (GInstanceInitFunc) gtk_bin_init, G_TYPE_FLAG_ABSTRACT); { } g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); } return g_define_type_id__volatile; } |
GtkBin创建后,GtkWindow对象也就创建了;
这样一系列的对象存在如下:
| `GtkObject |
| | |
| `GtkWidget |
| | |
| `GtkContainer |
| | |
| +GtkBin |
| | | |
| | +GtkWindow |
不过这里是先提出要孙子,谁提呢?如来佛吧!
然后如来就查查这个人的父亲是谁?已经出生了吗?没有再往上找;…
这些对象都有些本领(回调函数),一般情况下,也是先把老子的钱榨干,然后用儿子的,然后用…
上面仅仅是一些对象,那这些对象携带的回调函数都是从哪里调用的呢?
关键之处是g_object_new()里面调用的函数
要仔细的分析!window = g_object_new (GTK_TYPE_WINDOW, NULL); |
重点看看g_object_newv:
gpointer g_object_newv (GType object_type, guint n_parameters, GParameter *parameters) { GObjectConstructParam *cparams, *oparams; GObjectNotifyQueue *nqueue = NULL; /* shouldn't be initialized, just to silence compiler */ GObject *object; GObjectClass *class, *unref_class = NULL; GSList *slist; guint n_total_cparams = 0, n_cparams = 0, n_oparams = 0, n_cvalues; GValue *cvalues; GList *clist = NULL; gboolean newly_constructed; guint i; g_return_val_if_fail (G_TYPE_IS_OBJECT (object_type), NULL); class = g_type_class_peek_static (object_type); if (!class) class = unref_class = g_type_class_ref (object_type); for (slist = class->construct_properties; slist; slist = slist->next) { clist = g_list_prepend (clist, slist->data); n_total_cparams += 1; } /* collect parameters, sort into construction and normal ones */ oparams = g_new (GObjectConstructParam, n_parameters); cparams = g_new (GObjectConstructParam, n_total_cparams); for (i = 0; i < n_parameters; i++) { GValue *value = ¶meters[i].value; GParamSpec *pspec = g_param_spec_pool_lookup (pspec_pool, parameters[i].name, object_type, TRUE); if (!pspec) { g_warning ("%s: object class `%s' has no property named `%s'", G_STRFUNC, g_type_name (object_type), parameters[i].name); continue; } if (!(pspec->flags & G_PARAM_WRITABLE)) { g_warning ("%s: property `%s' of object class `%s' is not writable", G_STRFUNC, pspec->name, g_type_name (object_type)); continue; } if (pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)) { GList *list = g_list_find (clist, pspec); if (!list) { g_warning ("%s: construct property /"%s/" for object `%s' can't be set twice", G_STRFUNC, pspec->name, g_type_name (object_type)); continue; } cparams[n_cparams].pspec = pspec; cparams[n_cparams].value = value; n_cparams++; if (!list->prev) clist = list->next; else list->prev->next = list->next; if (list->next) list->next->prev = list->prev; g_list_free_1 (list); } else { oparams[n_oparams].pspec = pspec; oparams[n_oparams].value = value; n_oparams++; } } /* set remaining construction properties to default values */ n_cvalues = n_total_cparams - n_cparams; cvalues = g_new (GValue, n_cvalues); while (clist) { GList *tmp = clist->next; GParamSpec *pspec = clist->data; GValue *value = cvalues + n_total_cparams - n_cparams - 1; value->g_type = 0; g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_param_value_set_default (pspec, value); cparams[n_cparams].pspec = pspec; cparams[n_cparams].value = value; n_cparams++; g_list_free_1 (clist); clist = tmp; } /* construct object from construction parameters */ object = class->constructor (object_type, n_total_cparams, cparams); /* free construction values */ g_free (cparams); while (n_cvalues--) g_value_unset (cvalues + n_cvalues); g_free (cvalues); /* adjust freeze_count according to g_object_init() and remaining properties */ G_LOCK (construction_mutex); newly_constructed = slist_maybe_remove (&construction_objects, object); G_UNLOCK (construction_mutex); if (newly_constructed || n_oparams) nqueue = g_object_notify_queue_freeze (object, &property_notify_context); if (newly_constructed) g_object_notify_queue_thaw (object, nqueue); /* run 'constructed' handler if there is one */ if (newly_constructed && class->constructed) class->constructed (object); /* set remaining properties */ for (i = 0; i < n_oparams; i++) object_set_property (object, oparams[i].pspec, oparams[i].value, nqueue); g_free (oparams); /* release our own freeze count and handle notifications */ if (newly_constructed || n_oparams) g_object_notify_queue_thaw (object, nqueue); if (unref_class) g_type_class_unref (unref_class); return object; } |
g_type_class_ref这个就是核心:递归调用
gpointer g_type_class_ref (GType type) { TypeNode *node; GType ptype; /* optimize for common code path */ G_WRITE_LOCK (&type_rw_lock); //如果当前类型对应的节点数据已经存在了,并且也别初始化过,则紧紧增加一个ref_count,然后返回。 node = lookup_type_node_I (type); if (node && node->is_classed && node->data && node->data->class.class && node->data->class.init_state == INITIALIZED) { type_data_ref_Wm (node); G_WRITE_UNLOCK (&type_rw_lock); return node->data->class.class; } if (!node || !node->is_classed || (node->data && node->data->common.ref_count < 1)) { G_WRITE_UNLOCK (&type_rw_lock); g_warning ("cannot retrieve class for invalid (unclassed) type `%s'", type_descriptive_name_I (type)); return NULL; } //给当前节点分配内存等 type_data_ref_Wm (node); //找到其父节点 ptype = NODE_PARENT_TYPE (node); G_WRITE_UNLOCK (&type_rw_lock); g_static_rec_mutex_lock (&class_init_rec_mutex); /* required locking order: 1) class_init_rec_mutex, 2) type_rw_lock */ /* here, we either have node->data->class.class == NULL, or a recursive * call to g_type_class_ref() with a partly initialized class, or * node->data->class.init_state == INITIALIZED, because any * concurrently running initialization was guarded by class_init_rec_mutex. */ if (!node->data->class.class) /* class uninitialized */ { /* acquire reference on parent class */ //这个地方是最重要的,会一直找到最上面的基类 GtkWindow-> GtkBin -> GtkContainer ->GtkWidget->GtkObject GTypeClass *pclass = ptype ? g_type_class_ref (ptype) : NULL; G_WRITE_LOCK (&type_rw_lock); if (node->data->class.class) /* class was initialized during parent class initialization? */ INVALID_RECURSION ("g_type_plugin_*", node->plugin, NODE_NAME (node)); //当上面的递归调用入口的边界条件(ptype == NULL, 即发现GObject没有父类后,就会,就不会再次调用g_type_class_ref了,这时候函数调用堆栈就会返回就从最后一个g_type_class_ref开始往下执行了,此时是GObject, 所以会从类的顶层开始调用其Class_init(); 这样就实现了从父类,到子类,再到孙子类的class_init()的过程。比较巧妙。 type_class_init_Wm (node, pclass); G_WRITE_UNLOCK (&type_rw_lock); } g_static_rec_mutex_unlock (&class_init_rec_mutex); return node->data->class.class; } |
上面的递归调用会调用函数type_class_init_Wm()完成对某个类的初始化,即,分配内存同时调用class_init()
static void type_class_init_Wm (TypeNode *node, GTypeClass *pclass) { GSList *slist, *init_slist = NULL; GTypeClass *class; IFaceEntry *entry; TypeNode *bnode, *pnode; guint i; g_assert (node->is_classed && node->data && node->data->class.class_size && !node->data->class.class && node->data->class.init_state == UNINITIALIZED); class = g_malloc0 (node->data->class.class_size); node->data->class.class = class; node->data->class.init_state = BASE_CLASS_INIT; if (pclass) { //找到GObjectClass对应的节点 TypeNode *pnode = lookup_type_node_I (pclass->g_type); //拷贝给子类GtkWidgetClass, 这就是继承? memcpy (class, pclass, pnode->data->class.class_size); if (node->is_instantiatable) { /* We need to initialize the private_size here rather than in * type_data_make_W() since the class init for the parent * class may have changed pnode->data->instance.private_size. */ node->data->instance.private_size = pnode->data->instance.private_size; } } class->g_type = NODE_TYPE (node); G_WRITE_UNLOCK (&type_rw_lock); /* stack all base class initialization functions, so we * call them in ascending order. */ //这个遍历是从GtkWindow-> GtkBin -> GtkContainer ->GtkWidget->GtkObject->GObject ,即从子到父亲的顺序; //而列举出的base_class_init()函数的列表正好是反的: GObject::class_init_base() à GtkObject::class_init_base()à GtkWidget::class_init_base()à GtkContainer::class_init_base()à GtkBin::class_init_base()à GtkWidow::class_init_base() //所以首先会调用基类的base_class_init()函数,然后依次进行下去 for (bnode = node; bnode; bnode = lookup_type_node_I (NODE_PARENT_TYPE (bnode))) if (bnode->data->class.class_init_base) init_slist = g_slist_prepend (init_slist, (gpointer) bnode->data->class.class_init_base); for (slist = init_slist; slist; slist = slist->next) { GBaseInitFunc class_init_base = (GBaseInitFunc) slist->data; class_init_base (class); } g_slist_free (init_slist); G_WRITE_LOCK (&type_rw_lock); node->data->class.init_state = BASE_IFACE_INIT; /* Before we initialize the class, base initialize all interfaces, either * from parent, or through our holder info */ pnode = lookup_type_node_I (NODE_PARENT_TYPE (node)); i = 0; while (i < CLASSED_NODE_N_IFACES (node)) { entry = &CLASSED_NODE_IFACES_ENTRIES (node)[i]; while (i < CLASSED_NODE_N_IFACES (node) && entry->init_state == IFACE_INIT) { entry++; i++; } if (i == CLASSED_NODE_N_IFACES (node)) break; if (!type_iface_vtable_base_init_Wm (lookup_type_node_I (entry->iface_type), node)) { guint j; /* need to get this interface from parent, type_iface_vtable_base_init_Wm() * doesn't modify write lock upon FALSE, so entry is still valid; */ g_assert (pnode != NULL); for (j = 0; j < CLASSED_NODE_N_IFACES (pnode); j++) { IFaceEntry *pentry = CLASSED_NODE_IFACES_ENTRIES (pnode) + j; if (pentry->iface_type == entry->iface_type) { entry->vtable = pentry->vtable; entry->init_state = INITIALIZED; break; } } g_assert (entry->vtable != NULL); } /* If the write lock was released, additional interface entries might * have been inserted into CLASSED_NODE_IFACES_ENTRIES (node); they'll * be base-initialized when inserted, so we don't have to worry that * we might miss them. Uninitialized entries can only be moved higher * when new ones are inserted. */ i++; } node->data->class.init_state = CLASS_INIT; G_WRITE_UNLOCK (&type_rw_lock); //这句话非常重要,开始class_init的过程了!!! //从gtk_window_class_init()开始? if (node->data->class.class_init) node->data->class.class_init (class, (gpointer) node->data->class.class_data); G_WRITE_LOCK (&type_rw_lock); node->data->class.init_state = IFACE_INIT; /* finish initializing the interfaces through our holder info. * inherited interfaces are already init_state == INITIALIZED, because * they either got setup in the above base_init loop, or during * class_init from within type_add_interface_Wm() for this or * an anchestor type. */ i = 0; while (TRUE) { entry = &CLASSED_NODE_IFACES_ENTRIES (node)[i]; while (i < CLASSED_NODE_N_IFACES (node) && entry->init_state == INITIALIZED) { entry++; i++; } if (i == CLASSED_NODE_N_IFACES (node)) break; type_iface_vtable_iface_init_Wm (lookup_type_node_I (entry->iface_type), node); /* As in the loop above, additional initialized entries might be inserted * if the write lock is released, but that's harmless because the entries * we need to initialize only move higher in the list. */ i++; } node->data->class.init_state = INITIALIZED; } |
Base_class_init()调用的顺序:
1. g_object_base_class_init
static void g_object_base_class_init (GObjectClass *class) { GObjectClass *pclass = g_type_class_peek_parent (class); /* reset instance specific fields and methods that don't get inherited */ class->construct_properties = pclass ? g_slist_copy (pclass->construct_properties) : NULL; class->get_property = NULL; class->set_property = NULL; } |
2 gtk_object_base_class_init
static void gtk_object_base_class_init (GtkObjectClass *class) { /* reset instance specifc methods that don't get inherited */ class->get_arg = NULL; class->set_arg = NULL; } |
3 gtk_widget_base_class_init 是NULL所以不调用
4 gtk_container_base_class_init
static void gtk_container_base_class_init (GtkContainerClass *class) { /* reset instance specifc class fields that don't get inherited */ class->set_child_property = NULL; class->get_child_property = NULL; } |
5 gtk_bin, gtk_windonw的base_class_init()都是NULL
下面开始class_init:
class_init() 之前有个递归过程(图示)
GtkWindow g_type_class_ref |
GtkBin g_type_class_ref |
GtkContainer g_type_class_ref |
GtkWidget g_type_class_ref |
GtkObject g_type_class_ref |
GObject g_type_class_ref |
class_init |
class_init |
class_init |
class_init |
class_init |
class_init |
下面用运行代码来检查实际的过程:
条件:
g_type_init(); GtkWidget *window =gtk_window_new(GTK_WINDOW_TOPLEVEL); |
结果:
GObject: g_type_name (object_type) : GtkWindow GObject: class == NULL. GType: enter g_type_class_ref() times = 1. GType: current type: GtkWindow GType: type_data_ref_Wm(GtkWindow). GType: find current type's GtkWindow parent: GtkBin. GType: enter g_type_class_ref() times = 2. GType: current type: GtkBin GType: type_data_ref_Wm(GtkBin). GType: find current type's GtkBin parent: GtkContainer. GType: enter g_type_class_ref() times = 3. GType: current type: GtkContainer GType: type_data_ref_Wm(GtkContainer). GType: find current type's GtkContainer parent: GtkWidget. GType: enter g_type_class_ref() times = 4. GType: current type: GtkWidget GType: type_data_ref_Wm(GtkWidget). GType: find current type's GtkWidget parent: GtkObject. GType: enter g_type_class_ref() times = 5. GType: current type: GtkObject GType: type_data_ref_Wm(GtkObject). GType: find current type's GtkObject parent: GInitiallyUnowned. GType: enter g_type_class_ref() times = 6. GType: current type: GInitiallyUnowned GType: type_data_ref_Wm(GInitiallyUnowned). GType: find current type's GInitiallyUnowned parent: GObject. GType: enter g_type_class_ref() times = 7. GType: current type: GObject GType: type_data_ref_Wm(GObject). GType: find current type's GObject parent: (null). GType: Node name: GObject. GType: Node name: GInitiallyUnowned. GType: Node name: GtkObject. GType: enter g_type_class_ref() times = 8. GType: current type: GObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 9. GType: current type: GParamPointer GType: type_data_ref_Wm(GParamPointer). GType: find current type's GParamPointer parent: GParam. GType: enter g_type_class_ref() times = 10. GType: current type: GParam GType: type_data_ref_Wm(GParam). GType: find current type's GParam parent: (null). GType: Node name: GParam. GType: Node name: GParamPointer. GType: Node name: GtkWidget. GType: enter g_type_class_ref() times = 11. GType: current type: GParamString GType: type_data_ref_Wm(GParamString). GType: find current type's GParamString parent: GParam. GType: enter g_type_class_ref() times = 12. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamString. GType: enter g_type_class_ref() times = 13. GType: current type: GParamObject GType: type_data_ref_Wm(GParamObject). GType: find current type's GParamObject parent: GParam. GType: enter g_type_class_ref() times = 14. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamObject. GType: enter g_type_class_ref() times = 15. GType: current type: GParamInt GType: type_data_ref_Wm(GParamInt). GType: find current type's GParamInt parent: GParam. GType: enter g_type_class_ref() times = 16. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamInt. GType: enter g_type_class_ref() times = 17. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 18. GType: current type: GParamBoolean GType: type_data_ref_Wm(GParamBoolean). GType: find current type's GParamBoolean parent: GParam. GType: enter g_type_class_ref() times = 19. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamBoolean. GType: enter g_type_class_ref() times = 20. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 21. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 22. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 23. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 24. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 25. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 26. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 27. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 28. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 29. GType: current type: GParamObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 30. GType: current type: GdkEventMask GType: type_data_ref_Wm(GdkEventMask). GType: find current type's GdkEventMask parent: GFlags. GType: enter g_type_class_ref() times = 31. GType: current type: GFlags GType: type_data_ref_Wm(GFlags). GType: find current type's GFlags parent: (null). GType: Node name: GFlags. GType: Node name: GdkEventMask. GType: enter g_type_class_ref() times = 32. GType: current type: GParamFlags GType: type_data_ref_Wm(GParamFlags). GType: find current type's GParamFlags parent: GParam. GType: enter g_type_class_ref() times = 33. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamFlags. GType: enter g_type_class_ref() times = 34. GType: current type: GdkExtensionMode GType: type_data_ref_Wm(GdkExtensionMode). GType: find current type's GdkExtensionMode parent: GEnum. GType: enter g_type_class_ref() times = 35. GType: current type: GEnum GType: type_data_ref_Wm(GEnum). GType: find current type's GEnum parent: (null). GType: Node name: GEnum. GType: Node name: GdkExtensionMode. GType: enter g_type_class_ref() times = 36. GType: current type: GParamEnum GType: type_data_ref_Wm(GParamEnum). GType: find current type's GParamEnum parent: GParam. GType: enter g_type_class_ref() times = 37. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamEnum. GType: enter g_type_class_ref() times = 38. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 39. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 40. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 41. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 42. GType: current type: GParamObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 43. GType: current type: GtkTouchSoundType GType: type_data_ref_Wm(GtkTouchSoundType). GType: find current type's GtkTouchSoundType parent: GEnum. GType: enter g_type_class_ref() times = 44. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GtkTouchSoundType. GType: enter g_type_class_ref() times = 45. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 46. GType: current type: GtkSvibeEffectType GType: type_data_ref_Wm(GtkSvibeEffectType). GType: find current type's GtkSvibeEffectType parent: GEnum. GType: enter g_type_class_ref() times = 47. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GtkSvibeEffectType. GType: enter g_type_class_ref() times = 48. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 49. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 50. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 51. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 52. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 53. GType: current type: GParamBoxed GType: type_data_ref_Wm(GParamBoxed). GType: find current type's GParamBoxed parent: GParam. GType: enter g_type_class_ref() times = 54. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamBoxed. GType: enter g_type_class_ref() times = 55. GType: current type: GParamBoxed GType: current type already exist. return. GType: enter g_type_class_ref() times = 56. GType: current type: GParamFloat GType: type_data_ref_Wm(GParamFloat). GType: find current type's GParamFloat parent: GParam. GType: enter g_type_class_ref() times = 57. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamFloat. GType: enter g_type_class_ref() times = 58. GType: current type: GParamBoxed GType: current type already exist. return. GType: enter g_type_class_ref() times = 59. GType: current type: GParamBoxed GType: current type already exist. return. GType: enter g_type_class_ref() times = 60. GType: current type: GParamBoxed GType: current type already exist. return. GType: enter g_type_class_ref() times = 61. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 62. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 63. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 64. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 65. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 66. GType: current type: GParamString GType: current type already exist. return. GType: Node name: GtkContainer. GType: enter g_type_class_ref() times = 67. GType: current type: GtkResizeMode GType: type_data_ref_Wm(GtkResizeMode). GType: find current type's GtkResizeMode parent: GEnum. GType: enter g_type_class_ref() times = 68. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GtkResizeMode. GType: enter g_type_class_ref() times = 69. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 70. GType: current type: GParamUInt GType: type_data_ref_Wm(GParamUInt). GType: find current type's GParamUInt parent: GParam. GType: enter g_type_class_ref() times = 71. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamUInt. GType: enter g_type_class_ref() times = 72. GType: current type: GParamObject GType: current type already exist. return. GType: Node name: GtkBin. GType: Node name: GtkWindow. GType: enter g_type_class_ref() times = 73. GType: current type: GtkWindowType GType: type_data_ref_Wm(GtkWindowType). GType: find current type's GtkWindowType parent: GEnum. GType: enter g_type_class_ref() times = 74. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GtkWindowType. GType: enter g_type_class_ref() times = 75. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 76. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 77. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 78. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 79. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 80. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 81. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 82. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 83. GType: current type: GtkWindowPosition GType: type_data_ref_Wm(GtkWindowPosition). GType: find current type's GtkWindowPosition parent: GEnum. GType: enter g_type_class_ref() times = 84. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GtkWindowPosition. GType: enter g_type_class_ref() times = 85. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 86. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 87. GType: current type: GParamInt GType: current type already exist. return. GType: enter g_type_class_ref() times = 88. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 89. GType: current type: GParamObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 90. GType: current type: GParamString GType: current type already exist. return. GType: enter g_type_class_ref() times = 91. GType: current type: GParamObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 92. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 93. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 94. GType: current type: GdkWindowTypeHint GType: type_data_ref_Wm(GdkWindowTypeHint). GType: find current type's GdkWindowTypeHint parent: GEnum. GType: enter g_type_class_ref() times = 95. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GdkWindowTypeHint. GType: enter g_type_class_ref() times = 96. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 97. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 98. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 99. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 100. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 101. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 102. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 103. GType: current type: GParamBoolean GType: current type already exist. return. GType: enter g_type_class_ref() times = 104. GType: current type: GdkGravity GType: type_data_ref_Wm(GdkGravity). GType: find current type's GdkGravity parent: GEnum. GType: enter g_type_class_ref() times = 105. GType: current type: GEnum GType: current type already exist. return. GType: Node name: GdkGravity. GType: enter g_type_class_ref() times = 106. GType: current type: GParamEnum GType: current type already exist. return. GType: enter g_type_class_ref() times = 107. GType: current type: GParamObject GType: current type already exist. return. GType: enter g_type_class_ref() times = 108. GType: current type: GParamDouble GType: type_data_ref_Wm(GParamDouble). GType: find current type's GParamDouble parent: GParam. GType: enter g_type_class_ref() times = 109. GType: current type: GParam GType: current type already exist. return. GType: Node name: GParamDouble. GType: enter g_type_class_ref() times = 110. GType: current type: GtkWindow GType: current type already exist. return. GObject: g_type_name (object_type) : GtkStyle GObject: class == NULL. GType: enter g_type_class_ref() times = 111. GType: current type: GtkStyle GType: type_data_ref_Wm(GtkStyle). GType: find current type's GtkStyle parent: GObject. GType: enter g_type_class_ref() times = 112. GType: current type: GObject GType: current type already exist. return. GType: Node name: GtkStyle. GType: enter g_type_class_ref() times = 113. GType: current type: GtkStyle GType: current type already exist. return. (process:802): GLib-GObject-WARNING **: invalid (NULL) pointer instance (process:802): GLib-GObject-CRITICAL **: g_signal_connect_data: assertion `G_TYPE_CHECK_INSTANCE (instance)' failed | `void | `GInterface | +GTypePlugin | +AtkImplementorIface | `GtkBuildable | `gchar | `guchar | `gboolean | `gint | `guint | `glong | `gulong | `gint64 | `guint64 | `GEnum | +GdkExtensionMode | +GtkTouchSoundType | +GtkSvibeEffectType | +GtkStateType | +GtkTextDirection | +GtkDirectionType | +GtkDragResult | +GtkWidgetHelpType | +GtkResizeMode | +GtkWindowType | +GtkWindowPosition | +GdkWindowTypeHint | `GdkGravity | `GFlags | `GdkEventMask | `gfloat | `gdouble | `gchararray | `gpointer | `GType | `GBoxed | +GValueArray | +GtkRequisition | +GdkRectangle | +GdkEvent | +GtkSelectionData | +GdkColor | `GtkBorder | `GParam | +GParamChar | +GParamUChar | +GParamBoolean | +GParamInt | +GParamUInt | +GParamLong | +GParamULong | +GParamInt64 | +GParamUInt64 | +GParamUnichar | +GParamEnum | +GParamFlags | +GParamFloat | +GParamDouble | +GParamString | +GParamParam | +GParamBoxed | +GParamPointer | +GParamValueArray | +GParamObject | +GParamOverride | `GParamGType | `GObject | +GInitiallyUnowned | | | `GtkObject | | | `GtkWidget | | | `GtkContainer | | | `GtkBin | | | `GtkWindow | +GtkStyle | +GdkDrawable | | | `GdkWindow | +GdkDragContext | +GtkTooltip | +GdkScreen | +GtkDeviceGroup | `GdkPixbuf |
相关文章推荐
- 看看GtkWindow如何被生出来的(gtk_window_new()的内部机制)
- TDD Tip:方法内部New出来的对象如何Mock
- TDD Tip:方法内部New出来的对象如何Mock
- JavaScript 工作机制:V8 引擎内部机制及如何编写优化代码的 5 个诀窍
- android 源码剖析之------Window的内部实现机制(添加、删除、更新)
- [zz]new FunctionName()的内部机制
- 如何提升测试环境的稳定性?来看看阿里内部的实践总结
- 你知道1+1=2是如何在cpu中运作的么?----跟我到cpu内部去看看吧!(2)
- 你知道1+1=2是如何在cpu中运作的么?----跟我到cpu内部去看看吧!(1)
- 深入研究 蒋金楠(Artech)老师的 MiniMvc(迷你 MVC),看看 MVC 内部到底是如何运行的
- mPopupWindow.setBackgroundDrawable(new BitmapDrawable())被废弃了,那如何设置呢?
- new和instanceof的内部机制
- 如何在Window系统下把Maven仓库中所有的jar文件都列出来?
- 看看黑客如何破解验证码机制
- [多图]大家看看从微软出来的高级程序员是如何工作和生活的?!
- DirectFB作为GDK后端时,gtk_widget_show_all()的内部机制。
- Android Window 内部机制 补充
- 如何解读决策树和随机森林的内部工作机制?
- Window内部机制与创建过程
- Window内部机制浅谈