• Non ci sono risultati.

Enum e Flags

Le Enum in C sono un tipo di dato che pu`o assumere un numero finito di valori. Questi valori vengono elencati associando un identificatore ad un valore intero. Ad esempio in GTK l’orientamento di una pagina viene definito tramite la seguente enum:

1 t y p e d e f e n u m { 2 G T K _ P A G E _ O R I E N T A T I O N _ P O R T R A I T = 0 , 3 G T K _ P A G E _ O R I E N T A T I O N _ L A N D S C A P E = 1 , 4 G T K _ P A G E _ O R I E N T A T I O N _ R E V E R S E _ P O R T R A I T = 2 , 5 G T K _ P A G E _ O R I E N T A T I O N _ R E V E R S E _ L A N D S C A P E = 3 6 } G t k P a g e O r i e n t a t i o n ;

Listing 4.5: Esempio di enum in C

Nel caso in cui i valori interi non vengano definiti esplicitamente, di default verranno assegnati degli interi crescenti a partire da 0.

Un uso che viene fatto delle enum in C `e quello di definire dei flag. Questi sono delle enum dove i valori interi sono delle potenze di 2, in modo da poter essere combinati tramite OR bitwise per rappresentare una combinazione di pi`u valori della enum.

4.3.1

Rappresentazione in lablgtk3

In lablgtk3 le enum vengono rappresentate in OCaml tramite varianti polimorfe con costruttori senza parametri. Un’altra possibile scelta sarebbe potuta essere quella di rap- presentarle tramite ADT di tipo somma, ma siccome diverse enum possono condividere un costruttore con lo stesso nome, la scelta delle varianti polimorfe `e particolarmente adatta.

I costruttori delle varianti polimorfe vengono rappresentati in C tramite degli interi, dove il valore di questi viene calcolato tramite una funzione di hashhash_variantmessa a

disposizione dalla libreria di interfacciamento tra codice OCaml e C, che calcola il valore intero a partire dal nome del costruttore. Il meccanismo usato per far corrispondere ad

ogni costruttore dell’enum il valore intero originario dell’enum `e quello di definire una struct lookup_info che associa ad una chiave di tipo value un valore di tipo int. Ogni

enum viene poi codificata come un array di queste struct, dove il primo elemento contiene come valore la dimensione dell’array.

1 c o n s t l o o k u p _ i n f o m l _ g i g t k _ t a b l e _ p a g e _ o r i e n t a t i o n [] = { 2 { 0 , 4 } , 3 { M L T A G _ R E V E R S E _ L A N D S C A P E , G T K _ P A G E _ O R I E N T A T I O N _ R E V E R S E _ L A N D S C A P E } , 4 { M L T A G _ L A N D S C A P E , G T K _ P A G E _ O R I E N T A T I O N _ L A N D S C A P E } , 5 { M L T A G _ P O R T R A I T , G T K _ P A G E _ O R I E N T A T I O N _ P O R T R A I T } , 6 { M L T A G _ R E V E R S E _ P O R T R A I T , G T K _ P A G E _ O R I E N T A T I O N _ R E V E R S E _ P O R T R A I T } , 7 };

Listing 4.6: Esempio di tabella lookup info in C

Lablgtk3 definisce infine due funzioni, ml_lookup_from_c(const lookup_info table[], int data)eml_lookup_to_c(const lookup_info table[], value key), per recuperare i valori

contenuti dentro unalookup_info. Queste funzioni scandiscono l’array in input finch´e non

trovano la chiave/valore cercata, per poi restituire il valore/chiave corrispondente. Per abbassare la complessit`a di questa operazione viene assunto che le chiavi di lookup_info

(ovvero i valori di hash) siano ordinate in ordine crescente. Riassumendo, la conversione avviene in 3 fasi:

• Vengono generate lato OCaml delle varianti polimorfe per rappresentare i valori dell’enum;

• Ad ogni costruttore delle varianti polimorfe va fatto corrispondere un valore di hash;

• Ad ogni valore di hash deve corrispondere il valore intero originario contenuto nell’enum.

La conversione dei flag avviene in modo analogo a quella delle enum, con la differenza che in OCaml i costruttori delle varianti polimorfe non vengono usati singolarmente, ma contenuti in una lista. Per questo motivo `e necessario definire degli ulteriori binding in C che siano in grado di convertire una lista OCaml contenente i costruttori della variante

polimorfa nel valore reale del flag (ovvero l’OR bitwise). Per definire tali binding sono presenti due macro, Make_Flags_vale Make_OptFlags_val.

4.3.2

Generazione in ocaml-gi-gtk

In ocaml-gi-gtk `e stato deciso di seguire lo stesso approccio di lablgtk3 nella genera- zione di enums e flags.

Il parser di haskell-gi mette a disposizione le seguenti informazioni sulle enum:

1 d a t a E n u m e r a t i o n = E n u m e r a t i o n { 2 e n u m M e m b e r s :: [ E n u m e r a t i o n M e m b e r ] , 3 ... 4 } 5 6 d a t a E n u m e r a t i o n M e m b e r = E n u m e r a t i o n M e m b e r { 7 e n u m M e m b e r N a m e :: Text , 8 e n u m M e m b e r V a l u e :: Int64 , 9 e n u m M e m b e r C I d :: Text , 10 ... 11 }

Listing 4.7: API di haskell-gi per enums

Usando queste informazioni vengono generati in bindings usando lo stesso approccio di lablgtk3. Nello specifico vengono generati tre file, Enums.h, Enums.c ed Enums.ml.

Enums.h

Per ogni enum e flag vengono generati:

• i valori di hash corrispondenti ai costruttori delle varianti polimorfe OCaml. Per calcolare il valore di hash `e stata convertita in Haskell la funzione OCaml che calcola tali valori;

• la signature della tabella di lookup, definita nel file Enums.c;

1 ... 2 # d e f i n e M L T A G _ P O R T R A I T (( v a l u e ) ( 3 0 5 4 4 3 1 6 3 * 2 + 1 ) ) 3 # d e f i n e M L T A G _ L A N D S C A P E (( v a l u e ) ( - 1 3 3 8 3 0 6 2 9 * 2 + 1 ) ) 4 # d e f i n e M L T A G _ R E V E R S E _ P O R T R A I T (( v a l u e ) ( 7 6 5 8 4 8 4 4 0 * 2 + 1 ) ) 5 # d e f i n e M L T A G _ R E V E R S E _ L A N D S C A P E (( v a l u e ) ( - 5 4 2 6 6 8 9 6 2 * 2 + 1 ) ) 6 7 e x t e r n c o n s t l o o k u p _ i n f o m l _ g i g t k _ t a b l e _ p a g e _ o r i e n t a t i o n []; 8 # d e f i n e G I _ V a l _ G t k P a g e O r i e n t a t i o n ( d a t a ) m l _ l o o k u p _ f r o m _ c ( m l _ g i g t k _ t a b l e _ p a g e _ o r i e n t a t i o n , d a t a ) 9 # d e f i n e G I _ G t k P a g e O r i e n t a t i o n _ v a l ( key ) m l _ l o o k u p _ t o _ c ( m l _ g i g t k _ t a b l e _ p a g e _ o r i e n t a t i o n , key ) 10 ...

Listing 4.8: Estratto del contenuto di Enums.h

Enums.c

Per ogni enum e flag vengono generati:

• la tabella di lookup, facendo attenzione ad ordinare gli elementi secondo l’ordine crescente del valore di hash;

• una funzione che restituisce la tabella di lookup castata al tipo value;

• (solo per i flag) vengono chiamate le macro Make_Flags_vale Make_OptFlags_val per

generare le funzioni di conversione per le liste di flag OCaml.

Enums.ml

Per ogni enum e flag vengono generati:

• il tipo OCaml dell’enum/flag, ovvero una variante polimorfa contenente i possibili costruttori;

• un bindingexternalalla funzione per ottenere la tabella di lookup. Questa funzione

• un data_conv per l’enum/flag usando la tabella di lookup appena ottenuta. I data_conv sono un meccanismo di lablgtk3 per rendere pi`u agevole la conversione

dei tipi tra OCaml e C nei binding delle Properiet`a e dei Segnali.

1 ... 2 t y p e p a g e _ o r i e n t a t i o n = [ ‘ P O R T R A I T | ‘ L A N D S C A P E | ‘ R E V E R S E _ P O R T R A I T | ‘ R E V E R S E _ L A N D S C A P E ] 3 4 e x t e r n a l g e t _ p a g e _ o r i e n t a t i o n _ t a b l e : u n i t - > p a g e _ o r i e n t a t i o n G p o i n t e r . v a r i a n t _ t a b l e = " m l _ g i g t k _ g t k _ g e t _ p a g e _ o r i e n t a t i o n _ t a b l e " 5 let p a g e _ o r i e n t a t i o n _ t b l = g e t _ p a g e _ o r i e n t a t i o n _ t a b l e () 6 let p a g e _ o r i e n t a t i o n = G o b j e c t . D a t a . e n u m p a g e _ o r i e n t a t i o n _ t b l 7 ...

Listing 4.9: Estratto del contenuto di Enums.ml

4.4

Oggetti

GTK `e una libreria basata su GObject, la quale fornisce la possibilit`a di strutturare il codice C secondo un modello ad oggetti tramite dei meccanismi di classi, ereditariet`a ed interfacce. Per ottenere dei binding OCaml che fossero il pi`u vicini possibile a questo modello, gli autori di lablgtk3 hanno fatto uso delle funzionalit`a ad oggetti di OCaml come visto nella sezione 2.4. In ocaml-gi-gtk i binding sono stati generati seguendo lo stesso schema, ma `e stata cambiata la convenzione dei nomi. Per ogni oggetto vengono definiti i seguenti file:

• *T.ml, contiene le definizioni dei tipi OCaml associati all’oggetto; • *.ml, corrisponde ai file di “basso livello” di lablgtk3;

• *G.ml, contiene le definizioni delle classi OCaml che rappresentano l’oggetto. Questo `e il file che verr`a usato dagli utenti della libreria;

• *.c, contiene i binding C;

Tali file vengono generati a partire dalle informazioni ottenute dal parser GIR di haskell-gi, che per ogni oggetto espone le seguenti informazioni:

1 d a t a O b j e c t = O b j e c t { 2 o b j P a r e n t :: M a y b e Name , 3 o b j T y p e I n i t :: Text , 4 o b j T y p e N a m e :: Text , 5 o b j C T y p e :: M a y b e Text , 6 o b j I n t e r f a c e s :: [ N a m e ] , 7 o b j D e p r e c a t e d :: M a y b e D e p r e c a t i o n I n f o , 8 o b j D o c u m e n t a t i o n :: D o c u m e n t a t i o n , 9 o b j M e t h o d s :: [ M e t h o d ] , 10 o b j P r o p e r t i e s :: [ P r o p e r t y ] , 11 o b j S i g n a l s :: [ S i g n a l ] 12 }

Listing 4.10: API di haskell-gi per gli oggetti

Nel resto della sezione seguiranno i dettagli su come queste informazioni vengono usate per la generazione di ogni componente degli oggetti.

4.4.1

Macro di conversione

Per ogni oggetto nei file *.c e *.h vengono generate delle macro per eseguire la conversione tra i tipi OCaml e C. Vengono generate tre macro:

• conversione da OCaml a C, la macro ha il nome dell’oggetto corrente, seguito dal suffisso _val. Prende in input il valore da convertire ed usa la macro check_cast di

lablgtk per effettuare la conversione. Questa macro controlla che il valore in input non sianull, e nel caso applica una macro di conversione definita dal sistema di tipi

di GTK (che `e definita per ogni GObject). Questa macro controlla che il puntatore ricevuto in input sia effettivamente del tipo relativo alla classe in cui vuole essere convertita, ed applica la conversione. Nel caso in cui il tipo non corrisponda, viene emesso un warning a runtime;

• conversione da C ad OCaml, la macro ha il nome dell’oggetto corrente, preceduto dal prefissoVal_. Non fa altro che usare la macroVal_GAnyObjectdi lablgtk. Questa

macro controlla che il puntatore ricevuto in input sia di tipo GObject e fa un cast a GObject*che `e compatibile con il tipo value di OCaml;

• conversione da C ad OCaml per un valore opzionale, ovvero che pu`o essere null e

che in OCaml viene codicato con il tipo option. Per questa conversione non basta

una macro, ma deve essere generata una funzione ad hoc nel file .c. Questa funzione viene generata tramite la macro Make_Val_option di lablgtk. La funzione generata

controlla che il valore contenuto nel puntatore che riceve come parametro non sia

nulle nel caso alloca un blocco OCaml che corrisponde al costruttoreSomedioption.

Nel caso il valore sianull, viene restituita la macro Val_unitche `e compatibile con

il costruttore None di option. Nel file .h viene riportata la signature della funzione

generata per renderla visibile agli altri file.

1 # d e f i n e G t k B u t t o n _ v a l ( val ) c h e c k _ c a s t ( G T K _ B U T T O N , val ) 2 # d e f i n e V a l _ G t k B u t t o n V a l _ G A n y O b j e c t

3 v a l u e O p t V a l _ G t k B u t t o n ( G t k B u t t o n *) ;

Listing 4.11: Esempio di macro di conversione nel file .h (GtkButton)

1 M a k e _ V a l _ o p t i o n ( G t k B u t t o n , V a l _ G t k B u t t o n )

Listing 4.12: Esempio di macro di conversione nel file .c (GtkButton)

1 C A M L p r i m v a l u e m l _ s o m e ( v a l u e v ) 2 { 3 C A M L p a r a m 1 ( v ) ; 4 v a l u e ret = a l l o c _ s m a l l (1 ,0) ; 5 F i e l d ( ret ,0) = v ; 6 C A M L r e t u r n ( ret ) ; 7 } 8 9 # d e f i n e V a l _ o p t i o n ( v , f ) ( v ? m l _ s o m e ( f ( v ) ) : V a l _ u n i t ) 10 # d e f i n e M a k e _ V a l _ o p t i o n ( T , Val ) \

11 v a l u e Opt ## Val ( T * v ) { r e t u r n V a l _ o p t i o n ( v , Val ) ; }

4.4.2

Segnali

I segnali sono un meccanismo di GObject per permettere l’esecuzione di una callback al verificarsi di un determinato evento. Ad esempio GtkButton possiede il segnaleclicked

che permette di eseguire una callback alla pressione del bottone. Le callback non possono essere funzioni qualsiasi, ma devono avere un certo tipo definito dal segnale stesso. In altre parole, le callback devono avere dei parametri di un certo tipo e restituire un valore di un determinato tipo.

I segnali vengono generati esattamente come erano definiti in lablgtk3, in modo da poter usare i meccanismi di creazione e gestione delle closure gi`a definiti in questa libreria.

Basso livello

Nel file *.ml viene definito un modulo interno S che per ogni segnale contiene dei record di tipo GtkSignal.t, dove GtkSignal `e un modulo di lablgtk3.

1 m o d u l e S = s t r u c t 2 o p e n G t k S i g n a l 3 o p e n G o b j e c t 4 o p e n D a t a 5 let p o p d o w n = { n a m e =" p o p d o w n "; c l a s s e = ‘ g t k _ s c a l e _ b u t t o n ; m a r s h a l l e r = m a r s h a l _ u n i t } 6 let p o p u p = { n a m e =" p o p u p "; c l a s s e = ‘ g t k _ s c a l e _ b u t t o n ; m a r s h a l l e r = m a r s h a l _ u n i t } 7 let v a l u e _ c h a n g e d = { n a m e =" v a l u e _ c h a n g e d "; c l a s s e = ‘ g t k _ s c a l e _ b u t t o n ; m a r s h a l l e r = 8 fun f - > m a r s h a l 1 d o u b l e " S c a l e B u t t o n :: v a l u e _ c h a n g e d " f } 9 end

Listing 4.14: Esempio di generazione dei segnali nel file *.ml

La parte pi`u complicata da generare `e la funzione di marshalling. Questa si occupa di fare la conversione tra i tipi OCaml e C per i parametri ed il valore di ritorno della callback. lablgtk3 mette a disposizione le funzionimarshalX, dove la X indica il numero di

parametri attesi dalla callback. Per ognuna di queste funzioni, ne esiste una variante con suffisso_retda usare nel caso in cui la callback abbia un valore di ritorno diverso davoid.

Gobject di lablgtk3. All’interno di questo modulo, sono anche definite delle possibili istanze di questo tipo da usare per convertire i tipi di dato di GLib e GObject. Fanno eccezione i data_conv per enum e flag, che sono non presenti in questo file ma vengono

generati nel modulo Enums.ml come descritto nella sezione precedente. Idata_convper i

tipi oggetto devono essere invece annotati con il tipo dell’oggetto da convertire poich`e il compilatore non `e in grado di inferirlo. Dalle API di haskell-gi `e possibile ottenere una propriet`a del segnale dl tipo Callable, e tramite questa `e possibile ottenere ilTypedi tutti

i parametri e valore di ritorno della callback associata al segnale. Nel file Conversions.hs

`

e stata definita una funzione ocamlDataConv che converte un Type nel correttodata_conv.

1 m o d u l e S = s t r u c t 2 ...

3 let d r a g _ a c t i o n _ a s k = { . . . ; m a r s h a l l e r

4 fun f - > m a r s h a l 1 _ r e t ~ ret : int int " P l a c e s S i d e b a r ::

d r a g _ a c t i o n _ a s k " f } 5 6 let m o u n t = { . . . ; m a r s h a l l e r =fun f - > 7 m a r s h a l 1 8 ( g o b j e c t : G I G i o . M o u n t O p e r a t i o n T . t G o b j e c t . obj d a t a _ c o n v ) 9 " P l a c e s S i d e b a r :: m o u n t " f } 10 11 let o p e n _ l o c a t i o n = { . . . ; m a r s h a l l e r =fun f - > 12 m a r s h a l 2 13 ( g o b j e c t : G I G i o . F i l e T . t G o b j e c t . obj d a t a _ c o n v ) 14 E n u m s . p l a c e s _ o p e n _ f l a g s 15 " P l a c e s S i d e b a r :: o p e n _ l o c a t i o n " f } 16 17 let p o p u l a t e _ p o p u p = { . . . ; m a r s h a l l e r =fun f - > 18 m a r s h a l 3 19 ( g o b j e c t : W i d g e t T . t G o b j e c t . obj d a t a _ c o n v ) 20 ( g o b j e c t _ o p t i o n : G I G i o . F i l e T . t G o b j e c t . obj o p t i o n d a t a _ c o n v ) 21 ( g o b j e c t _ o p t i o n : G I G i o . V o l u m e T . t G o b j e c t . obj o p t i o n d a t a _ c o n v ) " P l a c e s S i d e b a r :: p o p u l a t e _ p o p u p " f } 22 ... 23 end

Alto livello

Nel file *G.ml viene invece definita una classe adibita al contenere i soli segnali. Questa classe porta il nome dell’oggetto in esame, seguita dal suffisso _signals. Per ogni

segnale dell’oggetto viene generato un metodo avente il nome del segnale ed implementato attraverso un metodoconnect. Questo metodo viene ereditato dalla classegobject_signals

da cui tutte le classi _signals ereditano.

1 c l a s s b u t t o n _ s i g n a l s obj = o b j e c t ( s e l f ) 2 ... 3 m e t h o d a c t i v a t e = s e l f # c o n n e c t B u t t o n . S . a c t i v a t e 4 m e t h o d c l i c k e d = s e l f # c o n n e c t B u t t o n . S . c l i c k e d 5 m e t h o d e n t e r = s e l f # c o n n e c t B u t t o n . S . e n t e r 6 m e t h o d l e a v e = s e l f # c o n n e c t B u t t o n . S . l e a v e 7 m e t h o d p r e s s e d = s e l f # c o n n e c t B u t t o n . S . p r e s s e d 8 m e t h o d r e l e a s e d = s e l f # c o n n e c t B u t t o n . S . r e l e a s e d 9 end

Listing 4.16: Esempio di generazione di una classe signals (GtkButton)

Il metodo connect applicato ai GtkSignal.t definiti nel file di basso livello produce una

funzione che riceve in input una funzione OCaml. Facendo uso del marshaller definito nel file di basso livello viene creata una GClosure, un tipo di GLib per definire una callback.

Tale closure viene associata al segnale tramite il metodo g_signal_connect_closure di

GObject e la funzione OCaml ricevuta in input sar`a eseguita all’attivarsi dell’evento associato al segnale.

4.4.3

Propriet`a

Le propriet`a sono un meccanismo di GObject per definire quelli che nella program- mazione orientata agli oggetti sono conosciuti come attributi di classe. Anche in questo caso vengono sfruttati i meccanismi di lablgtk3 per effettuare i binding.

1 d a t a P r o p e r t y F l a g = P r o p e r t y R e a d a b l e 2 | P r o p e r t y W r i t a b l e

3 | P r o p e r t y C o n s t r u c t 4 | P r o p e r t y C o n s t r u c t O n l y 5 d e r i v i n g (Show,Eq)

6 7 d a t a P r o p e r t y = P r o p e r t y { 8 p r o p N a m e :: Text , 9 p r o p T y p e :: Type , 10 p r o p F l a g s :: [ P r o p e r t y F l a g ] , 11 p r o p R e a d N u l l a b l e :: M a y b e Bool, 12 p r o p W r i t e N u l l a b l e :: M a y b e Bool, 13 p r o p T r a n s f e r :: T r a n s f e r , 14 p r o p D o c :: D o c u m e n t a t i o n , 15 p r o p D e p r e c a t e d :: M a y b e D e p r e c a t i o n I n f o 16 } d e r i v i n g (Show, Eq)

Listing 4.17: Informazioni sulle propriet`a estratte dal file GIR

Basso livello

Nel file di basso livello *.ml viene creato un modulo interno P contenente dentro al quale per ogni propriet`a posseduta dall’oggetto corrente viene generata una variabile di tipo Gobject.property. Questo `e un record contenente un campo name a cui va assegnato

il nome della propriet`a ed un campo conv a cui va assegnato il data_conv del tipo della

propriet`a. Per ottenere quest’ultimo viene ottenuto il tipoType della propriet`a corrente e

1 m o d u l e P = s t r u c t 2 o p e n G o b j e c t 3 o p e n D a t a 4 let a l w a y s _ s h o w _ i m a g e : ([ > B u t t o n T . t ] , _ ) p r o p e r t y = 5 { n a m e =" always - show - i m a g e "; c o n v = b o o l e a n } 6 let i m a g e : ([ > B u t t o n T . t ] , _ ) p r o p e r t y = 7 { n a m e =" i m a g e "; 8 c o n v =( g o b j e c t _ o p t i o n : W i d g e t T . t G o b j e c t . obj o p t i o n d a t a _ c o n v ) } 9 let i m a g e _ p o s i t i o n : ([ > B u t t o n T . t ] , _ ) p r o p e r t y = 10 { n a m e =" image - p o s i t i o n "; c o n v = E n u m s . p o s i t i o n _ t y p e } 11 let l a b e l : ([ > B u t t o n T . t ] , _ ) p r o p e r t y = 12 { n a m e =" l a b e l "; c o n v = s t r i n g } 13 let x a l i g n : ([ > B u t t o n T . t ] , _ ) p r o p e r t y = 14 { n a m e =" x a l i g n "; c o n v = f l o a t } 15 ... 16 end

Listing 4.18: Esempio di propriet`a generate (Button.ml)

Alto livello

Nel file di alto livello *G.ml le propriet`a vengono riportate all’interno di una classe avente suffisso _skel insieme ai metodi. Ad ogni propriet`a `e associata una lista di flag

(tipoPropertyFlag in haskell-gi) e a seconda dei flag di una propriet`a varia la generazione

del codice:

• read, indica che il valore della propriet`a pu`o essere letto, pertanto viene generato un metodo “getter”. Questo prende il nome della propriet`a (senza un prefissoget_)

e viene usata la funzioneGobject.get di lablgtk per implementare effettivamente la

funzionalit`a.

• write, indica che il valore della propriet`a `e modificabile, pertanto viene generato un metodo “setter”. Questo prende il nome della propriet`a con un prefisso set_ e

viene usata la funzione Gobject.set di lablgtk per implementare effettivamente la

• construct e construct-only, indicano che una propriet`a pu`o essere settata in fase di creazione dell’oggetto. Questi flag tuttavia non sono rilevanti in questa fase di generazione del codice, ma saranno trattate nella generazione dei costruttori 4.4.5. 1 c l a s s b u t t o n _ s k e l obj = o b j e c t ( s e l f ) 2 (* P r o p e r t i e s *) 3 m e t h o d a l w a y s _ s h o w _ i m a g e = G o b j e c t . get B u t t o n . P . a l w a y s _ s h o w _ i m a g e obj 4 m e t h o d s e t _ a l w a y s _ s h o w _ i m a g e = G o b j e c t . set B u t t o n . P . a l w a y s _ s h o w _ i m a g e obj 5 m e t h o d i m a g e = G o b j e c t . get B u t t o n . P . i m a g e obj 6 m e t h o d s e t _ i m a g e = G o b j e c t . set B u t t o n . P . i m a g e obj 7 m e t h o d i m a g e _ p o s i t i o n = G o b j e c t . get B u t t o n . P . i m a g e _ p o s i t i o n obj 8 m e t h o d s e t _ i m a g e _ p o s i t i o n = G o b j e c t . set B u t t o n . P . i m a g e _ p o s i t i o n obj 9 m e t h o d l a b e l = G o b j e c t . get B u t t o n . P . l a b e l obj 10 m e t h o d s e t _ l a b e l = G o b j e c t . set B u t t o n . P . l a b e l obj 11 m e t h o d x a l i g n = G o b j e c t . get B u t t o n . P . x a l i g n obj 12 m e t h o d s e t _ x a l i g n = G o b j e c t . set B u t t o n . P . x a l i g n obj 13 ...

Listing 4.19: Esempio di propriet`a generate (ButtonG.ml)

Le funzioni Gobject.get e Gobject.set tramite l’uso del data_conv sono in grado di con-

vertire il tipo OCaml della propriet`a nel tipo C, e fanno uso delle seguenti funzioni C di GObject per ottenere e modificare il valore delle propriet`a.

1 v o i d 2 g _ o b j e c t _ g e t _ p r o p e r t y ( G O b j e c t * object , 3 c o n s t g c h a r * p r o p e r t y _ n a m e , 4 G V a l u e * v a l u e ) ; 5 6 v o i d 7 g _ o b j e c t _ s e t _ p r o p e r t y ( G O b j e c t * object , 8 c o n s t g c h a r * p r o p e r t y _ n a m e , 9 c o n s t G V a l u e * v a l u e ) ;

4.4.4

Metodi

Seguendo l’esempio di lablgtk, per effettuare dei binding ai metodi di un oggetto `e necessario definire:

• le funzioni di stub in un file *.c;

• le dichiarazioni external nel file di basso livello *.ml. In queste dichiarazioni deve essere esplicitato il tipo della funzione;

• i metodi dentro una classe OCaml, che chiamano le funzioni external definite nel file di basso livello.

1 d a t a M e t h o d = M e t h o d { 2 m e t h o d N a m e :: Name , 3 m e t h o d S y m b o l :: Text , 4 m e t h o d T y p e :: M e t h o d T y p e , 5 m e t h o d M o v e d T o :: M a y b e Text , 6 m e t h o d C a l l a b l e :: C a l l a b l e 7 } d e r i v i n g (Eq, S h o w) 8 9 d a t a C a l l a b l e = C a l l a b l e { 10 r e t u r n T y p e :: M a y b e Type , 11 r e t u r n M a y B e N u l l :: Bool, 12 r e t u r n T r a n s f e r :: T r a n s f e r , 13 r e t u r n D o c u m e n t a t i o n :: D o c u m e n t a t i o n , 14 a r g s :: [ Arg ] , 15 s k i p R e t u r n :: Bool, 16 c a l l a b l e T h r o w s :: Bool, 17 c a l l a b l e D e p r e c a t e d :: M a y b e D e p r e c a t i o n I n f o , 18 c a l l a b l e D o c u m e n t a t i o n :: D o c u m e n t a t i o n 19 } d e r i v i n g (Show, Eq) 20 21 d a t a Arg = Arg { 22 a r g C N a m e :: Text , 23 a r g T y p e :: Type , 24 d i r e c t i o n :: D i r e c t i o n , 25 m a y B e N u l l :: Bool, 26 a r g D o c :: D o c u m e n t a t i o n , 27 a r g S c o p e :: Scope , 28 a r g C l o s u r e :: Int, 29 a r g D e s t r o y :: Int, 30 a r g C a l l e r A l l o c a t e s :: Bool, 31 t r a n s f e r :: T r a n s f e r 32 } d e r i v i n g (Show, Eq, Ord) 33 34 d a t a D i r e c t i o n = D i r e c t i o n I n 35 | D i r e c t i o n O u t 36 | D i r e c t i o n I n o u t 37 d e r i v i n g (Show, Eq, Ord)

Stub C

Gli stub vengono generati sfruttando le macro ML_X descritte nella sezione 4.2. Le

informazioni necessarie per la generazione di queste macro sono contenute nel campo

methodCallable di Method. Da quest’ultimo vengono estratte le informazioni relative ai

parametri del metodo tramite il campo args. Ogni parametro p possiede un campo

direction che indica se p `e un parametro di input, di output o entrambi. I parametri

sia di input che di output non sono attualmente gestiti e viene fallita la generazione del metodo. Altrimenti, se il metodo contiene solo parametri di input allora viene generata

una macro ML_X. Se sono presenti parametri di output viene invece usata una macro

ML_Xin_Yout, dove x indica il numero di parametri di input, e y indica il numero di

parametri di output.

1 # d e f i n e M L _ 2 i n _ 2 o u t ( n a m e s p a c e , cname , conv1in , conv2in , c T y p e 1 o u t ,

c o n v 1 o u t , c T y p e 2 o u t , c o n v 2 o u t , c o n v R e s ) \

2 C A M L p r i m v a l u e m l _ g i ## n a m e s p a c e ## _ ## c n a m e ( v a l u e arg1 , v a l u e a r g 2 ) \ 3 { \

4 C A M L p a r a m 2 ( arg1 , a r g 2 ) ; C A M L l o c a l 1 ( t u p l e ) ; \

5 c T y p e 1 o u t a ; c T y p e 2 o u t b ; t u p l e = a l l o c _ t u p l e (3) ; \

6 v a l u e res = c o n v R e s ( c n a m e ( c o n v 1 i n ( a r g 1 ) , c o n v 2 i n ( a r g 2 ) , & a , & b ) ) ; \ 7 S t o r e _ f i e l d ( tuple , 0 , res ) ; S t o r e _ f i e l d ( tuple , 1 , c o n v 1 o u t ( a ) ) ;

S t o r e _ f i e l d ( tuple , 2 , c o n v 2 o u t ( b ) ) ; \

8 C A M L r e t u r n ( t u p l e ) ; \ 9 }

Listing 4.22: Esempio di macroML_Xin_Yout

In aggiunta ai parametri presenti anche nelle macro ML_X, questa macro riceve in input

anche il tipo C dei parametri di output. Questi tipi vengono usati per allocare sullo stack le variabili che saranno passate per indirizzo alla funzione C. Viene infine allocata una tupla OCaml nella quale vengono inseriti i valori (convertiti nel tipo OCaml) di ritorno della funzione e dei parametri di output. Infine questa tupla viene restituita. Per ogni macro ML_Xin_Yout `e presente anche una macro ML_Xin_Yout_discard_ret che viene usata

per le funzioni che restituiscono void. In questo caso il valore di ritorno della funzione

non viene inserito nella tupla. L’uso di queste macroML_Xin_Youtpresenta tuttavia alcuni

• `e stato assunto che nelle funzioni C i parametri di input precedessero sempre i parametri di output. Tuttavia sono stati riscontrati casi in altre librerie della gerarchia di GTK dove questa assunzione `e errata;

• i parametri di output con tipo non di base vengono attualmente allocati sullo stack e vengono distrutti alla fine del corpo della funzione. La soluzione prevede di allocarli sullo heap tramitemalloc, ma non tutti i tipi non di base di GObject sono

allocabili in questo modo.

Questi problemi rendono la generazione del codice C poco uniforme e quindi non adatta all’uso di macro C. Pertanto gli stub per le funzioni con parametri di output andrebbero generati scrivendo direttamente in output le funzioni C.

Infine, come spiegato nella sezione 2.3.1, se uno stub ha pi`u di 5 parametri deve essere generato anche uno stub che riceve tali parametri attraverso un array ed il nome di entrambi gli stub va riportato nella dichiarazione external nel file di basso livello *.ml. La generazione di questi stub viene effettuata attraverso le macro ML_bcX dove x `e il

numero di parametri ricevuti dallo stub.

Basso livello

Nel file OCaml di basso livello vengono generate le dichiarazioni external che devono effettuare il binding con gli stub C. Queste dichiarazioni devono anche annotare esplici- tamente il tipo della funzione. Questa operazione `e complicata in quanto i tipi OCaml da annotare sono anche tipi non di base che fanno uso di pi`u costruttori di tipo. Per gestire la generazione dei tipi `e stato quindi definito un tipo intermedio (tra il tipo Type presente nelle API di haskell-gi ed il tipo testuale OCaml) tramite il seguente ADT:

1 d a t a R o w D i r e c t i o n = L e s s | M o r e 2 d e r i v i n g (Show, Eq) 3 4 d a t a T y p e R e p = 5 L i s t C o n T y p e R e p - - l i s t t y p e c o n s t r u c t o r 6 | O p t i o n C o n T y p e R e p - - o p t i o n t y p e c o n s t r u c t o r 7 | O b j C o n T y p e R e p - - G o b j e c t . obj t y p e c o n s t r u c t o r 8 | R o w C o n R o w D i r e c t i o n T y p e R e p - - [ > ] and [ < ] t y p e c o n s t r u c t o r

9 | T y p e V a r C o n T e x t T y p e R e p - - a d d s a t y p e v a r i a b l e ( ’ a , ’b , . . . ) 10 | T u p l e C o n [ T y p e R e p ] - - t u p l e t y p e c o n s t r u c t o r ( * * ) 11 | P o l y C o n T y p e R e p - - To r e p r e s e n t a p o l y m o r p h i c v a r i a n t c o n s t r u c t o r 12 | T e x t C o n T e x t - - For a t o m i c t y p e s 13 | N a m e C o n N a m e - - For o b j e c t / i n t e r f a c e s 14 d e r i v i n g (Show, Eq)

Listing 4.23: Tipo TypeRep per una rappresentazione intermedia dei tipi OCaml Il tipo TypeRep `e ricorsivo e presenta come costruttori di baseTextConeNameCon, usati

rispettivamente per i tipi di base e per gli oggetti/interfacce. La conversione da Type a TypeRep avviene nelle funzioni ocamlTypee outParamOcamlType di Conversions.hs. Invece

tramite la funzione typeShow di TypeRep.hs vengono convertiti in testo i TypeRep di

ogni parametro dei metodi. Vengono usate due funzioni distinte per ottenere i TypeRep poich`e i tipi relativi ad oggetti ed interfacce hanno una rappresentazione diversa in base a seconda che siano dei parametri di input o dei parametri di output. In entrambi i casi il tipo atteso `e quello di una variante polimorfa racchiusa nel tipo Gobject.obj. Tuttavia

viene variato il vincolo sulla variabile row espresso tramite lo zucchero sintattico > o <:

• >viene usato per i parametri di input, rendendo cos`ı possibile passare in input alla

funzione anche delle sottoclassi del tipo atteso;

• < viene invece usato per i parametri di output, non permettendo la costruzione di

un oggetto pi`u in basso nella gerarchia.

1 e x t e r n a l e n t e r : 2 [ > B u t t o n T . t ] G o b j e c t . obj - > 3 u n i t 4 = " m l _ g i g t k _ g t k _ b u t t o n _ e n t e r " 5 6 e x t e r n a l g e t _ a l i g n m e n t : 7 [ > B u t t o n T . t ] G o b j e c t . obj - > 8 ( f l o a t * f l o a t ) 9 = " m l _ g i g t k _ g t k _ b u t t o n _ g e t _ a l i g n m e n t " 10

11 e x t e r n a l g e t _ e v e n t _ w i n d o w : 12 [ > B u t t o n T . t ] G o b j e c t . obj - > 13 [ < G I G d k . W i n d o w T . t ] G o b j e c t . obj

14 = " m l _ g i g t k _ g t k _ b u t t o n _ g e t _ e v e n t _ w i n d o w "

Listing 4.24: Estratto di metodi generati nel file di basso livello (GtkButton)

Alto livello

Nel file di alto livello i metodi devono essere generati dentro la classe _skel, la quale

ha come parametro di classe obj, l’oggetto su cui vengono invocati i metodi. Non tutti i

metodi hanno come parametro l’oggetto su cui operano, ma nel caso lo abbiano, questo `

e sempre in prima posizione. I metodi OCaml dentro la classe _skel devono quindi

richiamare il binding external generato nel file di basso livello passando come primo parametroobjnel caso in cui la il metodo lo richieda. Ci sono tuttavia delle complicazioni:

1. se il metodo riceve altri oggetti/interfacce in input in aggiunta all’oggetto su cui opera, il metodo OCaml non deve ricevere in input un tipo [> *] obj come indi-

Documenti correlati