Code:

Using glib-utility.h

Previously: Writing Object-Oriented Code with GLib/GObject

In general, the code follows the examples and guidelines provided in the online documentation for GTK+. However, you may notice certain deviations in cases where I thought better of doing as the GTK+ developers do. In particular:

  • No use is made of GObject properties, as this tends to add an extra layer of complexity to code for very little return. Accessor and mutator methods serve better, and are generally faster because they do not require the lookup time to match a property name to the method that operates on it; nor again do they require the time taken to cascade through a series of case statements until the right match is found. It is true that we lose blurbs and descriptions for class properties -- but, of course, to document those is one of the reasons for project documentation. We also lose construction properties, but class factory methods easily overcome this loss.
  • Type identifiers are named CLASS_NAME_TYPE instead of TYPE_CLASS_NAME; e.g., AN_OBJECT_TYPE instead of A_TYPE_OBJECT. This is purely for aesthetic reasons, as I feel that the former usage "sounds" better than the latter usage.

Defining a Class

Using glib-utility.h, one defines a class as follows:

/* maman-bar.h -----------------------------------------------*/
#include "glib-utility.h"

// The base class, from which all other classes are derived
g_class(MamanBar, GObject, MAMAN_BAR, maman_bar,
  g_instance(
    /* Instance members */
    /* <private> */
    int hsize;
  ),
  /* Class members */
)

// Type identity for MamanBar
#define MAMAN_BAR_TYPE (maman_bar_get_type())

If you compare the above to the example provided on the GTK+ website, you will see how much more streamlined glib-utility.h can make things.

So how does it work? The parameters expected by the g_class() macro are:

  1. The base name of the class; i.e., MamanBar. Note that we are not providing the name of the class structure; merely the name of the class as it is referenced by your documentation or by other code.
  2. The base name of the parent class; i.e., GObject. Once again, we are not providing the name of the class structure, only the name of the class itself. In the GTK+ documentation, GObject is always referred to as such, never as GObjectClass.
  3. The type name of the class. This is the name used to defined the type identity of the class, as well as the support macros: e.g., IS_MAMAN_BAR(), IS_MAMAN_BAR_CLASS(), and so forth.
  4. The prefix used for all methods pertaining to the class or its instances; i.e., maman_bar, as in maman_bar_get_type().
  5. Instance members, prefixed by the g_instance() macro.
  6. Class members.

g_class() expands into several statements, which:

  1. Define and implement the type support macros. These are all defined and implemented as static inline functions, since C preprocessor macros cannot themselves define C preprocessor macros. In GCC, inline functions are fast as macros.
    1. MAMAN_BAR(): Used to cast arbitrary instances to instances of MamanBar.
    2. IS_MAMAN_BAR(): Used to determine if an arbitrary instance is an instance of MamanBar or its descendants.
    3. MAMAN_BAR_CLASS(): Used to cast an arbitrary class structure reference to one of MamanBarClass.
    4. IS_MAMAN_BAR_CLASS(): Used to determine whether an arbitrary class structure reference is related to MamanBarClass or its descendants.
    5. MAMAN_BAR_GET_CLASS(): Used to retrieve a reference to MamanBarClass from an arbitrary instance of MamanBar or its descendants.
  2. Perform the type definitions of MamanBar and MamanBarClass.
  3. Begin the definition of struct MamanBar, including the definition of its first member, Parent, which is of type GObject.
  4. The g_instance() macro then takes over, allowing the specification of additional instance members. It also defines struct MamanBarClass and its first member, Parent, which is of type GObjectClass, so that when you continue the class definition outside of g_instance(), the members are assigned to MamanBarClass.

All of this is done behind the scenes and with far fewer keystrokes.

Implementing a Class

One implements a class using glib-utility.h as follows:

/* maman-bar.c -----------------------------------------------*/
#include "maman-bar.h"

/* For concise code, all class methods should be implemented
   BEFORE g_class_implement() is used.  However, it is also
   possible to declare the prototypes of these methods here,
   then use g_class_implement(), and THEN provide the actual
   implementation of class methods.
*/


g_class_implement(MamanBar, G_TYPE_OBJECT, MAMAN_BAR, maman_bar,
  g_class_preamble(),

  g_class_typedef(
    /* If your class implements any interfaces, you will declare
       that here.
    */

  ),

  g_class_init(
    /* Class structure initialization goes here.  This area
       is primarily used to define (and override) virtual
       method handlers.
    */

  ),

  g_instance_init(
    /* Instance initialization goes here.  You will likely
       use this area only to initialize instance variables.
    */

    Self->hsize = 0;
  ),

  g_instance_dispose(),
  g_instance_finalize()
)

Once again, compare the above to the example provided on the GTK+ documentation website.

How does it work? g_class_implment() expects the following parameters:

  1. The name of the class being implemented; i.e., MamanBar. As with g_class(), we never use the name of the class structure.
  2. The type identity of the parent class; i.e., G_TYPE_OBJECT.
  3. The type name of the class being implemented; as with g_class(), this is the name of the class as it is used in the type identity and type helper macros: MAMAN_BAR.
  4. The prefix used for methods related to the class, as in maman_bar_get_type(): maman_bar.
  5. Preamble code, which is placed immediately before maman_bar_get_type() is implemented. See below for more information.
  6. Class structure construction code.
  7. Class instance construction code.
  8. Class instance destruction code.
  9. Class instance finalization code.

g_class_implement() expands into several statements, which:

  1. Implement the type identity function: maman_bar_get_type().
  2. Define the boilerplate construction and destruction routines. This is where g_class_preamble(), g_class_typedef(), g_class_init(), g_instance_init(), g_instance_dispose(), and g_instance_finalize() are used.
    1. You will likely never use g_class_preamble(), except to declare it, since its purpose is to allow you to insert custom code just before the _get_type() function is implemented.
    2. If your class implements one or more interfaces, you will declare them in g_class_typedef(). glib-utility.h provides a helper macro, g_adopt(), which removes some of the tedium from declaring the adoption of these interfaces. More information about interfaces is available in the GTK+ documentation.
    3. Code pertaining to class structure initialization is placed in g_class_init(). This function is called once for every instance of MamanBarClass that is created; in other words, once for MamanBarClass itself and then once for each descendant of MamanBar. You will usually use this space to initialize virtual method handlers and override those inherited from a parent class. Note that g_class_init() defines a parameter, Self, which GObject fills in with a reference to the instance of MamanBarClass being initialized. More information on class construction is available in the GTK+ documentation.
    4. Code pertaining to class instance initialization is placed in g_instance_init(). This function is called once for every instance of MamanBar and its descendants that is created. You will usually use this space to initialize instance variables. Note that g_instance_init() defines a single parameter, Self, which GObject fills in with a reference to the instance of MamanBar being initialized. More information on instance initialization is available in the GTK+ documentation.
    5. Code that has to do with class instance destruction is placed in g_instance_dispose(). This function is called whenever the reference count on an instance of MamanBar or its descendants reaches zero (through one or more calls to g_object_unref()). It should only be called once on each instance of MamanBar but, due to errors in programming, it may be called more than once. g_instance_dispose() defines a single local variable, Self, which refers to the instance of MamanBar being destroyed. There is more information available in the GTK+ documentation. Note that g_instance_dispose() takes care of chaining up to the parent class.
    6. Code that has to do with finalizing a class instance is placed in g_instance_finalize(). g_instance_finalize defines a single local variable, Self, which refers to the instance of MamanBar being finalized. There is more information available in the GTK+ documentation. Note that g_instance_finalize() takes care of chaining up to the parent class.

Next: A Working Example of glib-utility.h