GObject 参考手册:教程:如何定义和实现接口

How to define interfaces

The bulk of interface definition has already been shown in the section called “Non-instantiable classed types: interfaces” but I feel it is needed to show exactly how to create an interface. The sample source code associated to this section can be found in the documentation’s source tarball, in the sample/interface/maman-ibaz.{h|c} file.

As above, the first step is to get the header right:

#ifndef MAMAN_IBAZ_H
#define MAMAN_IBAZ_H
 
#include <glib-object.h>
 
#define MAMAN_TYPE_IBAZ                (maman_ibaz_get_type ())
#define MAMAN_IBAZ(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAMAN_TYPE_IBAZ, MamanIbaz))
#define MAMAN_IS_IBAZ(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAMAN_TYPE_IBAZ))
#define MAMAN_IBAZ_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), MAMAN_TYPE_IBAZ, MamanIbazInterface))
 
 
typedef struct _MamanIbaz MamanIbaz; /* dummy object */
typedef struct _MamanIbazInterface MamanIbazInterface;
 
struct _MamanIbazInterface {
  GTypeInterface parent;
 
  void (*do_action) (MamanIbaz *self);
};
 
GType maman_ibaz_get_type (void);
 
void maman_ibaz_do_action (MamanIbaz *self);
 
#endif /*MAMAN_IBAZ_H*/

This code is the same as the code for a normal GType which derives from a GObject except for a few details:

  • The _GET_CLASS macro is called _GET_INTERFACE and not implemented with G_TYPE_INSTANCE_GET_CLASS but with G_TYPE_INSTANCE_GET_INTERFACE.
  • The instance type, MamanIbaz is not fully defined: it is used merely as an abstract type which represents an instance of whatever object which implements the interface.

The implementation of the MamanIbaz type itself is trivial:

  • maman_ibaz_get_type registers the type in the type system.
  • maman_ibaz_base_init is expected to register the interface’s signals if there are any (we will see a bit (later how to use them). Make sure to use a static local boolean variable to make sure not to run the initialization code twice (as described in the section called “Interface Initialization”, base_init is run once for each interface implementation instantiation)
  • maman_ibaz_do_action dereferences the class structure to access its associated class function and calls it.
static void
maman_ibaz_base_init (gpointer g_class)
{
  static gboolean initialized = FALSE;
 
  if (!initialized) {
    /* create interface signals here. */
    initialized = TRUE;
  }
}
 
GType
maman_ibaz_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanIbazInterface),
      maman_ibaz_base_init,   /* base_init */
      NULL,   /* base_finalize */
      NULL,   /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      0,
      0,      /* n_preallocs */
      NULL    /* instance_init */
    };
    type = g_type_register_static (G_TYPE_INTERFACE, "MamanIbaz", &info, 0);
  }
  return type;
}
 
void maman_ibaz_do_action (MamanIbaz *self)
{
  MAMAN_IBAZ_GET_INTERFACE (self)->do_action (self);
}

How To define implement an Interface?

Once the interface is defined, implementing it is rather trivial. Source code showing how to do this for the IBaz interface defined in the previous section is located in sample/interface/maman-baz.{h|c}.

The first step is to define a normal GType. Here, we have decided to use a GType which derives from GObject. Its name is MamanBaz:

#ifndef MAMAN_BAZ_H
#define MAMAN_BAZ_H
 
#include <glib-object.h>
 
#define MAMAN_TYPE_BAZ             (maman_baz_get_type ())
#define MAMAN_BAZ(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAMAN_TYPE_BAZ, Mamanbaz))
#define MAMAN_BAZ_CLASS(vtable)    (G_TYPE_CHECK_CLASS_CAST ((vtable), MAMAN_TYPE_BAZ, MamanbazClass))
#define MAMAN_IS_BAZ(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAMAN_TYPE_BAZ))
#define MAMAN_IS_BAZ_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), MAMAN_TYPE_BAZ))
#define MAMAN_BAZ_GET_CLASS(inst)  (G_TYPE_INSTANCE_GET_CLASS ((inst), MAMAN_TYPE_BAZ, MamanbazClass))
 
 
typedef struct _MamanBaz MamanBaz;
typedef struct _MamanBazClass MamanBazClass;
 
struct _MamanBaz {
  GObject parent;
  int instance_member;
};
 
struct _MamanBazClass {
  GObjectClass parent;
};
 
GType maman_baz_get_type (void);
 
 
#endif //MAMAN_BAZ_H

There is clearly nothing specifically weird or scary about this header: it does not define any weird API or derives from a weird type.

The second step is to implement maman_baz_get_type:

GType 
maman_baz_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanBazClass),
      NULL,   /* base_init */
      NULL,   /* base_finalize */
      NULL,   /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      sizeof (MamanBaz),
      0,      /* n_preallocs */
      baz_instance_init    /* 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_TYPE_IBAZ,
                                 &ibaz_info);
  }
  return type;
}

This function is very much like all the similar functions we looked at previously. The only interface-specific code present here is the call to g_type_add_interface_static which is used to inform the type system that this just-registered GType also implements the interface MAMAN_TYPE_IBAZ.

baz_interface_init, the interface initialization function, is also pretty simple:

static void baz_do_action (MamanBaz *self)
{
  g_print ("Baz implementation of IBaz interface Action: 0x%x.\n", self->instance_member);
}
static void
baz_interface_init (gpointer   g_iface,
                    gpointer   iface_data)
{
  MamanIbazInteface *iface = (MamanIbazInteface *)g_iface;
  iface->do_action = (void (*) (MamanIbaz *self))baz_do_action;
}
static void
baz_instance_init (GTypeInstance   *instance,
                   gpointer         g_class)
{
  MamanBaz *self = MAMAN_BAZ(instance);
  self->instance_member = 0xdeadbeaf;
}

baz_interface_init merely initializes the interface methods to the implementations defined by MamanBaz: maman_baz_do_action does nothing very useful but it could 🙂

Interface definition prerequisites

To specify that an interface requires the presence of other interfaces when implemented, GObject introduces the concept of prerequisites: it is possible to associate a list of prerequisite interfaces to an interface. For example, if object A wishes to implement interface I1, and if interface I1 has a prerequisite on interface I2, A has to implement both I1 and I2.

The mechanism described above is, in practice, very similar to Java’s interface I1 extends interface I2. The example below shows the GObject equivalent:

  type = g_type_register_static (G_TYPE_INTERFACE, "MamanIbar", &info, 0);
  /* Make the MamanIbar interface require MamanIbaz interface. */
  g_type_interface_add_prerequisite (type, MAMAN_TYPE_IBAZ);

The code shown above adds the MamanIbaz interface to the list of prerequisites of MamanIbar while the code below shows how an implementation can implement both interfaces and register their implementations:

static void ibar_do_another_action (MamanBar *self)
{
  g_print ("Bar implementation of IBar interface Another Action: 0x%x.\n", self->instance_member);
}
 
static void
ibar_interface_init (gpointer   g_iface,
                     gpointer   iface_data)
{
  MamanIbarInterface *iface = (MamanIbarInterface *)g_iface;
  iface->do_another_action = (void (*) (MamanIbar *self))ibar_do_another_action;
}
 
 
static void ibaz_do_action (MamanBar *self)
{
  g_print ("Bar implementation of IBaz interface Action: 0x%x.\n", self->instance_member);
}
 
static void
ibaz_interface_init (gpointer   g_iface,
                    gpointer   iface_data)
{
  MamanIbazInterface *iface = (MamanIbazInterface *)g_iface;
  iface->do_action = (void (*) (MamanIbaz *self))ibaz_do_action;
}
 
static void
bar_instance_init (GTypeInstance   *instance,
                   gpointer         g_class)
{
  MamanBar *self = (MamanBar *)instance;
  self->instance_member = 0x666;
}
 
GType 
maman_bar_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanBarClass),
      NULL,   /* base_init */
      NULL,   /* base_finalize */
      NULL,   /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      sizeof (MamanBar),
      0,      /* n_preallocs */
      bar_instance_init    /* instance_init */
    };
    static const GInterfaceInfo ibar_info = {
      (GInterfaceInitFunc) ibar_interface_init,   /* interface_init */
      NULL,                                       /* interface_finalize */
      NULL                                        /* interface_data */
    };
    static const GInterfaceInfo ibaz_info = {
      (GInterfaceInitFunc) ibaz_interface_init,   /* interface_init */
      NULL,                                       /* interface_finalize */
      NULL                                        /* interface_data */
    };
    type = g_type_register_static (G_TYPE_OBJECT,
                                   "MamanBarType",
                                   &info, 0);
    g_type_add_interface_static (type,
                                 MAMAN_TYPE_IBAZ,
                                 &ibaz_info);
    g_type_add_interface_static (type,
                                 MAMAN_TYPE_IBAR,
                                 &ibar_info);
  }
  return type;
}

It is very important to notice that the order in which interface implementations are added to the main object is not random: g_type_add_interface_static must be invoked first on the interfaces which have no prerequisites and then on the others.

Complete source code showing how to define the MamanIbar interface which requires MamanIbaz and how to implement the MamanIbar interface is located in sample/interface/maman-ibar.{h|c} and sample/interface/maman-bar.{h|c}.

Interface Properties

Starting from version 2.4 of GLib, GObject interfaces can also have properties. Declaration of the interface properties is similar to declaring the properties of ordinary GObject types as explained in the section called “Object properties”, except that g_object_interface_install_property is used to declare the properties instead of g_object_class_install_property.

To include a property named ‘name’ of type string in the maman_ibaz interface example code above, we only need to add one [14] line in the maman_ibaz_base_init [15] as shown below:

static void
maman_ibaz_base_init (gpointer g_iface)
{
  static gboolean initialized = FALSE;
 
  if (!initialized) {
    /* create interface signals here. */
 
    g_object_interface_install_property (g_iface,
                g_param_spec_string ("name",
                    "maman_ibaz_name",
                    "Name of the MamanIbaz",
                    "maman",
                    G_PARAM_READWRITE));
    initialized = TRUE;
  }
}

One point worth noting is that the declared property wasn’t assigned an integer ID. The reason being that integer IDs of properties are utilized only inside the get and set methods and since interfaces do not implement properties, there is no need to assign integer IDs to interface properties.

The story for the implementers of the interface is also quite trivial. An implementer shall declare and define it’s properties in the usual way as explained in the section called “Object properties”, except for one small change: it shall declare the properties of the interface it implements using g_object_class_override_property instead of g_object_class_install_property. The following code snippet shows the modifications needed in the MamanBaz declaration and implementation above:

struct _MamanBaz {
  GObject parent;
  gint instance_member;
  gchar *name;        /* placeholder for property */
};
 
enum
{
  ARG_0,
  ARG_NAME
};
 
GType 
maman_baz_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanBazClass),
      NULL,   /* base_init */
      NULL,   /* base_finalize */
      baz_class_init, /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      sizeof (MamanBaz),
      0,      /* n_preallocs */
      baz_instance_init    /* 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_TYPE_IBAZ,
                                 &ibaz_info);
  }
  return type;
}
 
static void
maman_baz_class_init (MamanBazClass * klass)
{
  GObjectClass *gobject_class;
 
  gobject_class = (GObjectClass *) klass;
 
  parent_class = g_type_class_ref (G_TYPE_OBJECT);
 
  gobject_class->set_property = maman_baz_set_property;
  gobject_class->get_property = maman_baz_get_property;
 
  g_object_class_override_property (gobject_class, ARG_NAME, "name");
}
 
static void
maman_baz_set_property (GObject * object, guint prop_id,
                        const GValue * value, GParamSpec * pspec)
{
  MamanBaz *baz;
  GObject *obj;
 
  /* it's not null if we got it, but it might not be ours */
  g_return_if_fail (G_IS_MAMAN_BAZ (object));
 
  baz = MAMAN_BAZ (object);
 
  switch (prop_id) {
    case ARG_NAME:
      baz->name = g_value_get_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}
 
static void
maman_baz_get_property (GObject * object, guint prop_id,
                        GValue * value, GParamSpec * pspec)
{
  MamanBaz *baz;
 
  /* it's not null if we got it, but it might not be ours */
  g_return_if_fail (G_IS_TEXT_PLUGIN (object));
 
  baz = MAMAN_BAZ (object);
 
  switch (prop_id) {
    case ARG_NAME:
      g_value_set_string (value, baz->name);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *