Per comprendere al meglio l'utilità di ext_skel per la produzione di estensioni, procederemo con l'implementazione di due semplici funzioni. Una volta compreso il meccanismo utilizzato per creare, completare e compilare i sorgenti prodotti da ext_skel per un'estensione di piccole dimensioni, fare il passo verso strutture più complesse non risulterà affatto difficile.
Per prima cosa creiamo un file di testo contenente i prototipi delle nostre funzioni chiamandolo prototipi.txt:
int countuniquechars(string input [,bool case_sensitive])
Restituisce il numero di caratteri univoci nella stringa
string invertcase(string input)
Inverte maiuscole e minuscole
Queste due funzioni non saranno di molta utilità nello sviluppo di applicazioni complesse, ma ci aiuteranno a comprendere il meccanismo di creazione delle estensioni. Con l'aiuto di ext_skel, generiamo i file necessari:
cd /percorso/sorgenti/php/ext/
./ext_skel -extname=html_it_ext -proto=prototipi.txt
Se tutto va per il verso giusto, il programma verrà eseguito correttamente, e verranno stampate a video le istruzioni necessarie per procedere con la compilazione dell'estensione. I comandi generati possono essere seguiti alla lettera, senza alcun problema. Il primo comando permette di modificare il file di config.m4; questo file è utilizzato per modificare automaticamente il file configure di PHP. Se non dovesse essere apportata nessuna modifica a questo file, verrebbe utilizzata la configurazione di default. Il file prodotto è abbastanza lungo, e per motivi di spazio non viene mostrato. È necessario modificare il file rimuovendo i commenti (eliminando la parola dnl che precede la riga) delle righe seguenti in modo che includa automaticamente l'estensione che andremo a compilare:
dnl PHP_ARG_ENABLE(html_it_ext, wheter to enable html_it_ext support,
...
dnl [ --enable-html_it_ext Enable html_it_ext support])
Una volta applicate le modifiche ed eseguito il backup del file configure di PHP, eseguiamo in sequenza i seguenti comandi:
./buildconf -force
./configure -enable-html_it_ext
make
./sapi/cli/php -f ext/html_it_ext/html_it_ext.php
L'estensione è stata creata e compilata correttamente, ma se cercassimo di eseguire le funzioni in essa definite, verrebbe visualizzato un'avvertenza (warning) che ci informa che l'implementazione della funzione non è stata ancora effettuata. Apriamo quindi il file html_it_ext.c e procediamo con un'analisi superficiale della struttura e con l'implementazione delle funzioni.
Se cerchiamo all'interno dei sorgenti, dovremmo trovare la definizione della funzione countuniquechars:
PHP_FUNCTION(countuniquechars)
{
int argc = ZEND_NUM_ARGS();
int input_len;
char* input = NULL;
zend_bool case_sensitive;
if(zend_parse_parameters(argc TSRMLS_CC, "s|b", &input, &input_len, &case_sensitive) == FAILURE)
{
return;
}
php_error(E_WARNING, "countuniquechars: not yet implemented");
}
Come potete notare la struttura base di una funzione PHP è molto semplice. Tralasciando le righe relative alla definizione delle variabili utilizzate, abbiamo una funzione che si occupa di fare il parsing degli argomenti passati e di salvarli nello scope corrente, ed una funzione che restituisce un warning. Procediamo con l'implementazione della funzione:
PHP_FUNCTION(countuniquechars)
{
int argc = ZEND_NUM_ARGS();
int input_len;
char* input = NULL;
zend_bool case_sensitive;
int index;
char* h_buffer;
char c;
long h_count;
if(zend_parse_parameters(argc TSRMLS_CC, "s|b", &input, &input_len, &case_sensitive) == FAILURE)
{
return;
}
if(argc == 1)
{
case_sensitive = 1;
}
h_count = 0;
h_buffer = (char*)emalloc(input_len + 1);
memset(h_buffer, 0, input_len + 1);
for(index = 0; index < input_len; ++index)
{
c = case_sensitive ? input[index] : tolower(input[index]);
if(!strchr(h_buffer, c))
{
h_buffer[h_count] = c;
++h_count;
}
}
efree(h_buffer);
RETURN_LONG(h_count);
}
L'implementazione del metodo è molto semplice: prendiamo ogni carattere della stringa in input, lo convertiamo in minuscolo se necessario, e controlliamo che questo carattere non sia presente all'interno di un buffer. In caso affermativo aggiungiamo il carattere al buffer ed incrementiamo un contatore. Il valore del contatore viene infine restituito come risultato. È necessaria una precisazione sulle funziona utilizzate per l'allocazione della memoria: nelle estensioni PHP vengono utilizzate delle implementazioni alternative alle funzioni per la gestione della memoria. Queste implementazioni hanno lo stesso nome delle versioni standard, preceduto da una 'e'. Tramite PHP è possibile utilizzare emalloc, efree, estrdup ecalloc.
Procedendo con la seconda funzione, l'implementazione è subito fatta:
PHP_FUNCTION(invertcase)
{
int argc = ZEND_NUM_ARGS();
int input_len;
char* input = NULL;
int index;
char* h_buffer;
if(zend_parse_parameters(argc TSRMLS_CC, "s", &input, &input_len) == FAILURE)
{
return;
}
h_buffer = (char*)emalloc(input_len + 1);
memset(h_buffer, 0, input_len + 1);
for(index = 0; index < input_len; ++index)
{
h_buffer[index] = ('a' <= c && c <= 'z') ? toupper(input[index]) : tolower(input[index]);
}
RETVAL_STRING(h_buffer, 1);
efree(h_buffer);
}
Come possiamo notare, la struttura rimane pressoché identica. Varia solamente la logica applicativa ed il sistema utilizzato per restituire un valore. In questo caso è necessario restituire una copia della stringa creata, quindi utilizzeremo la macro RETVAL_STRING con i parametri appropriati.
Riprendiamo nuovamente i passi di configurazione e compilazione, e procediamo con il testing delle funzioni implementate. Otterremo i risultati sperati.