Lenguaje OMG/IDL


Introducción

El lenguaje OMG/IDL es uno de los pilares de la plataforma OMA de OMG, y en especial de CORBA.
Gracias a este lenguaje descriptivo podemos especificar las interfaces de nuestros módulos CORBA, sin ligarnos a ningún lenguaje concreto.
Además es un lenguaje muy adecuado para el diseño del sistema ya que permite pensar en el diseño del sistema, sin tener que descender a detalles de implementación.
Para evitar ambiguedades a nivel de diseño es un lenguaje fuertemente tipado, al estilo de Java.
Es un lenguaje cuya sintaxis está bastante cercana a la de ANSI C++ o Java, al ser orientado a objetos. Tiene facilidades como la definición de módulos e interfaces, herencia de interfaces o excepciones.
Es un lenguaje lo suficientemente descriptivo como para poder detallar interfaces de objetos que van a ser distribuidos.
Pasamos a describir el lenguaje con mayor detalle a continuación, así como ejemplos de los principales traducciones de OMG IDL a lenguajes como C, C++ y Java.

Descripción del lenguaje

La mejor forma de entender el lenguaje es mediante un ejemplo que iremos explicando a lo largo de este apartado.

El ejemplo que utilizaremos luego será implementado como uno de los ejemplos del curso, por lo que es necesario que los participantes en el curso se familiaricen con él.

1: // Módulo de intercambio de mensajes entre usuarios
2: module Mensajes {
3: 	interface comun {
4: 		// Version de la aplicacion
5: 		readonly attribute string version;
6: 
7: 		// Nombre de usuario
8: 		typedef string usuario;
9: 		// Lista de usuarios
10: 		typedef sequence lista_usuarios;
11: 
12: 		// Excepcion de usuario no presente
13: 		exception ClienteNoPresente {usuario nombre;};
14: 
15: 		struct alarma {
16: 			unsigned short codigo;
17: 			string mensaje;
18: 		};
19: 	}; 
20: 	
21: 	// Interfaz a implementar por el cliente
22: 	interface cliente {
23: 		// Función básica de recepcion de mensajes
24: 		// Devuelve "true" ante recepcion correcta
25: 		boolean recibeMensaje (in string mensaje);
26: 		// Mensajes de aviso especiales del servidor
27: 		boolean recibeAviso (in comun::alarma mensaje);
28: 		// Mensajes directos a otros usuarios
29: 		void enviaMensaje (in comun::usuario destino, in string mensaje);
30: 	};
31: 
32: 	// El operador es informado de accesos al servicio
33: 	interface operador:cliente {
34: 		boolean nuevaConexion(in string nombre);
35: 	};
36: 
37: 	// Interfaz a implementar por el servidor
38: 	interface servidor {
39: 		// Conexion de un operador
40: 		string conectar_operador (in operador interfaz, in string clave);
41: 		// Devuelve una cadena con el nombre del servidor
42: 		string conectar (in cliente interfaz);
43: 		// Desconecta un usuario del sistema
44: 		boolean desconectar (in cliente interfaz);
45: 		// Mira si un usuario esta conectado y recibe la interfaz al mismo
46: 		cliente presente (in string nombre) raises (comun::ClienteNoPresente);
47: 		// Devuelve una lista de los usuarios presentes en el sistema
48: 		void listaUsuarios (out comun::lista_usuarios lista);
49: 	};
50: 
51: };

Módulos e interfaces

Este ejemplo es bastante completo y nos va a permitir profundizar en diferentes aspectos de OMG IDL.
La primero que observamos es la forma de encapsular en módulos las interfaces. Un módulo debe de ser un conjunto de interfaces que proporcionan una funcionalidad concreta dentro del sistema. En nuestro caso este módulo es el encargado de definir el módulo de Mensajes de una herramienta de trabajo cooperativo.

Dentro del módulo hemos definido dos interfaces: una para los clientes y otra para el servidor central. La arquitectura sigue los esquemas centralizados, es decir, que un servidor central controla la forma de interactuar entre los diferentes clientes. Pero también se pueden obtener las interfaces a otros clientes y comunicarnos con ellos de forma directa.
Comprobamos aquí que CORBA se adapta sin problemas a paradigmas como el de cliente/servidor o de igual a igual sin ningún tipo de problema.

Operaciones y tipos de datos

Las funciones se agrupan en interfaces cuando tienen un proposito común.
Dichas funciones tienen parámetros de entrada, con sus tipos correspondientes y parámetros de salida. Es necesario especificar el parámetro de retorno, aunque este sea "void".

Es característicos de OMG/IDL que los parámetros que se pasan a la función pueden ser de tres tipos:

  • in: parámetros que se envían del cliente al servidor
  • out: parámetros en los que se reciben datos del servidor
  • inout: parámetros en los que se envían datos al servidor y se reciben datos de respuesta del servidor.
Esta característica de CORBA en el paso de parámetros complica la gestión de memoria dentro de la implementación. En los tipos de parametros "in" la gestión de memoria del parámetro es sencilla: el cliente se encarga de la reserva y liberación de memoria.
Pero en los parámetros "out" y "inout" aparece el problema de que, el cliente puede reservar una cantidad de memoria para el parámetro, cantidad que puede ser modificada por el objeto para que entre en el parámetro la respuesta.
En el estandar se han dividido 20 tipos de parámetros en 6 tipos de paso de parámetros. Por ejemplo, para parámetros de longitud fija (incluyendo las estructuras), el llamante reserva y libera el espacio excepto en el caso de "any".
Las referencias a objetos también son gestionadas en el cliente. Pero por ejemplo, si el parámetro es "inout", la implementación del objeto va a invocar la operación CORBA::Object_release en el valor original para reasignar el parámetro (algo que también afecta en el lado del cliente). Para guardar el valor original de la referencia al objeto, deberemos utilizar CORBA::duplicate antes de invocar la operación.
Hay casos en los parámetros "out" y "inout" en los que la implementación debe de reservar la memoria, pero debe ser liberada en el cliente.
Dentro del estandar estas detallados todos los casos y hay que ser cuidadoso para evitar agujeros de memoria en nuestras apliaciones.

Los tipos de estos datos son los que solemos encontrarnos en cualquier lenguaje de programación: integer (signed/unsigned long, short), float, double, boolean, octet, any.
También tenemos tipos estructurados como: struct, discriminated union, enumerations, sequence, string y array.
Un tipo especial de datos son los atributos. Un ejemplo de su uso lo encontramos en la línea 5. Los atributos al ser procesados por el compilador de IDL generan una función (get) si son de solo lectura (como en nuestro caso), o dos funciones (get y set) si son de lectura y escritura. El acceso al atributo se ha de realizar de forma obligatoria a través de estas operaciones.
De todos estos tipos de datos quizás merezca mención especial el tipo "any" en el que podemos meter cualquier otro tipo de dato de OMG/IDL. Un tipo de datos muy flexible que no solemos encontrar en los lenguajes usuales.

Por último comentar que existe un tipo especial de operación, aquellas del tipo "oneway". Son operaciones que se invocan sin esperar ningun valor de retorno, no siendo bloqueantes. Por ello en este tipo de operaciones el tipo de retorno ha de ser "void" y todos los parámetros han de ser del tipo "in". Un ejemplo de este tipo de ooperación lo observamos en la línea 27, donde el servidor envía las alarmas a los clientes, sin esperar que estos le respondan nada.

Excepciones

Algo importante en la invocación de operaciones sobre objetos CORBA es que, aunque para el desarrollador sean invocaciones comunes sobre objetos, el mecanismo para sus ejecución es complejo: han de pasar por los cabos del cliente, por el ORB, por el adaptador de objetos, encontrar el objeto adecuado, viajar por los cabos del servidor, realizar la invocación sobre el objeto y recorrer el mismo viaje de vuelta.

Es sencillo que en todo este trasiego puedan aparecer problemas. Y para informar al cliente de dichos problemas aparecen las excepciones. Ante problemas en la invocación de estas operaciones, el ORB nos puede devolver excepciones de diferentes tipo, según el problema aparecido.

Pero es más dentro de nuestro código también podemos crear excepciones para la gestión de errores, o para comunicar situaciones excepcionales.
Tal es el caso de la función de la línea 39, que en el caso de que un usuario no este conectado lo devuelve como una excepción.

Herencia

La herencia es un mecanismo de reutilización de funcionalidad. En nuestro caso la utilizamos para definir la interfaz de un operador, que es un cliente con una operación más, la de recibir información de conexiones.

REVISAR NUMEROS DE LINEA

Mapping a C

Al no tener el lenguaje C objetos ni excepciones, en el mappping de OMG/IDL a C en todas las operaciones aparecen el objeto sobre el que se va a invocar, y una variable de contexto para recoger información sobre las excepciones.
La carencia en C de espacios de nombres obliga a nombres de la forma "CORBA_object" y con ello, aparece el problema de posibles colisiones de nombres.
Un ejemplo de mapping sencillo podría ser:
interface ejemplo {
	long operacion (in string arg);
};
Este interfaz en IDL genera en C (partes relevantes):
typedef CORBA_Object ejemplo;
extern CORBA_long operacion (
			ejemplo o, CORBA_string arg, CORBA_Enviroment *ev);
Queda patente del ejemplo como en cada operación, hay que especificar el objeto sobre el que se va a ejecutar la operación, así como el entorno por el que se pasarán las excepciones.
Los tipos básicos no se traducen directamente a tipos en C, ya que los tipos en C pueden variar de una arquitectura a otra (número de bits, forma de ordenación de los bytes ...). Por ello por ejemplo el tipo IDL "long" se mapea a "CORBA_long" en C, y no a un long directamente.

Para traducir el mecanismo de herencia lo que se hace, como en C no existe la herencia, es incluir dentro de la traducción todos los elementos de la interfaz, y todos los de las interfaces de los que hereda.

Como ya dijimos el manejo de excepciones se realiza utilizando variables de entorno "CORBA_Enviroment". Este mismo mecanismo es el que se utiliza en el caso de los compiladores de C++ que aún no tuvieran soporte para excepciones.

Mapping a C++

La traducción de OMG/IDL es más directa a C++ que a C, a pesar de que en cuando se publicó el mapping de OMG/IDL a C++ aún no estaba aprobado el estandar ANSI C++, lo que obligó a dar rodeos para aspectos (p.e. string) que hoy serían mucho más sencillo.

Siguiendo con el mismo ejemplo anterior:

interface ejemplo {
	long operacion (in string arg);
};
Este interfaz en IDL genera en C++ (partes relevantes):
class ejemplo : virtual public CORBA::Object {
	virtual CORBA::Long operacion( const char* arg ) = 0;
}
Todos las interfaces se convierten en objetos CORBA por el mecanismo de la herencia, hecho que muestra que todos los objetos CORBA tienen una funcionalidad común, con operaciones como:
  • _duplicate
  • _narrow
  • _nil
Recordemos que los objetos CORBA en nuestra aplicación son tipos abstractos, es decir, no podemos interpretar su representación. Estas operaciones comunes nos permiten copiar referencias, ver si una referencia apunta a un objeto válido, o hacer un "casting" de particularización.
Normalmente C++ mos proporciona el "casting" de generalización. Con la operación de _narrow podemos transformar un "CORBA::Object" en un objeto concreto.

Como vemos la operación de la interfaz es definida como una operación abstracta pura, es decir, que para implementar este objeto estamos obligados a implementar esta operación. Normalmente para implementar la interfaz lo que se hace es heredar de la clase ejemplo (class ejemploImp:virtual public ejemplo) e implementar la operación.

Como comentamos en la introducción a IDL, uno de los problemas a la hora de desarrollar con CORBA era la gestión de memoria, en concreto con los parámetros "inout" y "out".
En C++ este problema se alivia un poco gracias a que los tipos de datos que no son básicos (long, short, float, double) reciben un tratamiento especial. Un tipo "T" reciben traducción sobre "T" y "T_var". El tipo "T_var" incluye mecanismos de gestión automática de memoria, por lo que si utilizamos estos tipos, nos vemos liberados de reservar y liberar memoria.

Dentro de los ejemplos profundizaremos más en el uso de IDL en C++, como se implementan los interfaces y como se usan los objetos.

Mapping a Java

El mapping a Java es el último que se produjo, finales de 1997, y es quizás el más directo de todos, debido al soporte que da Java a la programación orientada a objetos, el uso de interfaces, ...

Tomando el ejemplo anterior tenemos:

interface ejemplo {
	long operacion (in string arg);
};
Este interfaz en IDL genera en Java (partes relevantes):
public interface ejemplo extends org.omg.CORBA.CORBject {
  int operacion(java.lang.String arg);
}
Este ejemplo es muy parecido al de C++, pero como Java tiene la construcción sintáctica "interface" la traducción es todavía más inmediata.
Uno de los problemas que presenta Java es que no soporta la herencia múltiple. Esto obliga a que por ejemplo, si la clase que implementa la interfaz ya hereda de alguna otra, no podemos heredar de nuevo de la clase ejemplo (no heredamos directamente de la interfaz "ejemplo" si no de la clase "ejemploStub").
Cuando OMG definió como se pasaba de IDL a Java, Java estaba ya prácticamente estandarizado por lo que el "mapping" fue sencillo.
Los problemas de gestión de memoria aquí no aparecen ya que, Java gestiona de forma automática la memoria con el recolector de basura.

Resumen

Hasta el momento hemos hecho una introducción a como se traduce de OMG/IDL a C, C++ o Java. Hemos visto que el soporte de objetos de C++ y Java facilita su traducción.

A continuación pasamos a describir las herramientas básicas que utilizaremos para desarrollar los ejemplos, para a continuación pasar a programar utilizando Java.