总是梦见前男友的事:GType 类型系统 - 清华大学开源软件俱乐部 THOSS

来源:百度文库 编辑:偶看新闻 时间:2024/04/28 06:05:09

GType 类型系统

出自清华大学开源软件俱乐部 THOSS

跳转到: 导航, 搜索

下面是 Wikipedia 上对类型系统(type system)的定义:

In computer science, a type system defines how a programming language classifies values and expressions into types, how it can manipulate those types and how they interact.

GObject 提供了一套运行时的面向对象的类型系统——GType。说它是运行时的类型系统,是因为类型的定义、检查、操作、转换不是在编译时进行的,而是在程序运行过程中通过相关函数调用来完成;说它是面向对象的,是因为它提供了类、接口还有继承的机制。

目录

[隐藏]
  • 1 GType 类型系统的功能
    • 1.1 基本类型
    • 1.2 类和接口
    • 1.3 查看类型信息
    • 1.4 类型转换
  • 2 GType 类型系统内部的主要数据结构

    GType 类型系统的功能

    GType 主要提供了以下几个功能:

    • 基本类型
    • 类和接口
    • 查看类型信息,如父类、子类和实现的接口列表
    • 类型的向上和向下转换,类型检查

    基本类型

    所谓基本类型是指系统自动提供的,无须用户自己定义的类型。GType 定义了19种基本类型:

    /* gtype.h */         #define G_TYPE_INTERFACEG_TYPE_MAKE_FUNDAMENTAL (2)    #define G_TYPE_CHARG_TYPE_MAKE_FUNDAMENTAL (3)    #define G_TYPE_UCHARG_TYPE_MAKE_FUNDAMENTAL (4)    #define G_TYPE_BOOLEANG_TYPE_MAKE_FUNDAMENTAL (5)    #define G_TYPE_INTG_TYPE_MAKE_FUNDAMENTAL (6)    #define G_TYPE_UINTG_TYPE_MAKE_FUNDAMENTAL (7)    #define G_TYPE_LONGG_TYPE_MAKE_FUNDAMENTAL (8)    #define G_TYPE_ULONGG_TYPE_MAKE_FUNDAMENTAL (9)    #define G_TYPE_INT64G_TYPE_MAKE_FUNDAMENTAL (10)    #define G_TYPE_UINT64G_TYPE_MAKE_FUNDAMENTAL (11)    #define G_TYPE_ENUMG_TYPE_MAKE_FUNDAMENTAL (12)    #define G_TYPE_FLAGSG_TYPE_MAKE_FUNDAMENTAL (13)    #define G_TYPE_FLOATG_TYPE_MAKE_FUNDAMENTAL (14)    #define G_TYPE_DOUBLEG_TYPE_MAKE_FUNDAMENTAL (15)    #define G_TYPE_STRINGG_TYPE_MAKE_FUNDAMENTAL (16)    #define G_TYPE_POINTERG_TYPE_MAKE_FUNDAMENTAL (17)    #define G_TYPE_BOXEDG_TYPE_MAKE_FUNDAMENTAL (18)    #define G_TYPE_PARAMG_TYPE_MAKE_FUNDAMENTAL (19)    #define G_TYPE_OBJECTG_TYPE_MAKE_FUNDAMENTAL (20)

    可以发现多数基本类型都对应到 C 的基本类型,但有几个特殊的基本类型:G_TYPE_INTERFACE 代表接口类型,G_TYPE_PARAM 代表对象属性的参数说明,G_TYPE_OBJECT 代表 GObject 类型。关于 GType 的类型有几点需要强调:

    • 每一个类型有一个C语言类型为 GType 的唯一的标识符,这个标识符其实就是一个整形,上面这些宏返回的就是一个 GType,自定义的类(如 MY_IP_ADDRESS,也是如此);
    • 这个标识符用的不多,但是有些场合需要指定类型,就需要用到它:
    1. new 一个对象的时候;
    2. 指定对象属性的类型;
    3. 指定 GValue 值的类型;
    4. 声明一个类实现某个接口;
    5. GType 系统内部。

    类和接口

    GType 类型系统允许注册类、接口,支持声明某个类实现某个接口。由于 GObject 是所有类的基类,所以实际上在定义、注册和实现 GObject 类的子类这一节中已经说明了如何注册类了,下面主要讲如何定义、注册和实现一个接口。

    定义接口

    定义接口的方法和定义类的方法十分类似,主要区别在于接口不能实例化,所以不需要定义 instance 结构。下面是一个例子:

    #define MAMAN_IBAZ_TYPE                (maman_ibaz_get_type ())    #define MAMAN_IBAZ(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAMAN_IBAZ_TYPE, MamanIbaz))    #define MAMAN_IS_IBAZ(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAMAN_IBAZ_TYPE))    #define MAMAN_IBAZ_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), MAMAN_IBAZ_TYPE, MamanIbazInterface))         typedef struct _MamanIbaz MamanIbaz; /* dummy object */    typedef struct _MamanIbazInterface MamanIbazInterface;         /* 接口结构 */    struct _MamanIbazInterface {    /* 所有接口都是 GTypeInterface 的子类 */    GTypeInterface parent;         /* 虚函数列表 */    void (*do_action) (MamanIbaz *self);    };         GType maman_ibaz_get_type (void);         /* 一般都这样封装一下接口提供的函数 */    void maman_ibaz_do_action (MamanIbaz *self);

    要理解接口的定义和实现原理,首先需要理解“虚函数表”的概念,对 C++ 而言,每一个声明为 virtual 的成员函数都会加到类的“虚函数表”里面,这样系统就能保证对象的实际类型调用正确的虚函数实现,对 Java 而言,所有成员函数默认都是 virtual 的。GType 里面的接口结构其实就是一个“虚函数表”,里面保存了一些函数指针,每个实现这个接口的类都保存一份独立的接口结构拷贝,同时恰当设置里面的函数指针,保证使用的是自己的实现。

    下面是 maman_ibaz_get_type 和 maman_ibaz_do_action 函数的实现:

    GType    maman_ibaz_get_type ()    {    static GType type = 0;         if (!type)    {    type = g_type_register_static_simple (G_TYPE_INTERFACE, "MamanIbaz",    sizeof (MamanIbazInterface),    (GClassInitFunc) maman_ibaz_class_init,    0, NULL, 0);    }         return type;    }         void maman_ibaz_do_action (MamanIbaz *self)    {    MAMAN_IBAZ_GET_INTERFACE (self)->do_action (self);    }

    在定义一个接口的时候有两个 tricky 的地方:

    • MamanIbaz 结构并没有被定义,而只是声明。在使用接口的函数时,假设类 MamanBaz 实现了接口 MamanIbaz,那么传给函数的指针应该是 MAMAN_IBAZ(ptr_to_a_mamanbaz_object)。这和面向对象中接口的概念是一致的,实现了某个接口的类的对象能向上转换成接口的对象。
    • 在 maman_ibaz_do_action 函数的实现中,需要先通过 MAMAN_IBAZ_GET_INTERFACE (self) 获得一个接口结构(虚函数表),然后再调用虚表里面的 do_action 函数,由于 self 是由实现这个接口的对象转换来的,所以这就能保证执行的 do_action 函数是 self 对象的类所重载的版本。

    注册接口

    在 GType 类型系统中,类和接口的注册方法是一样的,两者的区别在于对 GTypeInfo 中各个域的作用。

    • base_init:一般都设为 NULL
    • base_finalize:一般都设为 NULL
    • class_init:接口的默认初始化函数,如果实现该接口的类不提供初始化接口(虚表)的函数的话,那么将由这个函数进行初始化,很多接口也直接设为 NULL
    • class_finalize:和 class_init 相反

    接口实现

    接口的函数由实现该接口的类提供。实现接口的类在自己的 xxx_get_type 函数里面通过 g_type_add_interface_static 函数来声明需要实现的接口。通过一个例子很容易就明白了:

    static void maman_baz_do_action (MamanIbaz *self)    {    g_print ("Baz implementation of IBaz interface Action.\n");    }              static void    baz_interface_init (gpointer         g_iface,    gpointer         iface_data)    {    MamanIbazInterface *iface = (MamanIbazInterface *)g_iface;    iface->do_action = maman_baz_do_action;    }         GType    maman_baz_get_type (void)    {    static GType type = 0;    if (type == 0) {    static const GTypeInfo info = {    sizeof (MamanBazInterface),    NULL,   /* base_init */    NULL,   /* base_finalize */    NULL,   /* class_init */    NULL,   /* class_finalize */    NULL,   /* class_data */    sizeof (MamanBaz),    0,      /* n_preallocs */    NULL    /* instance_init */    };    static const GInterfaceInfo ibaz_info = {    (GInterfaceInitFunc) baz_interface_init,    /* interface_init */    NULL,               /* interface_finalize */    NULL          /* interface_data */    };    type = g_type_register_static (G_TYPE_OBJECT,    "MamanBazType",    &info, 0);    g_type_add_interface_static (type,    MAMAN_IBAZ_TYPE,    &ibaz_info);    }    return type;    }

    g_type_add_interface_static 函数需要用户提供一个类型为 GInterfaceInfo 的参数,该结构定义如下:

    struct _GInterfaceInfo    {    GInterfaceInitFunc     interface_init;    GInterfaceFinalizeFunc interface_finalize;    gpointer               interface_data;    };

    第一个域是“虚表”的初始化函数,有实现接口的类提供,在这个函数中需要将虚表中的函数指针指向相应的实现函数。在上面的例子中,这个函数就是 baz_interface_init,它的第一个参数就是需要初始化的虚表。

    查看类型信息

    GType 提供了 API 用于检查一个类型的各种信息,对用户来说,比较常用的有几个:

    • g_type_parent:获取父类的类型
    • g_type_class_ref:返回类的 class 结构
    • g_type_interface_peek:返回类所实现的某个接口的接口结构(虚表)
    • g_type_children:返回子类列表
    • g_type_interfaces:返回实现的接口列表

    更多的 API 可以参考 GLib 的手册。

    类型转换

    在定义类和接口的时候,我们都需要定义一个宏,用于将其他类型转换成我们定义的类型,例如 MY_IP_ADDRESS 宏和 MAMAN_IBAZ_TYPE 宏,这些宏在底层都调用 g_type_check_instance_cast 函数,顾名思义,这个函数首先会检查转换是否合法,这类似于 C++ 中的 dynamic_cast。

    类型转换在 Gtk+ 的代码中有大量的应用,例如下面的代码:

    int main (int argc,    char *argv[])    {    GtkWidget *window, *ipaddress;    gint address[4] = { 1, 20, 35, 255 };         gtk_init (&argc, &argv);         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);    gtk_window_set_title (GTK_WINDOW (window), "GtkIPAddress");    gtk_container_set_border_width (GTK_CONTAINER (window), 10);         g_signal_connect (G_OBJECT (window), "destroy",         G_CALLBACK (gtk_main_quit), NULL);         ipaddress = my_ip_address_new ();    my_ip_address_set_address (MY_IP_ADDRESS (ipaddress), address);         g_signal_connect (G_OBJECT (ipaddress), "ip-changed",         G_CALLBACK (ip_address_changed), NULL);         gtk_container_add (GTK_CONTAINER (window), ipaddress);         gtk_widget_show_all (window);         gtk_main ();         return 0;    }

    GType 类型系统内部的主要数据结构

    在 GType 类型系统内部,每个类和接口都与一个 TypeNode 数据结构对应。

    struct _TypeNode    {    GTypePlugin *plugin;    guint        n_children : 12;    guint        n_supers : 8;    guint        _prot_n_ifaces_prerequisites : 9;    guint        is_classed : 1;    guint        is_instantiatable : 1;    guint        mutatable_check_cache : 1;/* combines some common path checks */    /* 子类列表 */    GType       *children;    /* 类和接口保存的数据是不一样的 */    TypeData * volatile data;    GQuark       qname;    GData       *global_gdata;    union {    /* 如果是一个类,这个 union 保存的是实现的接口列表 */    IFaceEntry  *iface_entries;/* for !iface types */    GType       *prerequisistes;    } _prot;    /* 父类数组 */    GType        supers[1]; /* flexible array */    };

    TypeData 实际上是一个 union,不同的类型对应不同的结构:

    union _TypeData    {    CommonData         common;    IFaceData          iface;    ClassData          class;    InstanceData       instance;    };

    如果类型是一个类,那么对应到 InstanceData,如果是接口,对应到 IFaceData:

    struct _InstanceData    {    CommonData         common;    guint16            class_size;    guint              init_state : 4;    GBaseInitFunc      class_init_base;    GBaseFinalizeFunc  class_finalize_base;    GClassInitFunc     class_init;    GClassFinalizeFunc class_finalize;    gconstpointer      class_data;    gpointer           class;    guint16            instance_size;    /* 私有数据的大小,通过 g_type_class_add_private 设置 */    guint16            private_size;    /* 现在已经不用这个域了 */    guint16            n_preallocs;    GInstanceInitFunc  instance_init;    };         struct _IFaceData    {    CommonData         common;    /* 用 GTypeInfo 里面的 class_size 域赋值 */    guint16            vtable_size;    /* 用 base_init 域赋值 */    GBaseInitFunc      vtable_init_base;    /* 用 base_finalize 域赋值 */    GBaseFinalizeFunc  vtable_finalize_base;    /* 用 class_init 域赋值 */    GClassInitFunc     dflt_init;    /* 用 class_finalize 域赋值 */    GClassFinalizeFunc dflt_finalize;    gconstpointer      dflt_data;    gpointer           dflt_vtable;    };

    可以发现,这两个结构的内容基本等同于 GTypeInfo 结构的内容,用户指定的类型信息大部分是保存在这两个结构里面的。

    那么,一个类实现的接口信息又保存在哪里呢?在 TypeNode 结构里面有一个 IFaceEntry 列表,每个 IFaceEntry 都保存该类实现的一个接口的信息。

    struct _IFaceEntry    {    GType           iface_type;    /* 类实现的接口的虚表,每个类独立 */    GTypeInterface *vtable;    InitState       init_state;    };

    回忆前面的内容,类需要为实现的接口提供一个初始化函数,这个函数是 GInterfaceInfo 结构的一个域,通过 g_type_add_interface_static 函数来设置。这个初始化函数又是怎样保存的呢?GType 为每一个接口关联了一个 IFaceHolder 结构的链表,每个表元素记录一个实现该接口的类的信息,其中包括该类提供的 GInterfaceInfo 结构。

    struct _IFaceHolder    {    GType           instance_type;    GInterfaceInfo *info;    GTypePlugin    *plugin;    IFaceHolder    *next;    };

    到现在,关于一个类型的所有信息如何设置、如何保存的问题已经讲清楚了,在类的实例化过程中将会用到这些信息。

    取自"http://www.thoss.org.cn/mediawiki/index.php/GType_%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F"