Monikers

Los "monikers" provienen de la terminología de OLE2, el sistema de componentes de MS Windows. En Bonobo, se han tomado las ideas iniciales de MS a la hora de desarrollarlos, y se han añadido extensiones que nos llevan a lo que es hoy el sistema de "monikers" de Bonobo: un sistema de referenciación de objetos.

Como podremos comprobar cuando hayamos terminado de leer este artículo, el sistema de "monikers" se integra a la perfección en un entorno basado en componentes, como es el caso de Bonobo, y, por consiguiente, de GNOME.

En un entorno basado en componentes, es necesario el poder activar fácilmente objetos desde nuestros programas. Para ello, vimos en artículos anteriores que tenemos la posibilidad de usar OAF directamente (nivel más bajo de la aquitectura, junto con ORBit), y que Bonobo tambien incluía las funciones necesarias para facilitarnos la vida a la hora de activar componentes desde nuestros programas. Sin embargo, este sistema es insuficiente si queremos tener un acceso más detallado a los componentes instalados en nuestro sistema, así como la información manejada por ellos.

Representación de los monikers

Los "monikers" son representados por una cadena de texto, y son activados mediante la función bonobo_get_object. Así, el formato de un moniker es el siguiente:

	  método:representación_del_moniker
	

donde "método" es la cadena que define el moniker de más alto nivel que queremos activar. ¿Alto nivel? Bien, los "monikers" pueden ser activados "en cadena", es decir, que, dada una cadena de texto representando un moniker, ésta puede activar un moniker, y éste, a su vez, puede activar a su vez otros "monikers" (y estos a su vez, activar otros, etc). Así, observemos varios ejemplos:

  • oafiid:GNOME_Evolution_CalendarFactory:1.0: representa una instancia del componente cuyo OAFIID (Identificador de la implementación de un objeto OAF) sea GNOME_Evolution_CalendarFactory:1.0.

  • oafiid:GNOME_Evolution_CalendarFactory:1.0:new: representa una NUEVA instancia (al contrario que el anterior moniker, que sólo activaría una nueva instancia si no existiera ninguna) del componente especificado.

  • file:/home/admin/sales.gnumeric!sheet1: representa la hoja llamada "sheet1" de la hoja de cálculo almacenada en el fichero /home/admin/sales.gnumeric.

  • file:/tmp/download.gz: representa el fichero /tmp/download.gz.

  • file:/tmp/download.gz#gunzip: representa el contenido descomprimido del fichero /tmp/download.gz.

En esta lista, se observa que no hay un formato bien definido de cómo se separan los distintos monikers dentro de la cadena de representación de los mismos, excepto con el moniker de más alto nivel, que siempre consiste en el nombre del moniker seguido del carácter ':'. Esto es completamente intencionado, de forma que cada moniker pueda usar la sintaxis que más le plazca, lo que permite al sistema de "monikers" de Bonobo integrarse a la perfección en otros sistemas de referenciación estándar como pueden ser, por ejemplo, los protocolos HTTP, FTP, etc. De esta forma, podríamos tener, por ejemplo, los siguientes monikers:

  • ftp://ftp.gnome.org

  • http://www.gnome.org/bonobo.html#Section1

En este caso, el "moniker" http utiliza el carácter '#' para la separación de los distintos componentes del "moniker" a activar, de esta forma, integrándose perfectamente en la sintaxis usada en el protocolo HTTP.

Todas estas cadenas expuestas anteriormente nos permiten la referenciación del objeto en cuestión que queremos activar. Pero lo que hace realmente interesante el uso de "monikers" en Bonobo es que, a la hora de activar un "moniker", no sólo especificamos la cadena que representa al objeto que queremos activar, sino que además especificamos la "forma" que tiene que tener ese objeto cuando nos sea devuelto. Todo esto lo veremos mejor con algunos ejemplos:

	  objeto = bonobo_get_object("file:/tmp/download.gz#gunzip", "IDL:Bonobo/Stream:1.0");
	  objeto = bonobo_get_object("http://www.gnome.org", "IDL:Bonobo/Control:1.0");
	  objeto = bonobo_get_object("config:/gnome/background/image", "IDL:Bonobo/Property:1.0");
	

Aquí se puede observar claramente la utilidad de los "monikers", como es la posibilidad de especificar el comportamiento exacto que deseamos del objeto a activar, aparte de la posibilidad de tratar a un mismo objeto de distintas maneras. Consideremos el siguiente ejemplo:

	  objeto = bonobo_get_object("http://www.gnome.org", "IDL:Bonobo/Stream:1.0");
	

En este caso, estamos pidiéndole al "moniker" http un Bonobo/Stream (que es un objeto desde el que podemos leer datos) con el contenido de la página referenciada por "www.gnome.org". Esto nos puede ser muy útil si, por ejemplo, en nuestro programa queremos obtener ficheros de servidores HTTP, datos que luego, de alguna manera, procesaremos en nuestro programa. Sin embargo, tambien puede darse el caso de que, en algun momento dado de nuestro programa, queramos mostrar el contenido de una página web. Una solución, tambien proporcionada por Bonobo, sería usar el componente de edición de GtkHTML. Otra, sería intentar activar un "moniker" que nos devuelva un visualizador HTML (un control Bonobo) mostrando el contenido de la página que hayamos pedido en la activación del "moniker". En código, esto se traduciría a:

	  objeto = bonobo_get_object("http://www.gnome.org", "Bonobo/Control:1.0");
	

Con esta línea de código, obtendríamos, en la variable "objeto", un control Bonobo que podría ser perfectamente el componente GtkHTML mencionado anteriormente.

bonobo-conf

Uno de los ejemplos más significativos de los "monikers" en Bonobo, es, sin duda, bonobo-conf, un proyecto que ha nacido con la intención de incluir en el modelo de componentes en que se está convirtiendo GNOME (gracias a Bonobo) el acceso a toda la configuración del usuario. Bonobo-conf es la implementación del "moniker" config:, del cual veiamos antes algunos ejemplos.

Bonobo-conf soporte la activación de componentes de tres formas (o interfaces) distintas:

  • Bonobo:Property:1.0: de esta forma, obtendremos un objeto a través del cual podremos leer y modificar la información asociada con una entrada específica de nuestra configuración:

    		Bonobo_Property prop;
                    CORBA_Any value;
                    CORBA_Environment ev;
    
                    property = bonobo_get_object("config:/gnome/UI/BackgroundImage", "IDL:Bonobo/Property:1.0", &ev);
                    value = bonobo_property_get_value(property, &ev);
    	      

  • Bonobo:PropertyBag:1.0: Bonobo::PropertyBag es un interfaz que contiene una lista de Bonobo::Property's. Por tanto, es especialmente útil en bonobo-conf, pues nos permite obtener una lista de propiedades almacenadas en nuestro sistema de configuración.

  • Bonobo:Control:1.0: este interfaz es el más interesante de todos, pues permite activar un control Bonobo que presenta una interfaz gráfica para configurar la opción de configuración seleccionada en el "moniker". Esto nos permitirá, por ejemplo, construir nuestras ventanas de configuración dinámicamente, simplemente reservando un espacio para cada uno de los controles Bonobo que activemos a través de bonobo-conf.

Bonobo-conf es una de las implementaciones de "monikers" más completas a fecha de hoy, por lo que es aconsejable que indaguemos en su código fuente para conocer más a fondo los entresijos de la implementación de "monikers".

Implementación de monikers

Para aprender cómo se implementan los "monikers" nos vamos a fijar en uno de los "monikers" estándar que se incluyen en Bonobo: el moniker http, que, como hemos visto anteriormente, nos permite referenciar objetos que se crean a partir del contenido de una petición HTTP.

El "moniker" http podemos encontrarlo incluido con el resto de fuentes de Bonobo, en el directorio monikers, exactamente en los ficheros bonobo-moniker-http.h y bonobo-moniker-http.c. Este último es el que más nos interesa, pues en él está el código de implementación del "moniker".

Para empezar, podremos observar, al final de dicho archivo, las siguientes líneas:

	  BONOBO_OAF_FACTORY ("OAFIID:Bonobo_Moniker_http_Factory",
	                      "http-moniker", VERSION,
	                      bonobo_moniker_http_factory,
	                      NULL)
	

Esta línea, que puede parecer un poco extraña en un programa en C, no es más que una macro (definida con #define) que genera la función main necesaria para la creación de una factoría. Esta macro está definida en el fichero bonobo-generic-factory.h de la siguiente manera:

	  #define BONOBO_OAF_FACTORY(oafiid, descr, version, fn, data)
	  int main (int argc, char *argv [])
	  {
                BonoboGenericFactory *factory;
                CORBA_Environment ev;
                CORBA_ORB orb;

                CORBA_exception_init (&ev);
                gnome_init_with_popt_table (descr, version, argc, argv,
                                            oaf_popt_options, 0, NULL);
                orb = oaf_init (argc, argv);
                if (!bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL))
                        g_error (_("Could not initialize Bonobo"));
                factory = bonobo_generic_factory_new (oafiid, fn, data);
                bonobo_running_context_auto_exit_unref (BONOBO_OBJECT (factory));
                bonobo_main ();
                CORBA_exception_free (&ev);
                return 0;
	  }
	

Como se puede observar, el código generado es exactamente el mismo que escribíamos en artículos anteriores para crear nuestras factorias.

Así pues, con esta macro hemos creado nuestra factoría de objetos, cuya función (bonobo_moniker_http_factory) será llamada cada vez que una aplicación desee crear un "moniker" http. Dicha función tiene la siguiente forma:

	  static BonoboObject *
	  bonobo_moniker_http_factory (BonoboGenericFactory *this, void *closure)
	  {
	       return BONOBO_OBJECT (bonobo_moniker_simple_new (
	                             "http:", http_resolve));
	  }
	

Como se puede ver, en la función de creación de objetos de nuestra factoría simplemente llamamos a la función bonobo_moniker_new, que devuelve un BonoboMoniker. Esta función recibe dos parámetros, el primero es la cadena que representa al "moniker" que estamos creando, y que identificará a nuestro "moniker" a la hora de la activación. El segundo parámetro es un puntero a una función, que será la encargada de devolver un objeto cuando una aplicación esté pidiendo la activación de un "moniker" http. Vamos a ver esta función:

	  static Bonobo_Unknown
	  http_resolve (BonoboMoniker *moniker,
                      const Bonobo_ResolveOptions *options,
                      const CORBA_char *requested_interface,
                      CORBA_Environment *ev)
	  {
                const char *url = bonobo_moniker_get_name (moniker);
                char *real_url;

                g_warning ("Going to resolve the http now");

                /* because resolving the moniker drops the "http:" */
                real_url = g_strconcat ("http:", url, NULL);

                if (strcmp (requested_interface, "IDL:Bonobo/Control:1.0") == 0) {
                        BonoboObjectClient *client;
                        Bonobo_Unknown object;

                        client = bonobo_object_activate ("OAFIID:GNOME_GtkHTML_EBrowser", 0);
                        if (!client) {
                                /* FIXME: Set a InterfaceNotFound exception here? */
                                return CORBA_OBJECT_NIL;
                        }

                        object = BONOBO_OBJREF (client);

                        if  (ev->_major != CORBA_NO_EXCEPTION)
                                return CORBA_OBJECT_NIL;

                        if  (object == CORBA_OBJECT_NIL) {
                                g_warning ("Can't find object satisfying requirements");
                                CORBA_exception_set  (
                                        ev, CORBA_USER_EXCEPTION,
                                        ex_Bonobo_Moniker_InterfaceNotFound, NULL);
                                return CORBA_OBJECT_NIL;
                        }

                        return bonobo_moniker_util_qi_return (object, requested_interface, ev);
                }
                else if (strcmp (requested_interface, "IDL:Bonobo/Stream:1.0") == 0) {
                        BonoboStream *stream;

                        stream = bonobo_stream_open_full (
                                "http", real_url, Bonobo_Storage_READ, 0644, ev);

                        if (!stream) {
                                g_warning ("Failed to open stream '%s'", real_url);
                                g_free (real_url);
                                CORBA_exception_set (
                                        ev, CORBA_USER_EXCEPTION,
                                        ex_Bonobo_Moniker_InterfaceNotFound, NULL);
                                return CORBA_OBJECT_NIL;
                        }

                        g_free (real_url);
                        return CORBA_Object_duplicate (BONOBO_OBJREF (stream), ev);
                }

                return CORBA_OBJECT_NIL;
	  }
	

Si leemos detenidamente el código de esta función, veremos que la implementación de "monikers" es realmente sencilla. Con estas pocas líneas, estamos realizando un montón de cosas, todo ello gracias a la enorme funcionalidad del API de Bonobo.

Lo primero que debemos hacer es comparar el parámetro requested_interface con los interfaces que nuestro "moniker" maneje. Es decir, este parámetro contendrá el valor que se le haya pasado como segundo parámetro a la función bonobo_get_object en la aplicación que está intentando cargar el objeto a través de nuestro "moniker". Por tanto, y puesto que la aplicación que está cargando el objeto espera que dicho objeto implemente determinado interfaz, debemos prestar especial atención a este parámetro, pues deberemos crear el objeto apropiado para cada ocasión. Por tanto, podemos deducir que el "moniker" http permite la creación de objetos (todos ellos basados, de alguna forma, en el contenido de recursos obtenidos via protocolo HTTP) de dos tipos: IDL:Bonobo/Control:1.0 y IDL:Bonobo/Stream:1.0.

El primero de ellos (IDL:Bonobo/Control:1.0) realiza una tarea muy útil, que consiste en devolver un control Bonobo que implementa un visualizador HTML. En este caso, lo que hace es crear una instancia del componente GNOME_GtkHTML_EBrowser, que viene incluido junto con GtkHTML, y que es usado, por ejemplo, en Evolution. Si usamos de esta forma el "moniker" http desde nuestras aplicaciones, podremos fácilmente cargar un visualizador de HTML e insertarlo en nuestras ventanas. Para realizar eso, en la implementación del "moniker" lo único que hay que hacer es activar el objeto que queremos, mediante la llamada a la función bonobo_object_activate, y luego, simplemente obtener una referencia a la implementación del interfaz que queramos (en este caso, IDL:Bonobo/Control:1.0) y devolver esa referencia al sistema de "monikers", todo ello mediante la llamada a la función bonobo_moniker_util_qi_return. Esto último es debido a que, como vimos en artículos anteriores, bonobo_object_activate devuelve un puntero a un BonoboObjectClient, y nosotros queremos la referencia a un determinado interfaz implementado por ese objeto, para lo que tenemos que hacer uso del método queryInterface del interfaz Bonobo::Unknown, que es precisamente lo que hace la función bonobo_moniker_util_qi_return.

En el segundo caso, el "moniker" http permite la creación de BonoboStream a partir del contenido de una petición HTTP. Con este sistema, se podría implementar fácilmente un servidor de caché, que aceptara peticiones HTTP de otras aplicaciones, y que o bien obtuviera los datos directamente de Internet, o de una copia local, si tuviera una disponible.

Como vemos, para la creación de BonoboStream, Bonobo tambien incluye las funciones necesarias para facilitarnos la vida. En este caso, simplemente usamos la función bonobo_stream_open_full, que crea un BonoboStream a partir de los datos que le pasamos. Luego, simplemente llamamos a la función de ORBit CORBA_Object_duplicate para crear un duplicado del objeto CORBA creado con el BonoboStream.

Tras la explicación de este código, vemos que la mayor parte del código que tenemos que escribir para implementar un "moniker" está más bien relacionado con la gestión de errores, pues la creación/activación de objetos en Bonobo es de lo más sencilla.

Otros "monikers"

Desde que se estabilizó el API del sistema de "monikers" de Bonobo, han ido apareciendo implementaciones de distintos "monikers" que ofrecen funcionalidad muy diversa. Forman parte de la distribución de Bonobo los llamados "monikers" estándar, que son: http, gunzip, file, oaf, cache, item y query, algunos de los cuales han sido comentados en este artículo.

Por otro lado, en otros programas basados en Bonobo se están empezando a incluir "monikers" para permitir el acceso a la información gestionada por dichos programas a través de este novedoso sistema de activación de componentes. Entre estos programas, destaca sin duda gPhoto, que implementa el "moniker" camera, que permite el acceso a las fotos de nuestra cámara digital. Tambien destaca el "moniker" database, implementado como parte de GNOME-DB, aunque éste aun no está del todo funcional, por lo que su utilidad de momento es mínima.