GObject 参考手册:教程:如何定义和实现在一个新 GObject

Clearly, this is one of the most common questions people ask: they just want to crank code and implement a subclass of a GObject. Sometimes because they want to create their own class hierarchy, sometimes because they want to subclass one of GTK+’s widget. This chapter will focus on the implementation of a subtype of GObject. The sample source code associated with this section can be found in the documentation’s source tarball, in the sample/gobject directory:

  • maman-bar.{h|c}: this is the source for a object which derives from GObject and which shows how to declare different types of methods on the object.
  • maman-subbar.{h|c}: this is the source for a object which derives from MamanBar and which shows how to override some of its parent’s methods.
  • maman-foo.{h|c}: this is the source for an object which derives from GObject and which declares a signal.
  • test.c: this is the main source which instantiates an instance of type and exercises their API.

Boilerplate header code

The first step before writing the code for your GObject is to write the type’s header which contains the needed type, function and macro definitions. Each of these elements is nothing but a convention which is followed not only by GTK+’s code but also by most users of GObject. If you feel the need not to obey the rules stated below, think about it twice:

  • If your users are a bit accustomed to GTK+ code or any GLib code, they will be a bit surprised and getting used to the conventions you decided upon will take time (money) and will make them grumpy (not a good thing)
  • You must assess the fact that these conventions might have been designed by both smart and experienced people: maybe they were at least partly right. Try to put your ego aside.

    Pick a name convention for your headers and source code and stick to it:

  • use a dash to separate the prefix from the typename: maman-bar.h and maman-bar.c (this is the convention used by Nautilus and most GNOME libraries).
  • use an underscore to separate the prefix from the typename: maman_bar.h and maman_bar.c.
  • Do not separate the prefix from the typename: mamanbar.h and mamanbar.c. (this is the convention used by GTK+)

I personally like the first solution better: it makes reading file names easier for those with poor eyesight like me.

When you need some private (internal) declarations in several (sub)classes, you can define them in a private header file which is often named by appending the private keyword to the public header name. For example, one could use maman-bar-private.h, maman_bar_private.h or mamanbarprivate.h. Typically, such private header files are not installed.

The basic conventions for any header which exposes a GType are described in the section called “Conventions”. Most GObject-based code also obeys one of of the following conventions: pick one and stick to it.

  • If you want to declare a type named bar with prefix maman, name the type instance MamanBar and its class MamanBarClass (name is case-sensitive). It is customary to declare them with code similar to the following:
          /*
           * Copyright/Licensing information.
           */
     
          #ifndef MAMAN_BAR_H
          #define MAMAN_BAR_H
     
          /*
           * Potentially, include other headers on which this header depends.
           */
     
          /*
           * Type macros.
           */
     
          typedef struct _MamanBar MamanBar;
          typedef struct _MamanBarClass MamanBarClass;
     
          struct _MamanBar {
            GObject parent;
            /* instance members */
          };
     
          struct _MamanBarClass {
            GObjectClass parent;
            /* class members */
          };
     
          /* used by MAMAN_TYPE_BAR */
          GType maman_bar_get_type (void);
     
          /*
           * Method definitions.
           */
     
          #endif
  • Most GTK+ types declare their private fields in the public header with a /* private */ comment, relying on their user’s intelligence not to try to play with these fields. Fields not marked private are considered public by default. The /* protected */ comment (same semantics as those of C++) is also used, mainly in the GType library, in code written by Tim Janik.
          struct _MamanBar {
            GObject parent;
     
            /*< private >*/
            int hsize;
          };
  • All of Nautilus code and a lot of GNOME libraries use private indirection members, as described by Herb Sutter in his Pimpl articles (see Compilation Firewalls and The Fast Pimpl Idiom : he summarizes the different issues better than I will).
          typedef struct _MamanBarPrivate MamanBarPrivate;
          struct _MamanBar {
            GObject parent;
     
            /*< private >*/
            MamanBarPrivate *priv;	/* Do not call this private, as that is a registered c++ keyword. */
          };

    The private structure is then defined in the .c file, instantiated in the object’s init function and destroyed in the object’s finalize function.

          static void
          maman_bar_finalize (GObject *object) {
            MamanBar *self = MAMAN_BAR (object);
            /* do stuff */
            g_free (self->priv);
          }
     
          static void
          maman_bar_init (GTypeInstance *instance, gpointer g_class) {
            MamanBar *self = MAMAN_BAR (instance);
            self->priv = g_new0 (MamanBarPrivate,1);
            /* do stuff */
          }
  • A similar alternative, available since GLib version 2.4, is to define a private structure in the .c file, declare it as a private structure in maman_bar_class_init using g_type_class_add_private. Instead of allocating memory in maman_bar_init a pointer to the private memory area is stored in the instance to allow convenient access to this structure. A private structure will then be attached to each newly created object by the GObject system. You don’t need to free or allocate the private structure, only the objects or pointers that it may contain. Another advantage of this to the previous version is that is lessens memory fragmentation, as the public and private parts of the instance memory are allocated at once.
          typedef struct _MamanBarPrivate MamanBarPrivate;
     
          struct _MamanBarPrivate {
            int private_field;
          };
     
          static void
          maman_bar_class_init (MamanBarClass *klass)
          {
            ...
            g_type_class_add_private (klass, sizeof (MamanBarPrivate));
            ...
          }
     
          static void
          maman_bar_init (GTypeInstance *instance, gpointer g_class) {
            MamanBar *self = MAMAN_BAR (instance);
            self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MAMAN_TYPE_BAR, MamanBarPrivate);
            /* do stuff */
          }

Finally, there are different header include conventions. Again, pick one and stick to it. I personally use indifferently any of the two, depending on the codebase I work on: the rule is consistency.

  • Some people add at the top of their headers a number of #include directives to pull in all the headers needed to compile client code. This allows client code to simply #include “maman-bar.h”.
  • Other do not #include anything and expect the client to #include themselves the headers they need before including your header. This speeds up compilation because it minimizes the amount of pre-processor work. This can be used in conjunction with the re-declaration of certain unused types in the client code to minimize compile-time dependencies and thus speed up compilation.

Boilerplate code

In your code, the first step is to #include the needed headers: depending on your header include strategy, this can be as simple as #include “maman-bar.h” or as complicated as tens of #include lines ending with #include “maman-bar.h”:

/*
 * Copyright information
 */
 
#include "maman-bar.h"
 
/* If you use Pimpls, include the private structure 
 * definition here. Some people create a maman-bar-private.h header
 * which is included by the maman-bar.c file and which contains the
 * definition for this private structure.
 */
struct _MamanBarPrivate {
  int member_1;
  /* stuff */
};
 
/* 
 * forward definitions
 */

Implement maman_bar_get_type and make sure the code compiles:

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 */
      NULL    /* instance_init */
      };
      type = g_type_register_static (G_TYPE_OBJECT,
                                     "MamanBarType",
                                     &info, 0);
    }
    return type;
}

Object Construction

People often get confused when trying to construct their GObjects because of the sheer number of different ways to hook into the objects’s construction process: it is difficult to figure which is the correct, recommended way.

Table 4, “the section called “g_object_new ()”” shows what user-provided functions are invoked during object instantiation and in which order they are invoked. A user looking for the equivalent of the simple C++ constructor function should use the instance_init method. It will be invoked after all the parent’s instance_init functions have been invoked. It cannot take arbitrary construction parameters (as in C++) but if your object needs arbitrary parameters to complete initialization, you can use construction properties.

Construction properties will be set only after all instance_init functions have run. No object reference will be returned to the client of g_object_new> until all the construction properties have been set.

As such, I would recommend writing the following code first:

static void
maman_bar_init (GTypeInstance   *instance,
                gpointer         g_class)
{
  MamanBar *self = (MamanBar *)instance;
  self->private = g_new0 (MamanBarPrivate, 1);
 
  /* initialize all public and private members to reasonable default values. */
  /* If you need specific construction properties to complete initialization,
   * delay initialization completion until the property is set. 
   */
}

And make sure that you set maman_bar_init as the type’s instance_init function in maman_bar_get_type. Make sure the code builds and runs: create an instance of the object and make sure maman_bar_init is called (add a g_print call in it).

Now, if you need special construction properties, install the properties in the class_init function, override the set and get methods and implement the get and set methods as described in the section called “Object properties”. Make sure that these properties use a construct only GParamSpec by setting the param spec’s flag field to G_PARAM_CONSTRUCT_ONLY: this helps GType ensure that these properties are not set again later by malicious user code.

static void
bar_class_init (MamanBarClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamSpec *maman_param_spec;
 
  gobject_class->set_property = bar_set_property;
  gobject_class->get_property = bar_get_property;
 
  maman_param_spec = g_param_spec_string ("maman",
                                          "Maman construct prop",
                                          "Set maman's name",
                                          "no-name-set" /* default value */,
                                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
 
  g_object_class_install_property (gobject_class,
                                   PROP_MAMAN,
                                   maman_param_spec);
}

If you need this, make sure you can build and run code similar to the code shown above. Make sure your construct properties can set correctly during construction, make sure you cannot set them afterwards and make sure that if your users do not call g_object_new with the required construction properties, these will be initialized with the default values.

I consider good taste to halt program execution if a construction property is set its default value. This allows you to catch client code which does not give a reasonable value to the construction properties. Of course, you are free to disagree but you should have a good reason to do so.

Some people sometimes need to construct their object but only after the construction properties have been set. This is possible through the use of the constructor class method as described in the section called “Object instantiation”. However, I have yet to see any reasonable use of this feature. As such, to initialize your object instances, use by default the base_init function and construction properties.

Object Destruction

Again, it is often difficult to figure out which mechanism to use to hook into the object’s destruction process: when the last g_object_unref function call is made, a lot of things happen as described in Table 5, “the section called “g_object_unref ()””.

The destruction process of your object must be split is two different phases: you must override both the dispose and the finalize class methods.

struct _MamanBarPrivate {
  gboolean dispose_has_run;
};
 
static GObjectClass parent_class = NULL;
 
static void
bar_dispose (GObject *obj)
{
  MamanBar *self = (MamanBar *)obj;
 
  if (self->priv->dispose_has_run) {
   /* If dispose did already run, return. */
    return;
  }
  /* Make sure dispose does not run twice. */
  object->priv->dispose_has_run = TRUE;
 
  /* 
   * In dispose, you are supposed to free all types referenced from this
   * object which might themselves hold a reference to self. Generally,
   * the most simple solution is to unref all members on which you own a 
   * reference.
   */
 
   /* Chain up to the parent class */
   G_OBJECT_CLASS (parent_class)->dispose (obj);
}
 
static void
bar_finalize (GObject *obj)
{
  MamanBar *self = (MamanBar *)obj;
 
   /* Chain up to the parent class */
   G_OBJECT_CLASS (parent_class)->finalize (obj);
}
 
static void
bar_class_init (BarClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
  gobject_class->dispose = bar_dispose;
  gobject_class->finalize = bar_finalize;
 
  parent_class = g_type_class_peek_parent (klass);
  g_type_class_add_private(klass,sizeof(MamanBarPrivate));
}
 
static void
maman_bar_init (GTypeInstance   *instance,
                gpointer         g_class)
{
  MamanBar *self = (MamanBar *)instance;
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, BT_TYPE_PATTERN, BtPatternPrivate);
  self->priv->dispose_has_run = FALSE;
 
}

Add similar code to your GObject, make sure the code still builds and runs: dispose and finalize must be called during the last unref. It is possible that object methods might be invoked after dispose is run and before finalize runs. GObject does not consider this to be a program error: you must gracefully detect this and neither crash nor warn the user. To do this, you need something like the following code at the start of each object method, to make sure the object’s data is still valid before manipulating it:

if (self->private->dispose_has_run) {
  /* Dispose has run. Data is not valid anymore. */
  return;
}

Object methods

Just as with C++, there are many different ways to define object methods and extend them: the following list and sections draw on C++ vocabulary. (Readers are expected to know basic C++ buzzwords. Those who have not had to write C++ code recently can refer to e.g. http://www.cplusplus.com/doc/tutorial/ to refresh their memories.)

  • non-virtual public methods,
  • virtual public methods and
  • virtual private methods

Non-virtual public methods

These are the simplest: you want to provide a simple method which can act on your object. All you need to do is to provide a function prototype in the header and an implementation of that prototype in the source file.

/* declaration in the header. */
void maman_bar_do_action (MamanBar *self, /* parameters */);
/* implementation in the source file */
void maman_bar_do_action (MamanBar *self, /* parameters */)
{
  /* do stuff here. */
}

There is really nothing scary about this.

Virtual public methods

This is the preferred way to create polymorphic GObjects. All you need to do is to define the common method and its class function in the public header, implement the common method in the source file and re-implement the class function in each object which inherits from you.

/* declaration in maman-bar.h. */
struct _MamanBarClass {
  GObjectClass parent;
 
  /* stuff */
  void (*do_action) (MamanBar *self, /* parameters */);
};
void maman_bar_do_action (MamanBar *self, /* parameters */);
/* implementation in maman-bar.c */
void maman_bar_do_action (MamanBar *self, /* parameters */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action (self, /* parameters */);
}

The code above simply redirects the do_action call to the relevant class function. Some users, concerned about performance, do not provide the maman_bar_do_action wrapper function and require users to dereference the class pointer themselves. This is not such a great idea in terms of encapsulation and makes it difficult to change the object’s implementation afterwards, should this be needed.

Other users, also concerned by performance issues, declare the maman_bar_do_action function inline in the header file. This, however, makes it difficult to change the object’s implementation later (although easier than requiring users to directly dereference the class function) and is often difficult to write in a portable way (the inline keyword is not part of the C standard).

In doubt, unless a user shows you hard numbers about the performance cost of the function call, just maman_bar_do_action in the source file.

Please, note that it is possible for you to provide a default implementation for this class method in the object’s class_init function: initialize the klass->do_action field to a pointer to the actual implementation. You can also make this class method pure virtual by initializing the klass->do_action field to NULL:

static void 
maman_bar_real_do_action_two (MamanBar *self, /* parameters */)
{
  /* Default implementation for the virtual method. */
}
 
static void
maman_bar_class_init (BarClass *klass)
{
  /* pure virtual method: mandates implementation in children. */
  klass->do_action_one = NULL;
  /* merely virtual method. */
  klass->do_action_two = maman_bar_real_do_action_two;
}
 
void maman_bar_do_action_one (MamanBar *self, /* parameters */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action_one (self, /* parameters */);
}
void maman_bar_do_action_two (MamanBar *self, /* parameters */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action_two (self, /* parameters */);
}

Virtual private Methods

These are very similar to Virtual Public methods. They just don’t have a public function to call the function directly. The header file contains only a declaration of the class function:

/* declaration in maman-bar.h. */
struct _MamanBarClass {
  GObjectClass parent;
 
  /* stuff */
  void (*helper_do_specific_action) (MamanBar *self, /* parameters */);
};
void maman_bar_do_any_action (MamanBar *self, /* parameters */);

These class functions are often used to delegate part of the job to child classes:

/* this accessor function is static: it is not exported outside of this file. */
static void 
maman_bar_do_specific_action (MamanBar *self, /* parameters */)
{
  MAMAN_BAR_GET_CLASS (self)->do_specific_action (self, /* parameters */);
}
 
void maman_bar_do_any_action (MamanBar *self, /* parameters */)
{
  /* random code here */
 
  /* 
   * Try to execute the requested action. Maybe the requested action cannot be implemented
   * here. So, we delegate its implementation to the child class:
   */
  maman_bar_do_specific_action (self, /* parameters */);
 
  /* other random code here */
}

Again, it is possible to provide a default implementation for this private virtual class function:

static void
maman_bar_class_init (MamanBarClass *klass)
{
  /* pure virtual method: mandates implementation in children. */
  klass->do_specific_action_one = NULL;
  /* merely virtual method. */
  klass->do_specific_action_two = maman_bar_real_do_specific_action_two;
}

Children can then implement the subclass with code such as:

static void
maman_bar_subtype_class_init (MamanBarSubTypeClass *klass)
{
  MamanBarClass *bar_class = MAMAN_BAR_CLASS (klass);
  /* implement pure virtual class function. */
  bar_class->do_specific_action_one = maman_bar_subtype_do_specific_action_one;
}

Chaining up

Chaining up is often loosely defined by the following set of conditions:

  • Parent class A defines a public virtual method named foo and provides a default implementation.
  • Child class B re-implements method foo.
  • In the method B::foo, the child class B calls its parent class method A::foo.

There are many uses to this idiom:

  • You need to change the behaviour of a class without modifying its code. You create a subclass to inherit its implementation, re-implement a public virtual method to modify the behaviour slightly and chain up to ensure that the previous behaviour is not really modified, just extended.
  • You are lazy, you have access to the source code of the parent class but you don’t want to modify it to add method calls to new specialized method calls: it is faster to hack the child class to chain up than to modify the parent to call down.
  • You need to implement the Chain Of Responsibility pattern: each object of the inheritance tree chains up to its parent (typically, at the beginning or the end of the method) to ensure that they each handler is run in turn.

I am personally not really convinced any of the last two uses are really a good idea but since this programming idiom is often used, this section attempts to explain how to implement it.

To explicitly chain up to the implementation of the virtual method in the parent class, you first need a handle to the original parent class structure. This pointer can then be used to access the original class function pointer and invoke it directly. [13]

The function g_type_class_peek_parent is used to access the original parent class structure. Its input is a pointer to the class of the derived object and it returns a pointer to the original parent class structure. The code below shows how you could use it:

static void
b_method_to_call (B *obj, int a)
{
  BClass *klass;
  AClass *parent_class;
  klass = B_GET_CLASS (obj);
  parent_class = g_type_class_peek_parent (klass);
 
  /* do stuff before chain up */
  parent_class->method_to_call (obj, a);
  /* do stuff after chain up */
}

A lot of people who use this idiom in GTK+ store the parent class structure pointer in a global static variable to avoid the costly call to g_type_class_peek_parent for each function call. Typically, the class_init callback initializes the global static variable. gtk/gtkhscale.c does this.

[13] The original adjective used in this sentence is not innocuous. To fully understand its meaning, you need to recall how class structures are initialized: for each object type, the class structure associated to this object is created by first copying the class structure of its parent type (a simple memcpy) and then by invoking the class_init callback on the resulting class structure. Since the class_init callback is responsible for overwriting the class structure with the user re-implementations of the class methods, we cannot merely use the modified copy of the parent class structure stored in our derived instance. We want to get a copy of the class structure of an instance of the parent class.

Leave a Reply

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