Deriving a custom class from GObject

This post demonstrates how to create a custom class derived from GObject. In this example it is a class named “Hamster”. Furthermore, it is shown, how to define a private class members, in this case the “name” of the hamster. The way custom properties can be defined is also illustrated.
There is no much explanation yet, so consider it as a work in progress…

source code of the header file mz-hamster.h:

#ifndef __MZ_HAMSTER_H__
#define __MZ_HAMSTER_H__

#include <glib-object.h>

G_BEGIN_DECLS

#define MZ_TYPE_HAMSTER			( mz_hamster_get_type( ) )
#define MZ_HAMSTER( obj )		( G_TYPE_CHECK_INSTANCE_CAST( (obj), MZ_TYPE_HAMSTER, MzHamster ) )
#define MZ_IS_HAMSTER( obj )		( G_TYPE_CHECK_INSTANCE_TYPE( (obj), MZ_TYPE_HAMSTER ) )
#define MZ_HAMSTER_CLASS( klass )	( G_TYPE_CHECK_CLASS_CAST( (klass), MZ_TYPE_HAMSTER, MzHamsterClass ) )
#define MZ_IS_HAMSTER_CLASS( klass )	( G_TYPE_CHECK_CLASS_TYPE( (klass), MZ_TYPE_HAMSTER ) )
#define MZ_HAMSTER_GET_CLASS( obj )	( G_TYPE_INSTANCE_GET_CLASS( (obj), MZ_TYPE_HAMSTER, MzHamsterClass ) )

typedef struct _MzHamster      MzHamster;
typedef struct _MzHamsterClass MzHamsterClass;

struct _MzHamster
{
  GObject parent;
};

struct _MzHamsterClass
{
  GObjectClass parent_class;
};

GType mz_hamster_get_type( void ) G_GNUC_CONST;

MzHamster * mz_hamster_new( const gchar *name );

gchar * mz_hamster_get_name( MzHamster *hamster );
void mz_hamster_set_name( MzHamster *hamster, const gchar *name );

G_END_DECLS

#endif	/* __MZ_HAMSTER_H__ */

source code of mz-hamster.c:

#include <glib/gprintf.h>
#include "mz-hamster.h"

#define MZ_HAMSTER_GET_PRIVATE( obj )	  ( G_TYPE_INSTANCE_GET_PRIVATE( (obj), MZ_TYPE_HAMSTER, MzHamsterPrivate ) )

G_DEFINE_TYPE( MzHamster, mz_hamster, G_TYPE_OBJECT )

typedef struct _MzHamsterPrivate MzHamsterPrivate;

struct _MzHamsterPrivate
{
  gchar *name;
};

enum
{
  PROP_0,

  PROP_NAME
};

static void mz_hamster_finalize( GObject *hamster );

static void
mz_hamster_set_property( GObject *object,
			 guint prop_id,
			 const GValue *value,
			 GParamSpec *pspec );

static void
mz_hamster_get_property( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec );

static void 
mz_hamster_class_init( MzHamsterClass *klass )
{
  GObjectClass *gobject_class = G_OBJECT_CLASS( klass );

  gobject_class->finalize = mz_hamster_finalize;

  g_type_class_add_private( klass, sizeof( MzHamsterPrivate ) );

  gobject_class->get_property = mz_hamster_get_property;
  gobject_class->set_property = mz_hamster_set_property;

  g_object_class_install_property( gobject_class,
				   PROP_NAME,
				   g_param_spec_string( "name",
							"Name",
							"hamster's name",
							NULL,
							G_PARAM_READWRITE ) );
}

static void
mz_hamster_init( MzHamster *hamster )
{
  MzHamsterPrivate *priv = MZ_HAMSTER_GET_PRIVATE( hamster );

  g_printf( "...instance init" );

  priv->name = g_strdup( "unnamed" );
}
  
static void
mz_hamster_finalize( GObject *object )
{
  MzHamster *hamster = MZ_HAMSTER( object );
  MzHamsterPrivate *priv = MZ_HAMSTER_GET_PRIVATE( hamster );
  GObjectClass *parent_class = G_OBJECT_CLASS( mz_hamster_parent_class );

  g_printf( "...instance finalize\n" );

  g_free( priv->name );

  ( *parent_class->finalize )( object );
}

MzHamster *
mz_hamster_new( const gchar *name )
{
  MzHamster *hamster;

  hamster = MZ_HAMSTER( g_object_new( MZ_TYPE_HAMSTER, NULL ) );

  if( name != NULL )
    mz_hamster_set_name( hamster, name );
  
  return hamster;
}

gchar *
mz_hamster_get_name( MzHamster *hamster )
{
  MzHamsterPrivate *priv;
  
  g_return_val_if_fail( MZ_IS_HAMSTER( hamster ), NULL );

  priv = MZ_HAMSTER_GET_PRIVATE( hamster );

  return g_strdup( priv->name );
}

void
mz_hamster_set_name( MzHamster *hamster, const gchar *name )
{
  MzHamsterPrivate *priv;
  
  g_return_if_fail( name );
  g_return_if_fail( MZ_IS_HAMSTER( hamster ) );

  priv = MZ_HAMSTER_GET_PRIVATE( hamster );

  if( priv->name != NULL )
    g_free( priv->name );

  priv->name = g_strdup( name );
}

static void
mz_hamster_set_property( GObject *object,
			 guint prop_id,
			 const GValue *value,
			 GParamSpec *pspec )
{
  MzHamster *hamster = MZ_HAMSTER( object );

  switch( prop_id ) {
    case PROP_NAME:
      mz_hamster_set_name( hamster, g_value_get_string( value ) );
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
  }
}
      
static void
mz_hamster_get_property( GObject *object,
			 guint prop_id,
			 GValue *value,
			 GParamSpec *pspec )
{
  MzHamster *hamster = MZ_HAMSTER( object );

  switch( prop_id ) {
    case PROP_NAME:
      g_value_set_string( value, mz_hamster_get_name( hamster ) );
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
  }
}

main.c file:

#include <glib-object.h>
#include <glib/gprintf.h>

#include "mz-hamster.h"

int main( void )
{
  MzHamster *hamster;
  gchar *name;
  GValue value = { 0, };	/* GValue instance must contain zeros! */

  g_type_init( );

  hamster = mz_hamster_new( NULL );
  
  name = mz_hamster_get_name( hamster );
  g_printf( "name: %s\n", name );
  g_free( name );

  mz_hamster_set_name( hamster, "хoмяк" );
  name = mz_hamster_get_name( hamster );
  g_printf( "new name: %s\n", name );

  g_free( name );
  g_object_unref( G_OBJECT( hamster ) );
  
  g_printf( "bye!\n" );

  g_printf( "...checking properties...\n" );
  hamster = mz_hamster_new ( "karry" );
  
  g_value_init( &value, G_TYPE_STRING );
  g_value_set_string( &value, "papa" );
  g_object_set_property( G_OBJECT( hamster ), "name", &value );
  name = mz_hamster_get_name( hamster );
  g_printf( "new name: %s\n", name ); 
  
  mz_hamster_set_name( hamster, "mama" );
  g_object_get_property( G_OBJECT( hamster ), "name", &value );
  name = g_value_dup_string( &value );
  g_printf( "another hamster named: %s\n", name );
  
  return 0;
}

makefile:

SHELL=/bin/bash

deps=$(shell pkg-config --libs --cflags gobject-2.0 )
flags=-Wall -pedantic

hamster: main.c mzhamster.o
	gcc -o $@ $^ ${deps} ${flags}

%.o: %.c %.h
	gcc -o $@ -c $< ${deps} ${flags}

clean:
	rm -rf *.o hamster

output from the shell:

$ ./hamster 
...instance init
name: unnamed
new name: хoмяк
...instance finalize
bye!
...checking properties...
...instance init
new name: papa
another hamster named: mama

In the object oriented language like Vala this example looks rather trivial (property.vala):

namespace Mz {
	public class Hamster : Object {
		// Definition of a property with a standard get and set methods
		public string name { get; set; }
		
		public Hamster( string name = "unnamed" ) {
			stdout.printf( "...instance init\n" );
			Object( name: name );
		}
		
		// Destructor
		~Hamster( ) {
			stdout.printf( "...instance named %s is finalized\n", this.name );
		}
		
		public static void main( string[] args ) {
			var hamster = new Hamster( );
			
			stdout.printf( "name: %s\n", hamster.name );
			
			hamster.name = "хoмяк";
			stdout.printf( "new name: %s\n", hamster.name );
			
			hamster = new Hamster( "karry" );
			stdout.printf( "bye\n" );
			
			stdout.printf( "...checking properties...\n" );
			Value svalue = "papa";
			hamster.set_property( "name", svalue );
			stdout.printf( "new name: %s\n", hamster.name );
			
			hamster.set( "name", "mama" );
			string name;
			hamster.get( "name", out name );
			stdout.printf( "another hamster named: %s\n", name );
		}
	}
}

This one can be easiliar compiled with:

valac-0.14 property.vala

Vala compiler is also able to generate the corresponding C code by invoiking the command:

 valac-0.14 property.vala --ccode

By doing so the file “property.c” will be created containing a similiar C code, introduced above. There are more detailed examples how properties can be defined in Vala, shown here. Line 28 shows, that Vala has a build-in auto-(un)boxing support for Glib’s Value structure, which saves you from writing the following lines:

// to initialize structures no "new" keyword is needed 
Value svalue = Value( typeof( string ) );
svalue.set_string( "papa" );

Further examples of using GLib.Value structure can be found here.

By comparing the amount of code needed to create a custom class written in C with the code written in a object-oriented language like python, C++ or Vala very often the question arises: “What is the benefit of writing so many complicated at the first look and unnecessary lines of code in C to create a class, if in another language it is almost a trivial task?”

If one knows the background hiding behind this decision to still write in C, it’s very easy to understand this situation. First of all, C is a very tiny programming language without any modern language features. It’s available almost on every device, and if not, it can be rather easily integrated than other heavier languages. Additionally, big part of gnu/linux based operating systems are written in C, including a rich collections of libraries, which can be integrated in the new C based projects. GObject, on the other side, is the one of the possible ways bringing the object-oriented capabilities to the C programming language, which allows the use of the modern design. The model used in GObject should in first place simplify the construction and intregration of binding to another languages, so the libraries on top of it, e.g. Gtk+, can be easily used to create window based applications.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s