ORBit, la implementación de CORBA de GNOME

ORBit cumple con los dos requisitos del proyecto GNOME: es libre, y es uno de los ORBs más rápidos que existen, aparte de que es muy ligero (sólo añade 70 KB a los programas que lo usan), lo cual lo hace ideal para ser usado en un escritorio como GNOME. Además, consigue una velocidad casi idéntica a una simple llamada a función cuando se usa en local, pues detecta automáticamente que las comunicaciones se están realizando en la misma máquina, desactivando en ese caso gran parte de la complejidad del protocolo de comunicaciones usado en CORBA (GIOP/IIOP).

Como el resto de partes básicas de la arquitectura de GNOME, ORBit está implementado en C, y el lenguaje que soporta es C precisamente. Por tanto, en este artículo vamos a hablar de cómo usar ORBit desde C, pero hay que destacar que existe soporte para otros lenguajes de programación, como C++, Perl, PHP. Información sobre esto la podemos encontrar en la sección de referencias.

Cabos y esqueletos

Estamos hablando de independencia de lenguaje, de sistema operativo, etc. Pero, ¿cómo se consigue esto? La respuesta es muy sencilla: una vez que nos hagamos con un ORB de CORBA (Object Request Broker, o sea, una implementación del estándar CORBA, como es el caso de ORBit), simplemente usamos un programa especial que suelen incluir los ORBs, que se conoce como compilador IDL, y que genera una serie de ficheros en determinado lenguaje de programación, que luego compilaremos en nuestro sistema operativo/plataforma. Una vez hecho esto, toda la responsabilidad de realizar las comunicaciones entre las aplicaciones haciendo uso de los objetos y los propios objetos, pasa a ser responsabilidad de nuestro ORB. Éste, una vez que sabe qué interfaces IDL se van a usar, y conocida la forma de acceder a los objetos, simplemente tiene que hacer uso del protocolo de comunicaciones de CORBA (GIOP/IIOP) para hacer las invocaciones de los métodos de los objetos.

Por tanto, nuestro siguiente paso va a consistir en generar los cabos y los esqueletos a partir de los interfaces IDL, todo ello, mediante el compilador IDL que incluye ORBit, llamado orbit-idl, como se muestra a continuación:

	  orbit-idl --skeleton-impl String.idl
	

Este comando generará cinco ficheros:

  • String.h: definiciones usadas tanto en la implementación de los objetos como en las aplicaciones que hagan uso de ellos

  • String-common.c: funciones usadas tanto en la implementación de los objetos como en las aplicaciones que hagan uso de ellos

  • String-stubs.c: cabos para las aplicaciones que hacen uso de los objetos

  • String-skels.c: esqueletos para la implementación de los objetos

  • String-skelimpl.c: implementación básica de los objetos definidos en el fichero IDL. Este fichero será el que usemos como base para la implementación de los objetos. Contiene una serie de funciones vacías (no implementadas) que simplemente tendremos que rellenar con nuestra implementación.

En este ejemplo, hemos generado los cabos y esqueletos de nuestros interfaces para el lenguaje C con ORBit. Pero este paso podríamos haberlo realizado perfectamente con otro ORB en otro sistema operativo o plataforma, y así, por ejemplo, implementar los clientes (o sea, la parte que accede a los objetos) en dicho sistema operativo/plataforma, y dejar la parte de implementación de los objetos en GNU/Linux+ORBit.

Accediendo a los objetos

Para acceder a los objetos que implementemos, lo primero que tenemos que hacer es activar dichos objetos, o sea obtener una instancia de un objeto que implemente el interfaz que queramos usar. Para hacer las cosas más sencillas, vamos a usar Bonobo Activation, que, como vimos en los artículos dedicados a Bonobo, facilita muchísimo la activación de objetos. Así, sin más dilaciones, vamos a ver el código de nuestro programa, y luego lo comentamos:

	  #include <orb/orbit.h>
	  #include <bonobo-activation/bonobo-activation.h>
	  #include "String.h"

	  int main (int argc, char *argv[])
	  {
	        CORBA_Environment ev;
	        MyModule_String corba_string;

                /* inicializamos Bonobo-Activation */
                bonobo_activation_init (argc, argv);

                /* activamos el objeto a través de Bonobo-Activation */
                CORBA_exception_init(&ev);
                corba_string = bonobo_activation_activate_from_id("OAFIID:MyModule_string", 0, NULL, &ev);
                if (ev._major == CORBA_NO_EXCEPTION
                        && corba_string != CORBA_OBJECT_NIL) {
                        gchar *up;
                        gchar *lw;
                        gchar word[32];

                        /* aquí, ya tenemos una instancia de MyModule::String en corba_string */

                        /* ejecutamos un bucle en el que le pedimos al usuario que introduzca */
                        /* una palabra, que mostraremos seguidamente tanto en mayúsculas como */
                        /* en minúsculas */
                        do {
                                printf("Introduzca palabra: ");
                                scanf("%s", word);

                                /* ahora, hacemos las llamadas a los objetos CORBA */
                                up = MyModule_String_toUpper(corba_string, word, &ev);
                                if (ev._major != CORBA_NO_EXCEPTION) {
                                        printf("se ha producido un error en la llamada a MyModule::String::toUpper\n");
                                        break;
                                }

                                lw = MyModule_String_toLower(corba_string, word, &ev);
                                if (ev._major != CORBA_NO_EXCEPTION) {
                                        printf("se ha producido un error en la llamada a MyModule::String::toLower\n");
                                        break;
                                }

                                printf("Tu palabra '%s' es '%s' en mayúsculas y '%s' en minúsculas\n",
                                       word, up, lw);
                        } while (strlen(word) > 0);

                        /* desactivamos el objeto antes de terminar */
                        CORBA_Object_release(corba_string, &ev);
                }
                return 0;
	  }
	

Como se puede apreciar en este ejemplo, usamos una serie de tipos de datos y funciones con unos nombres similares a los que usamos en el fichero IDL. ¿Qué es esto? ¿nos han plagiado nuestro interfaz antes de implementarlo siquiera? No, simplemente, son nombres de funciones, estructuras, etc que están declarados (e implementadas, en el caso de las funciones) en los ficheros generados por el compilador IDL en el punto anterior. Si observamos con detalle, podremos apreciar que estos nombres siguen unas reglas fácilmente visibles, que consiste en incluir el nombre del módulo y del interfaz en todos los identificadores. Así, por ejemplo, los métodos toUpper y toLower que definíamos en el fichero IDL pasan a convertirse, en el caso del lenguaje C, en MyModule_String_toUpper y MyModule_String_toLower. Estas reglas de nomenclatura permiten evitar posibles conflictos de nombres con otros proyectos, aunque, a pesar de todas las precauciones, es más que probable que, si no usamos un espacio de nombres para nuestros módulos que realmente sea único, entremos tarde o temprano en conflicto con algun otro proyecto. Así, la dirección del proyecto GNOME decidió en su día usar GNOME como módulo de más alto nivel, y dentro de él, que cada aplicación usara su propio módulo (pues en CORBA es posible definir módulos dentro de otros módulos. Así, lo correcto para el ejemplo que estamos mostrando en este artículo, sería usar el siguiente IDL:

	  module GNOME {
                module MyModule {
                        interface String {
                                string toUpper (in string str);
                                string toLower (in string str);
                        };
                };
	  };
	

Como vemos, la única diferencia con respecto al IDL que escribimos al principio es que en este último, hemos incluido nuestro módulo dentro de un módulo de más alto nivel, GNOME. Por supuesto, en aras de la sencillez de los ejemplos, usaremos el primer IDL, aunque la única diferencia es que los elementos generados por el compilador IDL usarían el prefijo GNOME_MyModule_ en vez de MyModule_.

Por último, sólo nos queda compilar el cliente. Para ello, usaremos el siguiente comando:

	  gcc -o client `pkg-config --cflags --libs ORBit-2.0` client.c String-common.c String-stubs.c
	

Como vemos, tenemos que incluir dos de los ficheros generados por orbit-idl en nuestra fase de compilación/enlazado.

Implementación de nuestro objeto

Bien, pues ya en el último paso de este pequeño tutorial sobre el uso de ORBit, nos queda lo más complicado, que es todo lo relativo a la implementación real del objeto CORBA que definimos en nuestro fichero IDL. Para ello, lo mejor es que abramos, con nuestro editor preferido, uno de los ficheros generados por el compilador IDL en los pasos anteriores. En el que estamos interesados en este caso es String-skelimpl.c que, como comentábamos anteriormente, contiene la implementación base del objero definido en el fichero IDL, y que, como tambien comentábamos, simplemente tenemos que rellenar.

Nada más ver el contenido del fichero, es probable que el lector pase al siguiente artículo de la revista, pues hay que reconocer que no es un código muy legible que digamos. Pero no desesperemos, en este caso, simplemente estamos interesados en dos funciones, que podremos encontrar justo al final de dicho fichero, como puede observarse en la figura 1.

Ambas funciones tienen el mismo aspecto, con una variable de tipo CORBA_char declarada al principio de la función, y una línea que devuelve el contenido de dicha variable como resultado de la función. Como vemos, este código, lo primero, es que no hace absolutamente nada, y lo segundo, que puede que nuestro programa termine con un error grave (acceso a ilegal a memoria) si tratamos de usar las funciones en su estado actual, pues estamos devolviendo un puntero que no ha sido inicializado. Pero todo tiene una razón, y la razón para este código tan estúpido es que es simplemente un esqueleto de las funciones que tenemos que implementar generado amablemente por orbit-idl.

Así, lo que vamos a hacer, es implementar dichas funciones:

	  static CORBA_char *
	  impl_MyModule_String_toUpper(impl_POA_MyModule_String * servant,
	                               CORBA_char * str, CORBA_Environment * ev)
	  {
                int cnt;
                CORBA_char *retval = CORBA_string_dup(str);

                for (cnt = 0; cnt < strlen(retval); cnt++)
                        retval[cnt] = toupper(retval[cnt]);
                return retval;
	  }

	  static CORBA_char *
	  impl_MyModule_String_toLower(impl_POA_MyModule_String * servant,
	                               CORBA_char * str, CORBA_Environment * ev)
	  {
                int cnt;
                CORBA_char *retval = CORBA_string_dup(str);

                for (cnt = 0; cnt < strlen(retval); cnt++)
                        retval[cnt] = tolower(retval[cnt]);
                return retval;
	  }
	

No necesitan mucha explicación estas funciones, que simplemente convierten una cadena a mayúsculas o minúsculas. La única diferencia es que, en el caso de CORBA, a la hora de asignar memoria para cadenas y otros tipos (como estructuras, secuencias), tendremos que usar una serie de funciones específicas en vez de usar las que usaríamos en un programa "normal" de C. Por ejemplo, en este caso, en vez de usar strdup (o g_strdup), usamos CORBA_string_dup.

Si observamos el contenido de todos los ficheros generados por el compilador IDL, veremos que no hay ninguna función main. Por tanto, el último paso en la implementación de nuestro objeto será escribir dicha función, en la que inicializaremos todo lo necesario para activar nuestro objeto. Esta función, lo normal sería incluirla en un fichero aparte, pero tambien podemos añadirla al fichero String-skelimpl.c. Así, éste sería su aspecto:

	  int main (int argc, char argv[])
	  {
                CORBA_Environment ev;
                CORBA_ORB orb;
                CORBA_char *objref;
                PortableServer_POA root_poa;
                MyModule_String corba_string;
                PortableServer_POAManager pm;

                CORBA_exception_init(&ev);
                orb = bonobo_activation_init(argc, argv);

                /* obtenemos referencia al "RootPOA" */
                root_poa = (PortableServer_POA)
                        CORBA_ORB_resolve_initial_references(orb, "RootPOA", &ev);

                /* creamos instancia de nuestro objeto */
                corba_string = impl_MyModule_String__create(root_poa, &ev);
                objref = CORBA_ORB_object_to_string(orb, corba_string, &ev);
                /* registramos el objeto en Bonobo-Activation */
                if (bonobo_activation_register_active_server ("OAFIID:MyModule_String", corba_string)
                    != Bonobo_ACTIVATION_REG_SUCCESS) {
                        printf("No se pudo registrar el objeto en Bonobo\n");
                        return -1;
                }

                /* ejecutamos el objeto */
                pm = PortableServer_POA__get_the_POAManager(root_poa, &ev);
                PortableServer_POAManager_activate(pm, &ev);

                CORBA_ORB_run(orb, &ev);

                /* cuando el programa salga de CORBA_ORB_run, es hora de terminar */
                CORBA_ORB_shutdown(orb, TRUE, &ev);
                bonobo_activation_unregister_active_server ("OAFIID:MyModule_String", corba_string);

                return 0;
	  }
	

Aquí ya vemos que las cosas se complican, aunque esto no debe preocuparnos, pues la mayor parte de los pasos son siempre iguales, por los que no es ni siquiera necesario que entendamos lo que hacen (de hecho, su explicación la reservamos para otro posible artículo sobre uso más avanzado de CORBA). Simplemente tenemos que preocuparnos de la llamada a la función impl_MyModule_String__create y al identificador de implementación OAF (OAFIID) usado en las funciones bonobo_activation_register_active_server y bonobo_activation_unregister_active_server. En la primera función, impl_MyModule_String__create, generada por el compilador IDL, creamos una instancia de nuestro objeto. El valor que devuelve es una referencia al objeto creado. Con este valor (corba_string) hacemos las llamadas a Bonobo-Activation, para indicarle que queremos registrar un objeto ya activo. De esta forma, Bonobo tiene constancia de la existencia de nuestro objeto, algo que es esencial para que otra aplicación pueda acceder a él.

El resto del código realiza todas las inicializaciones y tareas necesarias para la ejecución del ORB (ORBit en este caso). En la mayor parte de los casos, simplemente nos limitaremos a copiar y pegar este código entre proyectos, aunque recomendamos encarecidamente al lector que intente por su cuenta aprender qué hace ese código, pues conocer CORBA/ORBit a fondo ofrece características realmente interesantes, especialmente el uso de POAs, que sin duda querremos aprovechar en nuestras aplicaciones si vamos a usar CORBA "de verdad".

Para compilar nuestro objeto, usaremos un comando muy similar al que usamos anteriormente para compilar el cliente. Lo único que cambia es la lista de ficheros a compilar, que en el caso del servidor añade String-skelimpl.c y sustituye String-stubs.c por String-skels.c, que es el fichero adecuado, como explicábamos antes, para compilar la implementación de nuestro objeto. Así:

	  gcc -o server `pkg-config --cflags --libs ORBit-2.0` String-skelimpl.c String-common.c String-skels.c
	

Ejecutando la aplicación

Llegados a este punto, sólo nos queda probar el código que hemos escrito y, si funciona y nos ha llamado la atención el uso de CORBA, comenzar a hacer pruebas uno mismo. Para lo primero, ejecutar la aplicación, lo primero que vamos a necesitar es un fichero .server (descritos en los artículos referentes a Bonobo publicados anteriormente); este fichero es necesario para que la activación de nuestro objeto mediante Bonobo se lleve a cabo satisfactoriamente. Seguidamente, lo mejor es que abramos dos terminales, y en uno de ellos ejecutemos el programa servidor (server) y, acto seguido, en el otro terminal, ejecutemos el cliente. Si todo ha ido bien, deberíamos ser capaces de enviar, a través del programa cliente, cadenas al servidor que nos son seguidamente mostradas tanto en mayúsculas como en minúsculas.

Para lo segundo, investigar por nuestra cuenta sobre el uso de ORBit/CORBA en GNOME, lo mejor es que visitemos las páginas especificadas en la sección de referencias, donde podremos obtener enlaces a otros lugares en Internet donde encontrar ejemplos y más documentación. Hay que destacar que, desgraciadamente, la documentación sobre todo esto es bastante escasa, y en algunos casos, la existente está bastante desactualizada. Por ello, recomendamos al lector que procure contrastar la documentación que lea con código fuente. Para obtener código fuente, sin duda el mejor sitio es el servidor CVS de GNOME.