Introduzione

Come è di norma per molti altri linguaggi, la libreria standard principale di Hybris è composta da moduli dinamici che vengono caricati dall'interprete in base alla scelta dell'utente, tramite la direttiva import. Questi moduli, di default1) vengono installati nella directory

  /usr/lib/hybris/library/
  

E seguono un alberatura di directory ben precisa, che rappresenta il loro “namespace”. Ad esempio, il comando :

import std.io.console;

Caricherà il modulo :

  /usr/lib/hybris/library/std/io/console.so
  

e così via.

Per sviluppare un modulo, è necessaria una buona conoscenza del C/C++, un ambiente di sviluppo che soddisfi i requisiti per la compilazione e la conoscenza di alcune API che il kit di sviluppo di Hybris offre .

Modulo Hello World

Andiamo ora a vedere un modulo molto semplice che esporterà la funzione say_hello .

#include <hybris.h>
 
HYBRIS_DEFINE_FUNCTION(say_hello);
 
HYBRIS_EXPORTED_FUNCTIONS() {
	{ "say_hello", say_hello,  H_REQ_ARGC(0) },
	{ "", NULL }
};
 
HYBRIS_DEFINE_FUNCTION(say_hello){
    printf( "Hello world from my first Hybris module!\n" );
 
    return H_DEFAULT_RETURN;
}

Analiziamo i vari elementi che compongono il modulo.

#include <hybris.h>

Con questa direttiva includiamo l'header hybris.h necessario per poter utilizzare le API del kit di sviluppo dei moduli.

HYBRIS_DEFINE_FUNCTION(say_hello);

Dichiariamo con questa macro, il prototipo della funzione che vogliamo esportare.

HYBRIS_EXPORTED_FUNCTIONS() {
	{ "say_hello", say_hello, H_REQ_ARGC(0) },
	{ "", NULL }
};

Questa lista contiene la descrizione vera e propria della funzione che stiamo esportando :

  • “say_hello” è il nome che la funzione avrà all'interno di uno script Hybris.
  • say_hello è il puntatore alla funzione nel modulo, della quale abbiamo appena dichiarato il prototipo con la macro HYBRIS_DEFINE_FUNCTION.
  • H_REQ_ARGC(0) indica che la funzione non richiede nessun (zero) parametro.
  • { ””, NULL } termina la lista, va sempre inserito alla fine della lista delle funzioni come terminatore.
HYBRIS_DEFINE_FUNCTION(say_hello){
    printf( "Hello world from my first Hybris module!\n" );
 
    return H_DEFAULT_RETURN;
}

Questa è la funzione vera e propria, che stamperà il messaggio “Hello world from my first Hybris module!”. Ogni funzione deve restituire un valore quindi, in questo caso, utiliziamo la macro H_DEFAULT_RETURN ad indicare che stiamo restituendo un valore di default non dovendo restituire nulla in particolare.

Compilazione

Per compilare il modulo appena creato, utilizzeremo i seguenti comandi :

g++ -c -export-dynamic -fPIC hello.cpp
g++ -shared -lc -o hello.so hello.o -lhybris

Dove :

  • hello.cpp è il nome del sorgente del modulo.
  • hello.o è il file intermedio che viene generato durante la compilazione
  • hello.so è il nome finale che avrà il modulo

Installazione

Per installare un modulo, bisogna prima decidere il suo namespace, ovvero in quale alberatura di directory si troverà e di conseguenza quali namespace passare alla direttiva import per caricarlo. Nel nostro caso, ipotiziamo il namespace “test”, di conseguenza il file del modulo andrà copiato in :

  /usr/lib/hybris/library/test/hello.so
  

E potrà essere richiamato ed usato come nel seguente esempio :

import test.hello;
 
say_hello();
  Hello world from my first Hybris module!
  

Modulo Math

Nel precedente modulo, abbiamo considerato una semplice funzione che non accetta argomenti in ingresso, ne restituisce alcun valore (se non quello di default) in output. Consideriamo ora un esempio più elaborato, un modulo “math” che esporta la funzione “add” la quale eseguirà l'addizione tra due numeri e ne restituirà il risultato. In questo caso la funzione, accettando due parametri, deve essere impostata per accettare anche due tipologie diverse (delle quali parleremo in un paragrafo successivo) per ogni parametro, ovvero ognuno dei due parametri potrà essere o un numero intero o un numero reale.

#include <hybris.h>
 
HYBRIS_DEFINE_FUNCTION(add);
 
HYBRIS_EXPORTED_FUNCTIONS() {
	{ "add", add,  H_REQ_ARGC(2), { H_REQ_TYPES( otInteger, otFloat ), H_REQ_TYPES( otInteger, otFloat ) } },
	{ "", NULL }
};
 
HYBRIS_DEFINE_FUNCTION(add){
	// prendo gli argomenti passati alla funzione dallo script    
	Object *arg_0,
		   *arg_1;
 
	arg_0 = ob_argv(0);
	arg_1 = ob_argv(1);
 
	// prelevo il valore double dai due argomenti, in base alla loro
	// tipologia
	double a, b;
 
	if( ob_is_int( arg_0) ){
        a = (double)ob_int_val( arg_0 );
    }
    else{
        a = ob_float_val( arg_0 );
    }
 
    if( ob_is_int( arg_1 ) ){
        b = (double)ob_int_val( arg_1 );
    }
    else{
        b = ob_float_val( arg_1 );
    }
 
	// eseguo la somma e restituisco un numero reale come risultato
    return (Object *)gc_new_float( a + b );
}

Come è possibile vedere, l'esempio è leggermente diverso dal modulo precedente, andiamo ad analizzare le differenze principali .

{ "add", add,  H_REQ_ARGC(2), { H_REQ_TYPES( otInteger, otFloat ), H_REQ_TYPES( otInteger, otFloat ) } }

Mentre prima utilizzavamo H_REQ_ARGC(0) e basta, poichè la funzione non richiedeva dei parametri in ingresso, questa volta la funzione richiede due parametri in ingresso ( H_REQ_ARGC(2) ), ognuno dei quali può rispettivamente essere un numero intero o un numero reale H_REQ_TYPES( otInteger, otFloat ) .

// prendo gli argomenti passati alla funzione dallo script    
Object *arg_0,
       *arg_1;

arg_0 = ob_argv(0);
arg_1 = ob_argv(1);

Ogni oggetto di Hybris, è rappresentato dalla struttura “Object” (definita nel file types.h) che naturalmente non può essere direttamente usato come un tipo C/C++. In questo blocco di codice, tramite la macro ob_argv( n ) dove n rappresenta l'n-esimo argomento della funzione che ci interessa, preleviamo i due argomenti della funzione add e li mettiamo dentro le variabili arg_0 (il primo argomento) e arg_1 (il secondo).

// prelevo il valore double dai due argomenti, in base alla loro
// tipologia
double a, b;
 
if( ob_is_int( arg_0) ){
    a = (double)ob_int_val( arg_0 );
}
else{
    a = ob_float_val( arg_0 );
}
 
if( ob_is_int( arg_1 ) ){
    b = (double)ob_int_val( arg_1 );
}
else{
    b = ob_float_val( arg_1 );
}

Qui invece eseguiamo la trasformazione da struttura Object ad una variabile di tipologia double, quindi compatibile con il C/C++, per effettuare comodamente la nostra addizione. Dal momento che ognuno degli argomenti può essere sia un intero che un numero reale, utiliziamo la macro ob_is_int per determinare se il numero è intero, ed in tal caso ne preleviamo il valore int tramite la macro ob_int_val e lo castiamo a double. Viceversa, se la variabile è di tipo reale (quindi ob_is_int restituirà false), preleviamo direttamente il suo valore tramite la macro ob_float_val. A questo punto, abbiamo i valori double dentro le variabili a e b e possiamo procedere ad effettuare l'addizione e a restituire un oggetto reale.

return (Object *)gc_new_float( a + b );

Il cast ad “Object *” è necessario poichè, a prescindere dalla sua tipologia (reale, intero, stringa, ecc), il valore di ritorno di ogni funzione deve essere riportato ad una tipologia base comune, appunto la struttura Object. La macro gc_new_float richiederà al garbage collector di creare un nuovo oggetto di tipo reale, inizializzandolo con il valore della somma “a + b”.

Inizializzazione di un Modulo

Se il modulo avesse la necessità di eseguire determinate operazioni, ad esempio inizializzare delle variabili globali, durante la fase di caricamento, deve esportare la funzione hybris_module_init nel seguente modo :

extern "C" void hybris_module_init( vm_t * vm ){
    printf( "Hello World!\n" );
}

La struttura vm_t identifica un istanza alla virtual machine stessa e non va assolutamente toccata, a meno che non si è perfettamente consapevoli di cosa si sta facendo. Se esportata quindi, questa funzione verrà eseguita all'avvio del modulo.

Definizione di Costanti

Se il modulo avesse la necessità di definire alcune variabili costanti (che quindi sono visibili globalmente nello script e che non possono essere modificate), come ad esempio, nel caso del modulo math precedentemente analizzato, il valore del pi greco, può usare la macro HYBRIS_DEFINE_CONSTANT all'interno della funzione di inizializzazione appena vista, come ad esempio :

extern "C" void hybris_module_init( vm_t * vm ){
    HYBRIS_DEFINE_CONSTANT( vm, "PI", gc_new_float(3.14159) );
}

Dove

  • vm è l'istanza della virtual machine che viene passata alla funzione di inizializzazione.
  • “PI” è l'identificatore che dovrà essere utilizzato per accedere al valore della costante.
  • gc_new_float(3.14159) crea un oggetto di tipo float inizializzandolo con il valore 3.14159. 2)

Le API dell'SDK di Hybris

Codici delle tipologie

  • otBoolean Booleano.
  • otInteger Intero.
  • otFloat Reale.
  • otChar Carattere.
  • otString Stringa.
  • otBinary Binario.
  • otVector Vettore/Array.
  • otMap Mappa.
  • otAlias Alias.
  • otExtern Extern.
  • otHandle Handle.
  • otStructure Struttura.
  • otClass Classe.
  • otReference Riferimento.

Creazione e allocazione di oggetti

  • gc_new_boolean(v) Crea ed inizializza con il valore 'v' un oggetto booleano.
  • gc_new_integer(v) Crea ed inizializza con il valore 'v' un numero intero.
  • gc_new_alias(v) Crea ed inizializza con il valore 'v' un oggetto alias.
  • gc_new_extern(v) Crea ed inizializza con il valore 'v' un oggetto extern.
  • gc_new_float(v) Crea ed inizializza con il valore 'v' un numero reale.
  • gc_new_char(v) Crea ed inizializza con il valore 'v' un carattere.
  • gc_new_string(v) Crea ed inizializza con il valore 'v' una stringa.
  • gc_new_binary(d) Crea ed inizializza con il valore 'v' un oggetto binary.
  • gc_new_vector() Crea un vettore.
  • gc_new_map() Crea una mappa.
  • gc_new_struct() Crea una struttura.
  • gc_new_class() Crea una classe.
  • gc_new_reference(o) Crea un riferimento all'oggetto 'o'.
  • gc_new_handle(p) Crea ed inizializza con il puntatore 'p' un oggetto handle.

Controllo dei tipi

  • ob_is_boolean(o) Determina se un oggetto è booleano.
  • ob_is_int(o) Determina se un oggetto è intero.
  • ob_is_alias(o) Determina se un oggetto è un alias.
  • ob_is_extern(o) Determina se un oggetto è un extern.
  • ob_is_float(o) Determina se un oggetto è un numero reale.
  • ob_is_char(o) Determina se un oggetto è un carattere.
  • ob_is_string(o) Determina se un oggetto è una stringa.
  • ob_is_binary(o) Determina se un oggetto è di tipo binary.
  • ob_is_vector(o) Determina se un oggetto è un vettore.
  • ob_is_map(o) Determina se un oggetto è una mappa.
  • ob_is_struct(o) Determina se un oggetto è una struttura.
  • ob_is_class(o) Determina se un oggetto è una classe.
  • ob_is_reference(o) Determina se un oggetto è un riferimento.
  • ob_is_handle(o) Determina se un oggetto è un handle.

API di elaborazione avanzata

bool ob_is_type_in( Object *o, ... );

Restituisce true se l'oggetto 'o' è di una delle tipologie specificate nei parametri successivi, altrimenti restituisce false.

const char *ob_typename( Object * o );

Restituisce la tipologia dell'oggetto 'o' sotto forma di stringa.

Object* ob_clone( Object *o );

Crea un clone dell'oggetto 'o'.

size_t ob_get_size( Object *o );

Restituisce la grandezza dell'oggetto 'o' o, nel caso sia una collezione, il numero degli elementi che contiene.

int ob_cmp( Object *o, Object * cmp );

Esegue un confronto tra l'oggetto 'o' e 'cmp', restituisce :

  • 0 se o == cmp
  • 1 se o > cmp
  • -1 se o < cmp
long ob_ivalue( Object *o );

Esegue, se possibile, il cast ad int dell'oggetto e ne restituisce il valore intero.

double ob_fvalue( Object *o );

Esegue, se possibile, il cast a double dell'oggetto e ne restituisce il valore double.

bool ob_lvalue( Object *o );

Restituisce la rappresentazione logica dell'oggetto.

string ob_svalue( Object *o );

Restituisce la rappresentazione sotto forma di stringa dell'oggetto.

void ob_print( Object *o, int tabs = 0 );

Stampa l'oggetto 'o' sullo stdout, precedendolo con 'tabs' tabulazioni.

Object *ob_to_string( Object *o );

Converte, se possibile, l'oggetto 'o' in un oggetto stringa.

Object *ob_to_int( Object *o );

Converte, se possibile, l'oggetto 'o' in un oggetto intero.

Object *ob_cl_push( Object *a, Object *b );

Crea un clone di 'b' e richiama ob_cl_push_reference per aggiungere in coda il clone alla collezione 'a'.

Object *ob_cl_push_reference( Object *a, Object *b );

Aggiunge in coda l'oggetto 'b' alla collezione 'a'.

Object *ob_cl_pop( Object *o );

Rimuove l'ultimo elemento della collezione 'o' e lo restituisce.

Object *ob_cl_remove( Object *a, Object *b );

Rimuove l'elemento all'indice (o chiave se una mappa) 'b' dalla collezione 'a'.

Object *ob_cl_at( Object *a, Object *b );

Restituisce l'elemento all'indice (o chiave se una mappa) 'b' dalla collezione 'a'.

Object *ob_cl_set( Object *a, Object *b, Object *c );

Crea un clone di 'c' e richiama ob_cl_set_reference( a, b, clone ) per inserire il clone all'indice/chiave 'b' nella collezione 'a'.

Object *ob_cl_set_reference( Object *a, Object *b, Object *c );

Inserisce l'oggetto 'c' all'indice/chiave 'b' dentro la collezione 'a'.

1) Nel caso in cui non venga modificata manualmente la variabile “PREFIX” nel file di compilazione.
2) Vedere paragrafo sulle API di creazione degli oggetti.
Ultima modifica: 2010/05/17 02:10 da evilsocket