Cuestiones internas de Gambas

Descripción del mecanismo de funcionamiento de los componentes en Gambas (1)

Introducción

Gambas2 puede trabajar con componentes compilados o bien interpretados escritos en Gambas. En este documento se detalla el primer caso, no el segundo, así pues trata acerca de los componentes generalmente escritos en C y C++, si bien se podrían desarrollar en otros lenguajes capaces de generar librerías de enlace dinámico, como es el caso de FreePascal.

Los componentes son librerías

Los componentes escritos en C y C++ son librerías de enlace dinámico (en Linux tienen extensión “.so”), y el sistema de componentes sigue la filosofía de cualquier sistema tradicional de plugins, que consta de dos partes:

  • a) La aplicación principal, en este caso Gambas2, oferta una API que pone a disposición del plugin.

 

  • b) El plugin oferta a su vez nuevas capacidades al programa principal, lo que en Gambas2 se traduce en disponer  de nuevas clases especializadas en diferentes tareas.

La aplicación principal entrega su funcionalidad al Plugin

Cuando se solicita a Gambas2 el uso de un componente, lo primero que hace es cargar de forma dinámica dicha librería. A continuación busca los siguientes símbolos:

int GB_INIT (void) -> Es una función a la que se llama una vez el componente está en memoria, y que se utiliza para permitir a la librería o plugin que inicialice todo lo necesario para su funcionamiento. Dicha función devuelve un entero, que puede ser cero si se desea que a posteriori se descargue la librería de memoria cuando el programa finalice, u otro valor para que no se realice dicha acción. Esta particularidad se debe a que algunas librerías hacen fallar a la aplicación principal con una violación de segmento si son descargadas de memoria de forma abrupta, antes de que la aplicación principal haya finalizado.

void GB_EXIT (void) -> Función a la que se llama cuando la aplicación principal va a finalizar, para permitir al plugin liberar todos los recusos que tenga ocupados.

GB_INTERFACE GB -> Es una estructura de tipo “GB_INTERFACE”, la cual tiene punteros a funciones. Gambas2 rellena dicha estructura (en principio con valores nulos) con punteros a todas las funciones que conforman la API de Gambas2, de forma que el plugin cuenta a partir de ese momento con todos los recursos para manejar memoria, tipos de datos, ficheros, etc de forma acorde con el resto del intérprete y componentes de Gambas.

El plugin exporta sus clases al programa principal

En el sentido contrario, la aplicación principal recibe información del plugin acerca de sus capacidades, y la forma de llamar a sus objetos y clases. Para ello, la aplicación principal busca el símbolo “GB_CLASSES” en el plugin, que se trata de un array de punteros a descripciones de clases. Es decir, cada uno de estos punteros representa a una clase, la cual contiene información acerca de su nombre, particularidades, métodos, propiedades y eventos.

Cada puntero es del tipo génerico de la API de Gambas denominado “GB_DESC”. Se trata de una estructura utilizada para cualquier tipo de descripción,y tiene la forma:

typedef struct {
    char *name;
    int  val1;
    int  val2;
    int  val3;
    int  val4;
    int  val5;
} GB_DESC;

No siempre son utilizados todos los campos, ni tienen el mismo significado, pero tener una estructura de tamaño fijo facilita el recorrerlas después con punteros de forma sencilla.

La definiciones de la clase comienzan por una estructura GB_DESC, en la cual se indica el nombre de la clase, la versión de la API de Gambas con la que fue diseñada y el tamaño que ocupa el objeto de dicha clase una vez creado en memoria. A la hora de diseñar componentes en C y C++, el fichero de cabecera gambas.h aporta una macro para la declaración:

#define GB_DECLARE(name, size)  { name, (int)GB_VERSION, (int)size }

A contuación vienen el resto de estructuras GB_DESC que pueden contener información sobre cada método, propiedad o evento provisto sobre la clase, o bien sobre particularidades de dicha clase. Las particularidades, traducidas a macros pueden ser:

#define GB_NOT_CREATABLE_ID     ((char *)3)
#define GB_NOT_CREATABLE()      { GB_NOT_CREATABLE_ID }

No se puede crear. Indica que no se pueden crear objetos de una clase desde el código Gambas, lo cual puede ser debido a que solo disponga de métodos y propiedades estáticas, o bien porque la única forma de crearlas sea llamando a funciones de otros objetos que nos devuelven estos objetos nuevos creados desde un componente en C o C++, es decir, en los casos en que su creación aislada de un contexto externo no tenga sentido.

#define GB_VIRTUAL_CLASS_ID     ((char *)1)
#define GB_VIRTUAL_CLASS()      { GB_VIRTUAL_CLASS_ID }, { GB_NOT_CREATABLE_ID }

Clase virtual, indica que la interfaz no es una clase independiente, si no una interfaz adicional para otra clase, y por tanto no se puede utilizar de forma aislada ni como objeto ni como clase. Así por ejemplo, para las filas de un Widget “Grid” que represente una parrilla de filas y columnas para representar datos, podemos referirnos a las propiedades de las filas con una clase llamada “.GridRows” que siempre dependerá del objeto “Grid”, lo cual simplifica la sintaxis del lenguaje sin tener que crear objetos temporales: “Grid1.Rows.Count”, “Grid1.Rows.Clear()”… que es más elegante y ordenado que llenar a la clase “Grid” directamente de métodos y propiedades referidas a las filas: “Grid1.RowsCount”, “Grid1.RowsClear()”…

#define GB_AUTO_CREATABLE_ID    ((char *)4)
#define GB_AUTO_CREATABLE()     { GB_AUTO_CREATABLE_ID }

Autocreable. Indica que una llamada a la clase crea de forma implícita un objeto para tratar las llamadas. Se usa actualmente en el caso de formularios, de forma que se elimine la necesidad en el código Gambas de crear una instancia de un formulario, y en su lugar se puedan hacer llamadas genéricas a los métodos y propiedades de la clase, por ejemplo “Form1.Show()” donde “Form1” es el nombre de la clase, no de un objeto creado a partir de la clase.

#define GB_INHERITS_ID          ((char *)5)
#define GB_INHERITS(symbol)     { GB_INHERITS_ID, (int)symbol }

Indicador de herencia. Especifica que la clase hereda los métodos y propiedades de una clase padre.

#define GB_HOOK_CHECK_ID        ((char *)2)
#define GB_HOOK_CHECK(hook)     { GB_HOOK_CHECK_ID, (int)hook }

El “Hook Check” indica que antes de llamar a cada método o propiedad de la clase, se llamará a una función que definamos para comprobar si el objeto se encuentra en un estado válido para el trabajo a realizar. Esto es muy útil, por ejemplo, en el caso de Widgets: cuando se destruye el widget llamando al método “Destroy()”, se liberan todos los recursos asociados a dicho widget, pero el objeto Gambas sigue existiendo, por lo cual antes de cada llamada a una propiedad o método se comprueba si dicho objeto aún aloja a un Widget real.

En cuanto a los métodos, propiedades, eventos y constantes, se indican por medio de otras macros. En general, se especifica un código como primera letra de del nombre, que especifica su especie, y después el resto de datos necesarios según su especie, por ejemplo, para la declaración de un método:

#define GB_METHOD(symbol, type, exec, signature)
        { “m” symbol, (int)(void *)type, (int)exec, (int)(void *)signature }

Se indica al principio una ’m’, que indica que es un método no estático, seguido de su nombre, como puede ser “Clear”. Dado que se trata de un método, siguen los datos necesarios para completar la signatura: tipo de valor devuelto, codificado como una cadena, puntero a la función a llamar, y valores que recibe como parámetros codificados también como una cadena.

Del mismo modo, el resto de macros definen propiedades, constantes, eventos y otro tipo de métodos:

Declaración de constante:
#define GB_CONSTANT(symbol, type, value)   { “C” symbol, (int)type, (int)value }

Declaración de propiedad no estática:
#define GB_PROPERTY(symbol, type, proc)  { “p” symbol, (int)type, (int)proc }

Declaración de propiedad no estática de solo lectura:
#define GB_PROPERTY_READ(symbol, type, proc)   { “r” symbol, (int)type, (int)proc }

Declaración de propiedad estática:
#define GB_STATIC_PROPERTY(symbol, type, proc)
  { “P” symbol, (int)type, (int)proc }

Declaración de propiedad estática de sólo lectura:
#define GB_STATIC_PROPERTY_READ(symbol, type, proc)
  { “R” symbol, (int)type, (int)proc }

Declaración de propiedad que devuelve un puntero a sí mismo (para utilizar con clases virtuales):
#define GB_PROPERTY_SELF(symbol, type)
  { “r” symbol, (int)type, (int)-1 }

Declaración estática de propiedad que devuelve un puntero a sí mismo (para utilizar con clases virtuales):
#define GB_STATIC_PROPERTY_SELF(symbol, type)
  { “R” symbol, (int)type, (-1) }

Declaración de método:
#define GB_METHOD(symbol, type, exec, signature)
  { “m” symbol, (int)(void *)type, (int)exec, (int)(void *)signature }

Declaración de método estático:
#define GB_STATIC_METHOD(symbol, type, exec, signature)
  { “M” symbol, (int)(void *)type, (int)exec, (int)(void *)signature }

Declaración de evento:
#define GB_EVENT(symbol, type, signature, id)
  { “::” symbol, (int)(void *)type, (int)id, (int)(void *)signature }

 

Ejemplo de funcionamiento

Para ilustrar toda la mecánica, crearemos un programa que actúa a modo de intérprete muy primitivo de Gambas2, capaz tan sólo de cargar un plugin, verificar su estado y recorrer todas las clases que aporta el componente para mostrar su tabla de símbolos Gambas2. La carga del plugin se hace con “dlopen“, como cualquier otra librería, aunque se pueden usar otras alternativas.

Llamaremos al programa gbexlore.c, y se compilará con:

gcc gbexlore.c -o gbexplore -ldl

#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>

/*
Definimos el tipo genérico de descripciones GB_DESC
conforme a la API actual de Gambas2
*/

typedef struct
{
const char *name;
int val1;
int val2;
int val3;
int val4;
int val5;
} GB_DESC;

/*
Se encarga de cargar em memoria la librería, y comprobar
que realmente contiene las funciones de entrada y salida
propias de un plugin de Gambas
*/

void explore_class (GB_DESC *clase);
void load_gambas_library (char *name);

void load_gambas_library (char *name)
{
/*
Puntero que almacena un descriptor que apunta a la
librería que vamos a cargar
*/

void *handle;
/*
Puntero para recoger símbolos de la librería
*/

void *test;
/*
Puntero para recorrer las clases que contiene
el componente
*/

GB_DESC **clases;

if (!name) { perror (“Ruta no válida”); exit (-1); }

/* Carga en memoria la librería */
handle=dlopen(name,RTLD_LAZY);
/* ¿Se pudo cargar? */
if (!handle) { perror (“No fue posible cargar la librería”); exit(-1); }

/* comprobamos de igual modo la existencia de los símbolos GB, GB_INIT y GB_EXIT */
test=dlsym(handle,”GB”);
if (!test) { perror (“El símbolo GB no existe”);exit (-1); }

test=dlsym(handle,”GB_INIT”);
if (!test) { perror (“El símbolo GB_INIT no existe”);exit (-1); }

test=dlsym(handle,”GB_EXIT”);
if (!test) { perror (“El símbolo GB_EXIT no existe”);exit (-1); }

/*
Recibimos la definición de todas las clases que contiene
el componente a través del símbolo “GB_CLASSES”
*/

test=dlsym(handle,”GB_CLASSES”);
if (!test) { perror (“El símbolo GB_CLASSES no existe”);exit (-1); }
/*
Cast para iterar por cada clase del componente
*/

clases=(GB_DESC**)test;

/*
Iteración por todas las clases, hasta que
lleguemos a un puntero nulo, que indica el
fin de las definiciones
*/

while (*clases)
{
explore_class( *clases );
clases++;
}
}

/*
Exploración interna de cada clase
*/

void explore_class (GB_DESC *clase)
{
char *mname;
/*
El primer miembro de la definición de la clase contiene
el nombre, versión y tamaño del objeto de la clase
*/

printf(“_____________________________________ “);
printf(“CLASE: %s “,(*clase).name);
printf(“Versión de la API: %d “,(*clase).val1);
printf(“Tamaño del objeto: %d “,(*clase).val2);

clase++;
/*
La definición de la clase es un array de estructuras GB_DESC,
la última de las cuales tiene el caracter especial 0 en el
campo ’name’ (macro GB_END_DECLARE)
*/

while ( (*clase).name != (char*)0 )
{
/*
El nombre puede ser el nombre de una propiedad, método,
evento o constante, o bien un cararter especial que
indique particularidades de la clase. Las constantes
especiales están definidas en la API de Gambas2
*/

switch ((int)(*clase).name )
{
case 1: printf(“Clase virtual “); break;
case 2: printf(“Contiene Hook de comprobación “); break;
case 3: printf(“La clase no puede crear objetos “); break;
case 4: printf(“La clase crea objetos implícitos “); break;
case 5: printf(“la clase es hija de la clase: %s “,(char*)(*clase).val1); break;

default:
mname=(char*)(*clase).name;
mname++;
switch ( (*clase).name[0] )
{
case ’C’:
printf(“Constante                       : %s “,mname);
break;
case ’p’:
printf(“Propiedad                       : %s “,mname);
break;
case ’r’:
printf(“Propiedad solo lectura          : %s “,mname);
break;
case ’P’:
printf(“Propiedad estática              : %s “,mname);
break;
case ’R’:
printf(“Propiedad estática, solo lectura: %s “,mname);
break;
case ’m’:
printf(“Método                          : %s “,mname);
break;
case ’M’:
printf(“Método estático                 : %s “,mname);
break;
}
}

clase++;
}
}

/*
Función principal
*/

int main (int argc, char *argv[])
{
if (argc != 2) { perror(“Uso: gbexplore ruta”); exit(-1); }

load_gambas_library(argv[1]);

}

Referencias

* Programación de componentes con Gambas2.

* API de Gambas2, contenida en el fichero gambas.h

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *