GObject 对接口的模拟[转]

原文链接

参考文档

在文档 [1] 中谈到接口古已有之,但是类的继承赋予了它一些新的概念。本文结合实例,学习如何使用 GObject 库所提供的接口类型来表达这些概念。

接口声明

下面的代码(文件名 my-iusb.h)声明了一个叫做 MyIUsb 的接口,My 是项目名,I 是 interface 的首字母的大写,Usb 表示接口的名称。MyIUsb 就表示在“My”项目里,Usb 是一个 Interface。

#ifndef MY_IUSB_H
#define MY_IUSB_H
  
#include <glib-object.h>
  
#define MY_TYPE_IUSB (my_iusb_get_type ())
#define MY_IUSB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),MY_TYPE_IUSB, MyIUsb))
 
typedef struct _MyIUsb MyIUsb;
typedef struct _MyIUsbInterface MyIUsbInterface;
  
struct _MyIUsbInterface {
    GTypeInterface parent_interface;
  
    gchar * (*read) (MyIUsb *self);
    void (*write) (MyIUsb *self, const gchar *str);
};
 
 
GType my_iusb_get_type (void);
 
gchar * my_iusb_read (MyIUsb *self);
void my_iusb_write (MyIUsb *self, const gchar *str);
  
#endif

上述代码与文档[5]中 KbBibtex 类的声明代码很相似,但也有所区别。

首先,MyIUsb 接口的实例结构体,它只是个名字,并没有具体实现。这是因为,在 Java 那样的语言里谈到“接口”,那么则意味着它是无法实例化的。这其中是有一定的道理的,因为接口只是协议嘛。

其次,MyIUsbInterface 是 MyIUsb 接口的类结构体,它继承自 GTypeInterface 类结构体。也就是说,当你要声明接口时,那么接口的类结构体便要继承 GTypeInterface 类结构体,而当你声明的是可实例化为对象的类时,其类结构体便要继承 GObjectClass。

再次,在 MyIUsbInterface 结构体中,可包含一组函数指针,它们便是接口的协议。

最后,声明接口。对于本例而言,接口便是 my_iusb_readmy_iusb_write

上述代码的解读过程大致是:

  • 这是一个 usb 接口。
  • 这个接口即可以 read,也可以 write,但是 read 只能是返回字符串,而 write 只能是接受字符串
  • 声明这个接口的具体形式,在现实中则以理解为声明 usb 接口有几根线构成,每根线的功能等等。

接口的定义

建立 my-iusb.c 源文件,内容如下:

#include "my-iusb.h"
 
G_DEFINE_INTERFACE (MyIUsb, my_iusb, G_TYPE_INVALID);
 
static void
my_iusb_default_init (MyIUsbInterface *iface)
{
}
 
gchar *
my_iusb_read (MyIUsb *self)
{
        g_return_if_fail (MY_IS_IUSB (self));
 
        MY_IUSB_GET_INTERFACE (self)->read (self);
}
 
void
my_iusb_write (MyIUsb *self, const gchar *str)
{
        g_return_if_fail (MY_IS_IUSB (self));
 
        MY_IUSB_GET_INTERFACE (self)->write (self, str);
}

其中,my_iusb_default_initG_DEFINE_INTERFACE 宏的展开代码中声明的一个函数,其中可以放置接口的一些初始化代码。如果没有这方面的需求,就让它表现为一个空函数即可,否则编译器会警告你,说你有一个函数声明了但没有实现。

另外,上述代码中出现了三个陌生的宏,其功能如下:

  • G_DEFINE_INTERFACE 宏的功用与 G_DEFINE_TYPE 类似,后者在 GObject 子类化的时候经常使用;
  • MY_IS_IUSB 宏是用来检测对象是否为 MyIUsb 类型,最好要在 my-iusb.h 中进行定义,代码为:
#define MY_IS_IUSB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_IUSB))
  • 在 GObject 子类化的时候,也可以定义类似的宏,用于识别某个对象对应哪种类型。比如之前我们用过的一个 G_IS_OBJECT 宏,它可以识别对象是否为 GObject 类型的对象。
  • MY_IUSB_GET_INTERFACE 宏,用于从 MyIUsb 接口的实例结构体中取出类结构体指针,然后利用该指针访问接口对应的方法。至于 MyIUsb 接口的实例结构体是怎样与类结构体指针取得关联的,那是 GObject 的内幕,暂且不必关心。MY_IUSB_GET_INTERFACE 宏也需要在 my-iusb.h 中进行定义,如下:
#define MY_IUSB_GET_INTERFACE(obj) (\
                G_TYPE_INSTANCE_GET_INTERFACE ((obj), MY_TYPE_IUSB, MyIUsbInterface))

现在,将 my-iusb.h 文件更新为:

#ifndef MY_IUSB_H
#define MY_IUSB_H
  
#include <glib-object.h>
  
#define MY_TYPE_IUSB (my_iusb_get_type ())
#define MY_IUSB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),MY_TYPE_IUSB, MyIUsb))
#define MY_IS_IUSB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_IUSB))
#define MY_IUSB_GET_INTERFACE(obj) (\
                G_TYPE_INSTANCE_GET_INTERFACE ((obj), MY_TYPE_IUSB, MyIUsbInterface))
  
typedef struct _MyIUsb MyIUsb;
typedef struct _MyIUsbInterface MyIUsbInterface;
  
struct _MyIUsbInterface {
    GTypeInterface parent_interface;
  
    gchar * (*read) (MyIUsb *self);
    void (*write) (MyIUsb *self, const gchar *str);
};
 
 
GType my_iusb_get_type (void);
 
gchar * my_iusb_read (MyIUsb *self);
void my_iusb_write (MyIUsb *self, const gchar *str);
  
#endif

插曲:经常要用到并且需要自己定义的宏

文档 [2-5] 中自定义了多个宏,在此略微进行总结一下,免的后续文档再多费口舌。
对于 GObject 的子类化,那么在声明类的时候,在头文件中直接插入类似下面的一组宏定义:

#define P_TYPE_T (p_t_get_type ())
#define P_T(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), P_TYPE_T, PT))
#define P_IS_T(obj) G_TYPE_CHECK_INSTANCE_TYPE ((obj), P_TYPE_T))
#define P_T_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), P_TYPE_T, PTClass))
#define P_IS_T_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), P_TYPE_T))
#define P_T_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), P_TYPE_T, PTClass))

这些宏的用法总结如下:

  • P_TYPE_T:仅在使用 g_object_new 进行对象实例化的时候使用一次,用于向 GObject 库的类型系统注册 PT 类;
  • P_T (obj):用于将 obj 对象的类型强制转换为 P_T 类的实例结构体类型;
  • P_IS_T (obj):用于判断 obj 对象的类型是否为 P_T 类的实例结构体类型;
  • P_T_CLASS(klass):用于将 klass 类结构体的类型强制转换为 P_T 类的类结构体类型;
  • P_IS_T_CLASS(klass):用于判断 klass 类结构体的类型是否为 P_T 类的类结构体类型;
  • P_T_GET_CLASS(obj):获取 obj 对象对应的类结构体类型。

对于 GTypeInterface 的子类化,在声明类的时候,在头文件中直接插入类似下面的一组宏定义:

#define P_TYPE_IT (p_t_get_type ())
#define P_IT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), P_TYPE_IT, PIt))
#define P_IS_IT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), P_TYPE_IT))
#define P_IT_GET_INTERFACE(obj) \
        (G_TYPE_INSTANCE_GET_INTERFACE ((obj), P_TYPE_IT, PItInterface))
  • P_TYPE_IT:仅在接口实现时使用一次,用于向 GObject 库的类型系统注册 PIT 接口;
  • P_IT (obj):用于将 obj 对象的类型强制转换为 P_IT 接口的实例结构体类型;
  • P_IS_IT (obj):用于判断 obj 对象的类型是否为 P_IT 接口的实例结构体类型;
  • P_IT_GET_INTERFACE(obj):获取 obj 对象对应的 P_IT 接口的类结构体类型。

接口的实现——制造 U 盘

既然已经有了 USB 接口的声明(协议),那就意味着我们可以制造具备这种接口的类与对象了。
首先声明 U 盘类(my-udisk.h):

#ifndef MY_UDISK_H
#define MY_UDISK_H
 
#include "my-iusb.h"
 
#define MY_TYPE_UDISK (my_udisk_get_type ())
#define MY_UDISK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_UDISK, MyUdisk))
#define MY_IS_UDISK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_UDISK))
#define MY_UDISK_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_CAST ((klass), MY_TYPE_UDISK, MyUdiskClass))
#define MY_IS_UDISK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MY_TYPE_UDISK))
#define MY_UDISK_GET_CLASS(obj) \
        (G_TYPE_INSTANCE_GET_CLASS ((obj),MY_TYPE_UDISK,MyUdiskClass))
 
typedef struct _MyUdisk MyUdisk;
typedef struct _MyUdiskClass MyUdiskClass;
 
struct _MyUdisk {
        GObject parent;
        GString *data;
};
struct _MyUdiskClass {
        GObjectClass parent_class;
};
 
GType my_udisk_get_type (void);
 
#endif

上述代码声明了一个 MyUdisk 类,它是 GObject 的子类。MyUdisk 类的实例结构体中有一个 GString 类型的 data 属性,用于存储数据。也就是说,这个 U 盘设计的有些脑残,因为它只能存储一个字符串!

然后定义 U 盘类(my-udisk.c):

#include "my-udisk.h"
 
static void my_iusb_interface_init (MyIUsbInterface *iface);
 
G_DEFINE_TYPE_WITH_CODE (MyUdisk, my_udisk, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (MY_TYPE_IUSB, my_iusb_interface_init));
 
static gchar *
my_udisk_read (MyIUsb *iusb)
{
        MyUdisk *udisk = MY_UDISK (iusb);
        return udisk->data->str;
}
 
static void
my_udisk_write (MyIUsb *iusb, const gchar *str)
{
        MyUdisk *udisk = MY_UDISK (iusb);
        g_string_assign (udisk->data, str);
}
 
static void
my_udisk_init (MyUdisk *self)
{
        self->data = g_string_new (NULL);
}
 
static void
my_udisk_class_init (MyUdiskClass *self)
{
}
 
static void
my_iusb_interface_init (MyIUsbInterface *iface)
{
        iface->read = my_udisk_read;
        iface->write = my_udisk_write;
}

上述代码中,有几处需要留意的地方:

  • my_iusb_interface_init 函数声明必须要放在 G_DEFINE_TYPE_WITH_CODE 宏之前,因为这个宏的展开代码中需要使用这个函数;
  • G_DEFINE_TYPE_WITH_CODE 是文档 [2-5] 中出现过的 G_DEFINE_TYPE 宏的“扩展版本”,在本例中可以向 my_udisk_get_type 函数(即 MY_TYPE_UDISK 宏展开的那个函数)中插入 C 代码。在本例中,这个宏所插入的 C 代码是“G_IMPLEMENT_INTERFACE(MY_TYPE_IUSB,my_iusb_interface_init)”,其中 G_IMPLEMENT_INTERFACE 宏的作用是将接口添加到 MyUdisk 类中;
  • my_iusb_interface_init 函数的作用是表明 MyUdisk 类实现了 MyIUsb 所规定的接口。

至此,MyIUsb 接口的一个实现便完成了。下面的 main.c 文件中的代码用于测试这个 U 盘是否可以使用 MyIUsb 接口进行访问,即:

#include "my-udisk.h"
 
int
main (void)
{
        g_type_init ();
 
        MyUdisk *udisk = g_object_new (MY_TYPE_UDISK, NULL);
 
        my_iusb_write (MY_IUSB (udisk), "I am u-disk!");
        gchar *data = my_iusb_read (MY_IUSB (udisk));
 
        g_printf ("%s\n\n", data);
 
        g_printf ("Is udisk a MyIUsb object?\n");
        if (MY_IS_IUSB (udisk))
                g_printf ("Yes!\n");
        else
                g_printf ("No!\n");
 
        g_printf ("\nIs udisk a MyUdisk object?\n");
        if (MY_IS_UDISK (udisk))
                g_printf ("Yes!\n");
        else
                g_printf ("No!\n");
 
        return 0;
}

这个程序的编译命令及执行结果如下:

$ gcc $(pkg-config --cflags --libs gobject-2.0) my-iusb.c my-udisk.c main.c -o test
$ ./test
I am u-disk!
 
Is udisk a MyIUsb object?
Yes!
 
Is udisk a MyUdisk object?
Yes!

一切都在掌握之中啊。

还可以接着造移动硬盘

可以像制造 U 盘那样,便可以炮制移动硬盘了。这项艰巨的任务便交给你了,而懒惰的我只提供下面这个计算机主机程序:

#include "my-udisk.h"
 
int
main (void)
{
        g_type_init ();
 
        gchar *data = NULL;
 
        MyUdisk *udisk = g_object_new (MY_TYPE_UDISK, NULL);
        my_iusb_write (MY_IUSB (udisk), "I am an u-disk!");
        data = my_iusb_read (MY_IUSB (udisk));
        g_printf ("%s\n", data);
 
        MyPortableHardDisk *phdisk = g_object_new (MY_TYPE_PORTABLE_HARD_DISK, NULL);
        my_iusb_write (MY_IUSB (phdisk), "I am a portable hard disk!");
        data = my_iusb_read (MY_IUSB (phdisk));
        g_printf ("%s\n", data);
 
        return 0;
}