Diferencias entre las revisiones 2 y 3
Versión 2 con fecha 2008-12-04 08:48:53
Tamaño: 2771
Editor: anónimo
Comentario: converted to 1.6 markup
Versión 3 con fecha 2009-08-30 17:56:57
Tamaño: 3210
Comentario: Añadir tareas pendientes
Los textos eliminados se marcan así. Los textos añadidos se marcan así.
Línea 1: Línea 1:
{{{
ARTÍCULO EN DESARROLLO

Si quieres colaborar con nosotros completando este apartado o cualquier otra parte del Curso, puedes informarte en el enlace siguiente.
}}}
~-[[Documentacion/Desarrollo|>[Documentación para desarrolladores]]]-~


{{{
CONTENIDO
Los contenidos y tareas pendientes se indican a continuación:

/0710 Parámetros y valores.

Todos estos apartados están pendientes de redactar.

}}}





ARTÍCULO EN DESARROLLO

Si quieres colaborar con nosotros completando este apartado o cualquier otra parte del Curso, puedes informarte en el enlace siguiente.

>[Documentación para desarrolladores]

CONTENIDO
Los contenidos y tareas pendientes se indican a continuación:

/0710 Parámetros y valores.

Todos estos apartados están pendientes de redactar. 

Sistema de objetos de GLib

Una de las técnicas más modernas y utilizadas en la actualidad en la programación es lo que se denomina "programación orientada a objetos", o POO, que consiste en enfocar la programación de una forma mucho más cercana a la percepción real de las personas (los programadores), usando el concepto de objetos para encapsular las distintas funcionalidades de las aplicaciones.

La POO permite estructurar los programas de una forma mucho más clara para la percepción humana que la programación "tradicional" (programación estructurada) de forma que, cada funcionalidad específica de la aplicación, está claramente separada y encapsulada (implementación oculta al resto de la aplicación). Esto permite varias cosas:

  • Desarrollar cada una de las partes de la aplicación independientemente del resto.

La POO normalmente está asociada a lenguajes de programación que la soportan, tales como Java, C++, SmallTalk, etc. Sin embargo, al ser una técnica realmente potente para el desarrollo de aplicaciones, en el proyecto GNOME, y más concretamente en GTK+, se decidió usar dicha técnica en el lenguaje más usado dentro del proyecto: C. Éste no es un lenguaje preparado para POO, pero con un esfuerzo mínimo, y gracias al trabajo de los desarrolladores de GTK+, se puede usar en este lenguaje. Esto se consigue mediante el sistema de objetos de GLib (GObject), que, mediante el uso de las características del lenguaje C, ofrece la posibilidad de usar POO.

Este sistema de objetos de GLib tiene algunas limitaciones propias del uso del lenguaje C (lenguaje no pensado para la POO), pero aun así, su funcionalidad es tal, que es más que probable que jamás se eche ninguna funcionalidad en falta. Dicha funcionalidad incluye:

  • herencia, característica imprescindible de cualquier lenguaje orientado a objetos que se precie de serlo; permite la creación de clases que heredan la funcionalidad de otras clases ya existentes. Esto permite crear clases con la funcionalidad básica y, basadas en dicha clase, crear otras que añadan una funcionalidad más específica.

  • polimorfismo, que permite tratar a un mismo objeto bajo distintas personalidades.

  • interfaces, que permite la definición de interfaces (clases abstractas) y su posterior implementación en clases.

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:

  • Tamaño de la clase.
  • Funciones de inicialización (constructores en C++).
  • Funciones de destrucción (destructores en C++), llamadas de finalización en la jerga de GLib.
  • Tamaño de las instancias.
  • Normas de instanciación de objetos (uso del operador new en C++).
  • Funciones de copia.

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:

  • my_object_class_init: función de inicialización de la clase, donde se inicializacirá todo lo relacionado con la clase en sí, tal como las señales que tendrá la clase, los manejadores de los métodos virtuales si los hubiera, etc.

  • my_object_init: función de inicialización de las instancias de la clase, que será llamada cada vez que se solicite la creación de una nueva instancia de la clase. En esta función, las tareas a desempeñar son todas aquellas relacionadas con la inicialización de una nueva instancia de la clase, tales como los valores iniciales de las variables internas de la instancia.

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).

Señales

Las señales constituyen la forma que tienen las clases en GLib de informar de determinados cambios de estado o de requerir la ejecución de determinadas acciones. Pero antes de entrar en detalle sobre qué son y para qué sirven las señales, es necesario el estudio de una serie de conceptos, que se detallan a continuación.

GValue

GValue representa una forma única de contener un valor de cualquier tipo. Viendo su definición se comprueba mejor qué significa esto:

   1 struct _GValue
   2 {
   3         GType   g_type;
   4 
   5         /* public for GTypeValueTable methods */
   6         union {
   7                 gint    v_int;
   8                 guint   v_uint;
   9                 glong   v_long;
  10                 gulong  v_ulong;
  11                 gint64      v_int64;
  12                 guint64     v_uint64;
  13                 gfloat  v_float;
  14                 gdouble v_double;
  15                 gpointerv_pointer;
  16         } data[2];
  17 };
  18 
  19 GValue* g_value_init (GValue *value,
  20                         GType g_type);
  21 
  22 void g_value_unset (GValue *value);

Como puede observarse, GValue permite albergar valores de distintos tipos, todo ello en una unión de C. Lo interesante es que, a partir de esta simple organización (una estructura que contiene el tipo del valor almacenado y el valor), se pueden pasar valores de distintos tipos de un sitio a otro, usando un método común entre todas las partes involucradas.

Existen multitud de funciones relacionados con GValue, que permiten desde copiarlo (g_value_copy) a alterar su contenido fácilmente (g_value_get/_set).

GObject, la clase base

La base de todo este sistema de objetos es GObject, una clase que implementa todo lo básico, y que forma la base de todas las demás clases, tanto en GTK+ como en el resto de librerías de la plataforma GNOME.

GObject es una clase abstracta, es decir, una clase de la que no podemos crear instancias directamente, aunque sí contiene alguna implementación, como es toda la gestión del control de vida del objeto, de sus señales y manejadores, etc.

Documentacion/Desarrollo/SistemaDeObjetosDeGlib (última edición 2009-08-30 17:56:57 efectuada por DomingoGonzalez)