Gestión dinámica de tipos

GLib incluye un sistema dinámico de tipos, que no es más que una base de datos en la que se van registrando las distintas clases. En esa base de datos, se almacenan todas las propiedades asociadas a cada tipo registrado, información tal como las funciones de inicialización del tipo, el tipo base del que deriva, el nombre del tipo, etc. Todo ello identificado por un identificador único, conocido como GType.

Tipos basados en clases (objetos).

Para crear instancias de una clase, es necesario que el tipo haya sido registrado anteriormente, de forma que esté presente en la base de datos de tipos de GLib. El registro de tipos se hace mediante una estructura llamada GTypeInfo, que tiene la siguiente forma:

   1 struct _GTypeInfo
   2 {
   3         /* interface types, classed types, instantiated types */
   4         guint16                class_size;
   5         GBaseInitFunc          base_init;
   6         GBaseFinalizeFunc      base_finalize;
   7         /* classed types, instantiated types */
   8         GClassInitFunc         class_init;
   9         GClassFinalizeFunc     class_finalize;
  10         gconstpointer          class_data;
  11         /* instantiated types */
  12         guint16                instance_size;
  13         guint16                n_preallocs;
  14         GInstanceInitFunc      instance_init;
  15         /* value handling */
  16         const GTypeValueTable *value_table;
  17 };

La mayor parte de los miembros de esta estructura son punteros a funciones. Por ejemplo, el tipo GClassInitFunc es un tipo definido como puntero a función, que se usa para la función de inicialización de las clases.

Para comprender esta estructura, lo mejor es ver un ejemplo de cómo se usa. Para ello, se usan las siguientes funciones:

   1 GType g_type_register_static (GType            parent_type,
   2                                 const gchar     *type_name,
   3                                 const GTypeInfo *info,
   4                                 GTypeFlags       flags);
   5 GType g_type_register_fundamental (GType                       type_id,
   6                                 const gchar                *type_name,
   7                                 const GTypeInfo            *info
   8                                 const GTypeFundamentalInfo *finfo,
   9                                 GTypeFlags                  flags);

Con estas dos funciones y la estructura GTypeInfo se realiza el registro de nuevos tipos en GLib y, todo ello normalmente, se realiza en la función _get_type de la clase que se esté creando. Esta función es la que se usará posteriormente para referenciar al nuevo tipo, y tiene, normalmente, la siguiente forma:

   1 GType
   2 my_object_get_type (void)
   3 {
   4         static GType type = 0;
   5         if (!type) {
   6                 static GTypeInfo info = {
   7                         sizeof (MyObjectClass),
   8                         (GBaseInitFunc) NULL,
   9                         (GBaseFinalizeFunc) NULL,
  10                         (GClassInitFunc) my_object_class_init,
  11                         NULL, NULL,
  12                         sizeof (MyObject),
  13                         0,
  14                         (GInstanceInitFunc) my_object_init
  15                 };
  16                 type = g_type_register_static (PARENT_TYPE, "MyObject", info, 0);
  17         }
  18         return type;
  19 }

Como se puede apreciar, esta función simplemente tiene una variable (type) estática, que se inicializa a 0 y, cuando se le llama, si dicha variable es igual a 0, entonces registra el nuevo tipo (llamando a g_type_register_static) y rellena una estructura de tipo GTypeInfo antes, con los datos de la nueva clase, tales como el tamaño de la estructura de la clase (MyObjectClass), la función de inicialización de la clase (my_object_class_init), el tamaño de la estructura de las instancias de la clase (MyObject) y la función de inicialización de las instancias que se creen de esta clase (my_object_init).

Una vez que se tiene la función que registra la clase, crear instancias de esa clase es tan sencillo como llamar a la función g_object_new, que tiene la siguiente forma:

   1 gpointer g_object_new (GType        object_type,
   2                         const gchar *first_property_name,
   3                         ...);

Esta función tiene una lista variable de argumentos, que sirve para especificar una serie de propiedades a la hora de crear el objeto. Pero, de momento, lo único interesante de g_object_new es conocer su funcionamiento. Por ejemplo, para crear una instancia de la clase MyObject, una vez que se tiene la función de registro de la clase (my_object_get_type), no hay más que llamar a g_object_new de la siguiente forma:

   1 GObject *obj;
   2 obj = g_object_new (my_object_get_type (), NULL);

De esta forma, se le pide a GLib que cree una nueva instancia de la clase identificada por el valor devuelto por la función my_object_get_type, que será el valor devuelto por g_type_register_static, tal y como se mostraba anteriormente.

Tipos no instanciables (fundamentales).

Muchos de los tipos que se registran no son directamente instanciables (no se pueden crear nuevas instancias) y no están basados en una clase. Estos tipos se denominan tipos "fundamentales" en la terminología de GLib y son tipos que no están basados en ningún otro tipo, tal y como sí ocurre con los tipos instanciables (u objetos).

Entre estos tipos fundamentales se encuentran algunos viejos conocidos, como por ejemplo gchar y otros tipos básicos, que son automáticamente registrados cada vez que se inicia una aplicación que use GLib.

Como en el caso de los tipos instanciables, para registrar un tipo fundamental es necesaria una estructura de tipo GTypeInfo, con la diferencia que para los tipos fundamentales bastará con rellenar con ceros toda la estructura, excepto el campo value_table.

   1 GTypeInfo info = {
   2         0,                              /* class_size */
   3         NULL,                   /* base_init */
   4         NULL,                   /* base_destroy */
   5         NULL,                   /* class_init */
   6         NULL,                   /* class_destroy */
   7         NULL,                   /* class_data */
   8         0,                              /* instance_size */
   9         0,                              /* n_preallocs */
  10         NULL,                   /* instance_init */
  11         NULL,                   /* value_table */
  12 };
  13 static const GTypeValueTable value_table = {
  14         value_init_long0,               /* value_init */
  15         NULL,                   /* value_free */
  16         value_copy_long0,               /* value_copy */
  17         NULL,                   /* value_peek_pointer */
  18         "i",                    /* collect_format */
  19         value_collect_int,      /* collect_value */
  20         "p",                    /* lcopy_format */
  21         value_lcopy_char,               /* lcopy_value */
  22         };
  23 info.value_table = value_table;
  24 type = g_type_register_fundamental (G_TYPE_CHAR, "gchar", info, finfo, 0);

La mayor parte de los tipos no instanciables estan diseñados para usarse junto con GValue, que permite asociar fácilmente un tipo GType con una posición de memoria. Se usan principalmente como simples contenedores genéricos para tipos sencillos (números, cadenas, estructuras, etc.).

Implementación de nuevos tipos.

En el apartado anterior se mostraba la forma de registrar una nueva clase en el sistema de objetos de GLib, y se hacía referencia a distintas estructuras y funciones de inicialización. En este apartado, se va a indagar con más detalle en ellos.

Tanto los tipos fundamentales como los no fundamentales quedan definidos por la siguiente información:

Toda esta información queda almacenada, como se comentaba anteriormente, en una estructura de tipo GTypeInfo. Todas las clases deben implementar, aparte de la función de registro de la clase (my_object_get_type), al menos dos funciones que se especificaban en la estructura GTypeInfo a la hora de registrar la clase. Estas funciones son:

Interfaces.

Los interfaces GType son muy similares a los interfaces en Java o C#. Es decir, son definiciones de clases abstractas, sin ningún tipo de implementación, que definen una serie de operaciones que debe segir las clases que implementen dichos interfaces deben seguir.

En GLib, para declarar un interfaz, es necesario registrar un tipo de clase no instanciable, que derive de GTypeInterface. Por ejemplo:

   1 struct _GTypeInterface
   2 {
   3         GType g_type;         /* iface type */
   4         GType g_instance_type;
   5 };
   6 typedef struct
   7 {
   8         GTypeInterface g_iface;
   9         void (*method_a) (FooInterface *foo);
  10         void (*method_b) (FooInterface *foo);
  11 } FooInterface;
  12 static void foo_interface_method_a (FooInterface *foo)
  13 {
  14         foo->method_a ();
  15 }
  16 static void foo_interface_method_b (FooInterface *foo)
  17 {
  18         foo->method_b ();
  19 }
  20 static void foo_interface_base_init (gpointer g_class)
  21 {
  22         /* Initialize the FooInterface type. */
  23 }
  24 GType foo_interface_get_type (void)
  25 {
  26         GType foo_interface_type = 0;
  27         if (foo_interface_type == 0) {
  28                 static const GTypeInfo foo_interface_info =
  29                 {
  30                         sizeof (FooInterface),      /* class_size */
  31                         foo_interface_base_init,    /* base_init */
  32                         NULL,                       /* base_finalize */
  33                         NULL,
  34                         NULL,                       /* class_finalize */
  35                         NULL,                       /* class_data */
  36                         0,                          /* instance_size */
  37                         0,                          /* n_preallocs */
  38                         NULL                        /* instance_init */
  39                 };
  40                 foo_interface_type = g_type_register_static (G_TYPE_INTERFACE, "FooInterface",
  41                         foo_interface_info, 0);
  42         }
  43         return foo_interface_type;
  44 }

Un interfaz queda definido por una sola estructura cuyo primer miembro deber ser del tipo GTypeInterface, que es la clase base (ver la >) para los interfaces. Aparte de este primer miembro, la estructura que define el interfaz debe contener punteros a las funciones definidas en el interfaz. Es decir, los métodos del interfaz. Aparte de eso, como muestra el ejemplo anterior, constituye buena práctica incluir funciones que encapsulen el acceso a esos punteros a funciones, tal y como hacen las funciones foo_interface_method_a y foo_interface_method_b del ejemplo anterior. Aunque esto no es obligatorio, es aconsejable, pues ayuda en la legibilidad del código donde se haga uso del interfaz.

Hasta aquí, la definición del interfaz, que, como se puede apreciar, no contiene ninguna implementación. Simplemente, contiene la definición de los métodos que deben ser implementados para soportar este interfaz. Tras esto, para que el interfaz definido sea útil, es necesario hacer que otras clases definidas implementen dicho interfaz. Para ello, se usa la función g_type_add_interface_static, tal y como se muestra en el siguiente fragmento de código, en la función attach_interface_to_type:

   1 static void
   2 implementation_method_a (FooInterface *iface)
   3 {
   4         /* Implementación de método A */
   5 }
   6 static void
   7 implementation_method_b (FooInterface *iface)
   8 {
   9         /* Implementación de método B */
  10 }
  11 static void
  12 foo_interface_implementation_init (gpointer g_iface,
  13         gpointer iface_data)
  14 {
  15         FooInterface *iface = (FooInterface *) g_iface;
  16         iface->method_a = implementation_method_a;
  17         iface->method_b = implementation_method_b;
  18 }
  19 static void attach_interface_to_type (GType type)
  20 {
  21         static const GInterfaceInfo foo_interface_implementation_info =
  22         {
  23                 (GInterfaceInitFunc) foo_interface_implementation_init,
  24                 NULL,
  25                 NULL
  26         };
  27         g_type_add_interface_static (type,
  28                 foo_interface_get_type (),
  29                 foo_interface_implementation_info);
  30 }

g_type_add_interface_static registra, dentro del sistema de tipos de GLib que un determinado tipo implementa un determinado interfaz. Antes de eso, es necesario rellenar una estructura de tipo GInterfaceInfo, que contiene todos los datos (funciones de inicialización y finalización, así como datos de contexto a los que se podrá acceder desde cualquier punto, a través de la implementación del interfaz) referentes a una implementación concreta del interfaz:

   1 struct _GInterfaceInfo
   2 {
   3         GInterfaceInitFunc     interface_init;
   4         GInterfaceFinalizeFunc interface_finalize;
   5         gpointer               interface_data;
   6 };

Herencia.

Un sistema orientado a objetos que se precie debe, de alguna forma, ofrecer la posibilidad de crear nuevas clases que contengan la funcionalidad base de otras clases ya existentes. Esto se conoce como herencia en la terminología de la POO y permite la reutilización de funcionalidad ya existente, de forma que, simplemente, haya que añadir la funcionalidad extra requerida.

En GLib, desarrollada en C, esto se consigue mediante el uso de estructuras, como se ha visto anteriormente, y usando, como primer miembro de la estructura que identifica a la nueva clase, un valor del mismo tipo que la clase base. Por ejemplo, si se quisiera crear una clase ClaseB que derivara de ClaseA, la nueva clase debería definirse de la siguiente manera:

   1 struct ClaseA {
   2         ...
   3 };
   4 struct ClaseAClass {
   5         ...
   6 };
   7 ...
   8 struct ClaseB {
   9         struct ClaseA base;
  10 };
  11 struct ClaseBClass {
  12         struct ClaseAClass base_class;
  13 };

Éste es el primer paso para usar la herencia. De esta forma, como el primer miembro de la clase derivada es del tipo de la clase base, se puede convertir ("casting" en la terminología del lenguaje C) de un tipo a otro indistintamente. Así, por ejemplo:

   1 ClaseA *clasea;
   2 ClaseB *claseb;
   3 claseb = g_object_new (claseb_get_type (), NULL);
   4 clasea = (ClaseA *) claseb;

Los demás pasos para conseguir la herencia ya han sido comentados anteriormente y se reducen a especificar la clase base de la nueva clase al registrarla (g_type_register_static).


Documentacion/Desarrollo/SistemaDeObjetosDeGlib/GestionDinamicaDeTipos (última edición 2008-12-04 08:49:03 efectuada por localhost)