Cualquier cosa que valga la pena se hace en equipo

Cualquier cosa que valga la pena se hace en equipo

viernes, 4 de junio de 2010

La programacion Reflexiva-PARTE I

Reflection

Para la ejecucion de una aplicación .NET debemos tener presente el concepto de CONTENEDORES. Este es un concepto que se ha popularizado los ultimos años a traves de Java y difiere de los habituales run-times en que no es un agregado que lleva el ejecutable o un conjunto de bibliotecas que resuelven aspectos especificos (como graficos), SINO QUE ES UNA MAQUINA VIRTUAL dentro de la cual corre la aplicación.-
Es decir esta cascara es la que separa a la aplicación del Sistema operativo.-
Sus ventajas en el mundo Java son obvias en tanto estos contenedores aislan al programador de especificidades de tal o cual sistema operativo.-
No nos resultan tan obvias en el mundo Microsoft donde existe UN SOLO sistema operativo.-
Sin embargo hay otras decisiones que seguramente habrán pesado en la decisión de Microsoft de hacer un esquema de contenedores.-
El primero es que este esquema permite que exista un nivel de compilación que no es la generación del ejecutable mismo, sino de un lenguaje intermedio (MSIL para Microsoft, Bytecodes para Java y… P-CODE para quienes tuvimos oportunidad de trabajar profesionalmente con el maravilloso Pascal ¡!).-
El segundo es que varios lenguajes (al menos Visual Basic, C# y C++ en modo manejado) pueden trabajar sobre esta maquina virtual y compartir aquellos servicios, bibliotecas y assemblies que la maquina virtual necesita. Esto es un enorme avance desde el punto de vista de cómo cada lenguaje expone funcionalidades comunes a los programadores.-
Tambien aca podemos decir que esto existía, para determinados servicios, en los sistemas operativos propietarios de Digital e IBM.-
Sobre todo en los del primero, su sistema operativo VMS (hoy propiedad de Hewlett Packard) presentaba para la familia de lenguajes (Fortran, C, Cobol y Basic) idénticos nombres de rutinas para todo el sistema de I/O y de archivos ISAM.-
Solo que en estos casos un runtime que se adjuntaba al programa compilado era el encargado de proveer esta funcionalidad comun. Esto por supuesto duplicaba el codigo en cierto punto, aunque los servicios que utilizaba el runtime estaban en el kernel del SO por lo que no era tan problemático.-
¿Como funciona esto y para que sirve realmente?
Vamos a hacer un par de aplicaciones que nos muestren como funciona esto.
Antes de avanzar nos gustaría que repasemos como, en un lenguaje como el C, resolvemos un problema común:
Queremos invocar un método, que esta en una biblioteca CUYO ENLACE no se produce cuando compilamos, sino en tiempo de ejecución.-
Esto da a nuestros programas otra versatilidad. Es decir nuestro programa es capaz de:
Cargar una biblioteca a traves de su nombre LITERAL. Es decir un string en tiempo de ejecucion (por ejemplo leido de un archivo de configuración) dira cual es la biblioteca a a cargar)
Colocar un apuntador a una funcion de dicha biblioteca (definible tambien como un string)
Hay alguna habilidad que debe tener el SO en el que trabajamos y es la de aceptar bibliotecas compartidas. En Windows estas tienen la extension .DLL y en UNIX la extension .SO
Es decir la biblioteca que invocaremos desde nuestra aplicación sera del tipo compartida (Shared) y no del tipo estatica (STATIC).-
El ejemplo lo haremos para UNIX solo para ver alguna pequeña diferencia de cómo lo hariamos en Windows.-
El programa que haremos sabra cargar una biblioteca por nombre. En nuestro caso los nombres son lib1.so y lib2.so. Estos nombres son totalmente arbitrarios y podrian ser cualquier otro valido para el Sistema operativo.-
El programa cargara alguna de estas 2 bibliotecas y llamara la funcion saludo().
Esta funcion existe en ambas bibliotecas y el programa es capaz de trabajar con esta redefinicion (override) ya que dependera de la biblioteca que cargue lo que determinara cual es la tarea que termine realizando.-
Este es el codigo de una biblioteca escrito en ANSI C:

#include "stdio.h";
#include "dlfcn.h";/* bibliotecas de manejo dinamico de funciones:
dlopen(), dlclose(), dlsym() ... */

/* Explicamos como usar el programa. */
void usage(int argc, char* argv[])
{
fprintf(stderr, "Usar asi: %s [12] a los efectos de llamar a la lib1 o a la lib2 \n", argv[0]);
exit(1);
}

int main(int argc, char* argv[])
{
int lib_num;
/* Que biblioteca usar ? */
char biblioteca[100]; /* nombre del archivo de la biblioteca */
void* lib_handle; /* manejador para la biblioteca compartida cargada*/
void (*fn_de_la_biblioteca)(); /* puntero a UNA FUNCION de la biblioteca*/
const char* error; /* por si hay errores... */

/* necesito un argumento */
if (argc != 2)
usage(argc, argv);
/* Chau!! */

/* Esta es la biblioteca a cargar. */
lib_num = atoi(argv[1]); /* */

if (lib_num < 1 lib_num > 2)
usage(argc, argv); /* Chau !! */

/* ahora armo el nombre de la biblioteca concatenando la palabra "lib" con el numero 1 o 2
para obtener lib1.so o lib2.so */
sprintf(biblioteca, "lib%d.so", lib_num);

/* carga la biblioteca que desea (lib1.so o lib2.so) */
lib_handle = dlopen(biblioteca, RTLD_LAZY);



if (!lib_handle)
{
fprintf(stderr, "Error: %s\n", dlerror());
exit(1);
}

/* cargar la funcion saludo QUE ESTA EN CUALQUIERA
DE LAS DOS BIBLIOTECAS !*/
fn_de_la_biblioteca = dlsym(lib_handle, "saludo");

error = dlerror();
if (error) {
fprintf(stderr, "Error: %s\n", error);
exit(1);
}

/* Llamar a la funcion. */
(*fn_de_la_biblioteca)();

/* liberar el recurso */
dlclose(lib_handle);

return 0;
}

Este modulo es LIB1.SO:

#include ;

/* Esta es la funcion de la biblioteca*/
void saludo()
{
printf("Que puedo decir que no sea HOLA MUNDO?\n");
}

/* cleanup de la biblioteca */
void _fini()
{
printf("Limpiando la biblioteca 'lib2.so'\n");
}

Este modulo es LIB2.SO:
#include ;

/* funcion de inicializacion: OBLIGATORIO llamarla '_init'. */
void _init()
{
printf("Inicializando biblioteca 'lib1.so'\n");
}

/* Esta es lo funcion que nos importa */
void saludo()
{
printf("HALOAA !!\n");
}

Como vemos este problema responde a un patron comun: La resolucion en tiempo de ejecucion de una funcionalidad determinada.-
Los lenguajes modernos como C# y Java resuelven esto con el paradigma denominado programacion reflexiva.
Como en muchos otros conceptos de estos lenguajes modernos, las ideas se generaron en Smalltalk y C++.-

No hay comentarios: