C e Clipper 

Clipper è un linguaggio compilato, sintatticamente compatibile in larga misura con l'interprete del dBase III, orientato al database management. Sin dalle prime versioni, Clipper ha implementato gli strumenti necessari all'interfacciamento con il C per la realizzazione di funzioni non presenti nelle sue librerie standard (soprattutto per la gestione della macchina a basso livello). 

A tal fine è raccomandato l'utilizzo del Microsoft C (del resto le librerie Clipper sono scritte in Microsoft C); in realtà tutti i compilatori C in grado di generare, per il large memory model, moduli oggetto compatibili con l'architettura DOSSEG definita da Microsoft possono essere validamente impiegati in molti casi[1]. Il Microsoft C è indispensabile, per motivi di compatibilità, solo nel caso in cui si vogliano realizzare funzioni implementanti matematica in virgola mobile o routine grafiche[2]

Le funzioni scritte in C devono quindi essere compilate per il modello di memoria large[3] (vedere il capitolo dedicato) e, se si utilizza il compilatore Microsoft, deve essere richiesta l'opzione "Floating Point Alternate Library" (/FP); è inoltre molto comodo includere nel sorgente il file EXTEND.H, fornito con il pacchetto Clipper, che definisce alcune macro e costanti manifeste e contiene i prototipi di tutte le funzioni, facenti parte nella libreria Clipper, che consentono lo scambio di parametri e valori restituiti tra le funzioni Clipper e quelle C. Il modulo oggetto risultante dalla compilazione può essere collegato all'object file prodotto da Clipper, oppure può essere inserito in una libreria. 

Passaggio di parametri e restituzione di valori 

Nello scrivere funzioni C richiamabili da programmi Clipper va tenuto sempre presente che non ha senso, per esigenze di compatibilità tra i due linguaggi, parlare di parametri formali. Le funzioni C possono accedere a tutti i parametri attuali della chiamata Clipper invocando le funzioni _par...() (facenti parte della libreria Clipper e dichiarate in EXTEND.H) e devono pertanto essere dichiarate prive di parametri formali (void). Vi sono numerose funzioni _par...(), ciascuna dedicata ad un particolare tipo di parametro. 

Anche l'eventuale restituzione di un valore alla routine Clipper deve avvenire mediante le funzioni _ret...(), facenti parte della libreria Clipper e tra loro diversificate in base al tipo del valore da restituire. Le funzioni _ret...() non restituiscono il controllo alla routine Clipper, ma si limitano a predisporre la restituzione del valore; la funzione C cede il controllo solo al termine del proprio codice o eseguendo un'istruzione return). Ne segue che le funzioni C devono essere dichiarate void; inoltre le specifiche Clipper impongono che esse siano dichiarate anche pascal. Il file EXTEND.H definisce la macro CLIPPER, che può essere usata per dichiarare le funzioni C ed equivale proprio a void pascal

Il prototipo di una funzione C richiamabile da Clipper è perciò analogo a[4]
#include <EXTEND.H>
....
CLIPPER funzPerClipper(void);
Nell'ipotesi che funzPerClipper() accetti 4 parametri, la chiamata in Clipper è: 
RETVAL = FUNZPERCLIPPER(PAR_1,PAR_2,PAR_3,PAR_4)
Si tratta ora di analizzare brevemente le funzioni _par...() e _ret...(), per scoprire come funzPerClipper() può accedere a PAR_1, PAR_2, PAR_3 e PAR_4 e restituire RETVAL

La funzione 
int _parinfo(int order);
restituisce il tipo del parametro che occupa la posizione order nella lista dei parametri attuali; _parinfo(0) restituisce il numero di parametri passati alla funzione. Il tipo è identificato dalle seguenti costanti manifeste, definite in EXTEND.H
#define UNDEF     0
#define CHARACTER 1
#define NUMERIC   2
#define LOGICAL   4
#define DATE      8
#define MPTR     32 /* sommato al tipo effettivo se passato per reference */
#define MEMO     65
#define ARRAY   512
La costante MPTR ha un significato particolare: essa è sommata al tipo del parametro per indicare che esso è stato passato come reference (preceduto, secondo la sintassi Clipper, da una @): perciò, se _parinfo(1) restituisce 33, significa che il primo parametro attuale è una stringa passata per reference. 

La funzione 
int _parinfa(int order,int index);
restituisce il tipo dell'elemento in posizione index nell'array che a sua volta è parametro attuale di posizione order. Così, se _parinfa(2,5) restituisce 1, significa che il quinto elemento dell'array ricevuto come secondo parametro attuale è una stringa[5]. La chiamata 
_parinfa(order,0);
restituisce il numero di elementi dell'array che occupa la posizione order tra i parametri attuali. Il secondo parametro (index) può essere omesso: in tal caso _parinfa() equivale a _parinfo() [6]

Vediamo ora una rapida rassegna delle altre principali funzioni _par...(). Per i dettagli sintattici si rimanda alla documentazione del linguaggio Clipper; va detto, tuttavia, che nell'uso di tali funzioni il secondo parametro, index, può essere omesso: in particolare, esso deve essere utilizzato solo quando il parametro attuale la cui posizione è espressa dal primo parametro order sia un array. In tal caso, index individua uno specifico elemento all'interno dell'array medesimo. 

La funzione 
int _parni(int order,int index);
"recupera" il parametro attuale di posizione order (se esso è un array è possibile specificarne un singolo elemento con index; in caso contrario index può essere omesso) e lo restituisce sotto forma di intero: ad esempio, la chiamata 
#include <extend.h>
     ....
     int parm_1;
     ....
     parm_1 = _parni(1);
     ....
consente di accedere al primo parametro, un integer, passato dalla routine Clipper alla funzione C. Del tutto analoghe alla _parni() sono le funzioni 
long   _parnl(int order,int index);
double _parnd(int order,int index);
int    _parl(int order,int index);
char  *_pards(int order,int index);
char  *_parc(int order,int index);
Tutte, infatti, restituiscono il parametro attuale di posizione order (con la possibilità, se esso è un array, di individuarne un elemento mediante index). La _parnl() restituisce un long e la _parnd() un double, mentre la _parl() restituisce un intero che rappresenta un campo logico Clipper (0 equivale a .F. e 1 a .T.). 

La _pards() restituisce una stringa derivata da un campo Clipper di tipo date; la data è espressa nella forma "AAAAMMGG". E' opportuno copiare la stringa restituita in un buffer appositamente allocato, in quanto il buffer gestito dalla funzione _pards() è statico e viene sovrascritto ad ogni chiamata alla stessa. Ad esempio, nel codice 
#include <stdio.h>
#include <extend.h>
    ....
    char *date_1, date_2;
    ....
    date_1 = _pards(1);
    date_2 = _pards(2);
    printf("data 1 = %s\ndata 2 = %s\n",date_1,date_2);
la printf() visualizza due volte la data passata come secondo parametro, perché la seconda chiamata a _pards() sovrascrive il buffer statico che essa utilizza internamente. Per ottenere un funzionamento corretto è sufficiente apportare la modifica seguente: 
....
#include <string.h>
    ....
    strcpy(date_1,_pards(1));
    strcpy(date_2,_pards(2));
    ....
La funzione _parc() restituisce un puntatore a carattere. Va però ricordato che la gestione delle stringhe in Clipper è differente da quella implementata in C: in particolare, le stringhe possono contenere anche caratteri nulli (lo zero binario). Per conoscere la lunghezza effettiva di una stringa Clipper può essere utilizzata la funzione 
int _parclen(int order,int index);
che non include nel computo il NULL che chiude la stringa stessa, mentre la 
int _parcsiz(int order,int index);
restituisce il numero di byte effettivamente allocati per contenere la stringa (o, più in generale, l'array di caratteri), incluso l'eventuale NULL che la chiude. Se il parametro è una costante stringa, _parcsiz() restituisce 0

Passiamo ora ad un rapido esame delle funzioni _ret...(), che consentono alla funzione C di restituire un valore alla routine Clipper, con la precisazione che, essendo possibile restituire un unico valore, la funzione C può chiamare una sola volta una funzione _ret...() prima di terminare. 

E' il caso di citare per prima una funzione leggermente particolare: si tratta della 
void _ret(void);
che non restituisce a Clipper alcun valore (esattamente come se la funzione C terminasse con una semplice return), ma gestisce lo stack in modo tale che la routine Clipper possa chiamare la funzione C con un'istruzione DO (come se fosse, cioè, una procedure e non una function). 

Vi è poi una _ret...() per ogni tipo di dato, analogamente a quanto visto per le funzioni _par...()
void _retni(int ival);
void _retnl(long lval);
void _retnd(double dval);
void _retl(int bval);
void _retds(char *datestr);
void _retc(char *string);
La _retni() restituisce alla routine Clipper, sotto forma di campo numerico, l'intero che le è passato come parametro. Del tutto analoghe sono la _retnl() e la _retnd(), che devono essere utilizzate per restituire, rispettivamente, un long e un double

La _retl() restituisce a Clipper, sotto forma di campo logico, l'intero che riceve come parametro. Questo deve rappresentare un valore booleano, cioè deve essere 1 o 0, convertiti rispettivamente in .T. e .F.

La _retds() restituisce sotto forma di campo date la stringa passatale come parametro, che deve essere nella forma "AAAAMMGG"

La _retc() restituisce a Clipper il puntatore a carattere (stringa) che le è passato come parametro. Alla _retc() si affianca la 
void _retclen(char *buffer,int len);
che restituisce al Clipper, oltre all'indirizzo della stringa, anche la lunghezza della medesima. La _retclen() si rivela utile nei casi in cui il puntatore a carattere indirizza un buffer contenente anche byte nulli (zeri binari). 

Reference e puntatori 

Con Clipper Summer '87 non esiste modo per passare ad una funzione C l'indirizzo di una variabile Clipper (cioè il puntatore ad essa): ciò avviene, ma implicitamente, solo nel caso delle stringhe. La libreria Clipper 5 include invece alcune funzioni, le _stor...(), che consentono al codice C di accedere alle variabili ricevute per reference, cioè passate come parametri attuali anteponendo al nome il carattere @, in modo analogo a quello realizzabile (in C puro) mediante il passaggio di puntatori. La documentazione Clipper sottolinea che passare per reference una variabile ad una funzione C non significa comunque passarne l'indirizzo: in realtà la gestione del passaggio di parametri è, in Clipper, piuttosto complessa; tuttavia l'utilizzo dei reference comporta l'inserimento nello stack di informazioni relative all'indirizzo della variabile, ed è proprio grazie a tali informazioni che le nuove funzioni cui si è accennato consentono di "simulare" l'impiego di veri e propri puntatori. 

La funzione 
int _storni(int n,int order,int index);
memorizza l'intero n nella variabile Clipper passata via reference alla funzione C come parametro di posto order; il terzo parametro è facoltativo e deve essere utilizzato solo nel caso in cui il parametro ricevuto in posizione order sia un array: in tal caso index individua un preciso elemento al suo interno. La funzione restituisce 0 in caso di errore, 1 altrimenti. 

Analoghe alla _storni() sono le funzioni 
int _stornd(double n,int order,int index);
int _stornl(long n,int order,int index);
int _storl(int logical,int order,int index);
int _stords(char *string,int order,int index);
int _storc(char *string,int order,int index);
La _stornd() è utilizzabile per memorizzare un valore come double, mentre la _stornl() memorizza un long integer. La _storl() accetta come primo parametro un intero e lo memorizza come valore logico (0 equivale a .F.; un valore diverso da 0 equivale a .T.); la _stords() memorizza come data la stringa string, che deve avere formato "AAAAMMGG". La _storc() memorizza una stringa; in alternativa può essere utilizzata la 
int _storclen(char *buffer,int length,int order,int index);
che consente di memorizzare un array di caratteri indicandone la lunghezza[7]

Allocazione della memoria 

Le funzioni C destinate ad interfacciarsi con Clipper non possono gestire l'allocazione dinamica della memoria mediante malloc() e le altre  funzioni della libreriaC allo scopo predisposte: per esigenze di compatibilità è necessario utilizzare due funzioni che la libreria Clipper Summer '87 rende disponibili, ancora una volta, tramite i prototipi dichiarati in EXTEND.H. In particolare, la funzione 
unsigned char *_exmgrab(unsigned int size);
alloca in modo compatibile con Clipper un buffer ampio size byte e ne restituisce l'indirizzo sotto forma di puntatore a unsigned char [8]. In caso di errore viene restituito NULL. L'analogia con malloc() è evidente. La memoria allocata da _exmgrab() può essere gestita con le comuni tecniche C (puntatori, indirezioni, etc.); tuttavia essa deve essere disallocata con l'apposita funzione Clipper: 
void _exmback(unsigned char *pointer,unsigned int size);
che svolge un ruolo analogo a quello della funzione C free(); a differenza di questa, però, _exmback() richiede che, oltre all'indirizzo dell'area di memoria da disallocare, le sia passato anche il numero di byte da liberare. Se si intende disallocare l'intera area di RAM, il parametro size passato a _exmback() deve essere identico a quello omologo passato a _exmgrab(). Vediamo un semplice esempio: 
#include <extend.h>
....
    unsigned size = 1000;
    unsigned char *buffer;
    ....
    if(!(buffer = _exmgrab(size))) {
        ....   // gestione errore
    }
    ....   // utilizzo del buffer
    _exmback(buffer,size);
    ....
L'indirizzo di un buffer allocato da _exmgrab() può essere restituito a Clipper tramite la _retclen(); con riferimento all'esempio precedente la chimata potrebbe essere 
_retclen(buffer,size);
E' palese che il buffer non deve essere disallocato né prima né dopo la chiamata a _retclen()

La libreria del più recente Clipper 5 comprende invece 3 funzioni per l'allocazione dinamica della RAM, due delle quali sostituiscono quelle appena descritte. La 
void *_xalloc(unsigned int size);
rimpiazza la _exmgrab(). Anche la _xalloc() restituisce NULL in caso di errore. Alla _xalloc() si affianca una funzione di nuova concezione, la 
void *_xgrab(unsigned int size);
alloca anch'essa size byte nello heap di Clipper ma, a differenza della _xalloc() genera un run­time error in caso di errore. La _exmback() è stata sostituita dalla 
void _xfree(void *mem);
Come si vede, la _xfree() accetta un unico parametro, rappresentante l'indirizzo dell'area di memoria da liberare: non è più possibile, quindi, richiedere la disallocazione di una parte soltanto della memoria in precedenza allocata. 

Alcuni esempi 

Presentiamo di seguito alcuni esempi di funzioni C richiamabili da programmi Clipper. La prima permette di utilizzare alcuni servizi DOS da procedure Clipper. 
/********************

    BARNINGA_Z! - 1993

    CL_BDOS.C - cl_bdos()

    void pascal cl_bdos(void);

    Sintassi per Clipper:

    int cl_bdos(int dosfn,int dosdx,int dosal);
    int dosfn    numerico intero rappresentante il numero di servizio dell'int 21h
    int dosdx    numerico intero rappresentante il registro DX
    int dosal    numerico intero rappresentante il registro AL

    Restituisce: il valore restituito dall'int 21h nel regsitro AX

    COMPILABILE CON MICROSOFT C 5.1

        CL /c /AL /Oalt /FPa /Gs /Zl cl_bdos.c

********************/
#include <extend.h>
#include <dos.h>

#define  PNUM    3
#define  ERROR  -1

CLIPPER cl_bdos(void)
{
    register i;
    int fn, dx, al;

    if(_parinfo(0) != PNUM) {     // la proc. Clipper chiamante passa 3 param.
        _retni(ERROR);
        return;
    }
    for(i = 1; i <= PNUM; i++)
        if(_parinfo(i) != NUMERIC) {
            _retni(ERROR);
            return;
        }
    _retni(bdos(_parni(1),_parni(2),_parni(3)));
    return;
}
Ed ecco un esempio di chiamata alla cl_bdos() in una routine Clipper: 
DOS_FN = 12     && servizio 0Ch int 21h (vuota buffer tastiera e invoca altro servizio)
REG_AL = 7      && servizio da invocare in servizio 0Ch (7 = attende tasto)
RET_VAL = CL_BDOS(DOS_FN,0,REG_AL)      // chiama interrupt 21h
IF RET_VAL = -1
    @ 10,12 SAY "Errore!"
ELSE
    RET_VAL = RET_VAL % 256        // calcola AL
    ....   // utilizza il valore restituito (tasto premuto)
ENDIF
Il frammento di codice Clipper presentato utilizza la cl_bdos() per invocare l'int 21h, servizio 0Ch, con AL = 7: l'operazione che il servizio esegue consiste nel vuotare il buffer di tastiera e interrompere l'esecuzione del programma in attesa della pressione di un tasto. La cl_bdos() restituisce il valore di AX a sua volta restituito dall'int 21h: da questo viene poi "estratto" il valore di AL, cioè il codice ASCII del tasto premuto[9]. La formule per ricavare il valore di un registro a 16 bit a partire dai due sottoregistri a 8 bit che lo compongono è molto semplice: 
RX = (RH * 256) + RL
dove RX indica un generico registro a 16 bit, mentre RH e RL rappresentano, rispettivamente, il sottoregistro "alto" (gli 8 bit più significativi) e quello "basso" (gli 8 bit meno significativi). Inoltre sono valide le seguenti: 
RH = RX / 256
RL = RX % 256
Si può cioè affermare che gli 8 bit più significativi sono ottenibili dividendo il valore a 16 bit per 256, senza considerare resto o decimali, mentre gli 8 bit meno significativi sono il resto della precedente divisione. 

Ed ora il secondo esempio. E' noto che in ogni programma C main() può essere dichiarata con alcuni parametri formali: il secondo di questi, solitamente chiamato argv, è un array di puntatori a stringa (o, meglio, a carattere: char **argv), la prima delle quali (argv[0]) rappresenta il nome del programma eseguibile, completo di pathname. In Clipper è possibile accedere ai parametri della command line[10], ma non si ha modo di conoscere, in modo quasi "automatico", come nei programmi C, nome e pathname del programma stesso. L'ostacolo può essere aggirato con una funzione C, il cui modulo oggetto deve essere collegato all'object file prodotto dal compilatore Clipper. 
/********************

    BARNINGA_Z! - 1991

    CL_EXENM.C - cl_exename()

    void pascal cl_exename(void);

    char *cl_exename();
    Restituisce:  il puntatore ad una stringa che rappresenta il nome del
                  programma eseguibile completo di pathname

    COMPILABILE CON BORLAND C++ 3.1

        tcc -O -d -c -ml cl_exenm.c

********************/
#include <extend.h>
#include <dir.h>
#include <dos.h>

CLIPPER cl_exename(void)
{
    register i;
    unsigned PSPseg;
    char *ENVptr;
    static char exeName[MAXPATH];

    _AH = 0x62;
    geninterrupt(0x21);
    PSPseg = _BX;
    ENVptr = MK_FP(*(unsigned far *)MK_FP(PSPseg,0x2C),0);
    for(;;) {
        if(!*ENVptr++)
            if(!*ENVptr)
                break;
    }
    for(; *ENVptr != 1;)
        ENVptr++;
    for(ENVptr += 2; *ENVptr; )
        exeName[i++] = *ENVptr++;
    exeName[i] = 0;
    _retc(exeName);
}
La cl_exename() utilizza il servizio 62h dell'int 21h per conoscere l'indirizzo di segmento del Program Segment Prefix del programma e lo utilizza per costruire un puntatore all'environment, cioè alla prima delle variabili d'ambiente. Infatti, la word ad offset 2Ch nel PSP rappresenta l'indirizzo di segmento dell'area allocata dal DOS all'environment. L'espressione 
*(unsigned far *)MK_FP(PSPseg,0x2C)
restituisce detta word, pertanto l'espressione 
MK_FP(*(unsigned far *)MK_FP(PSPseg,0x2C),0)
restituisce il puntatore (far) alla prima stringa dell'environment. Le stringhe rappresentanti variabili d'ambiente sono memorizzate l'una di seguito all'altra; ogni stringa è terminata da un byte nullo, che in questo caso funge anche da "separatore". L'ultima stringa nell'environment è conclusa da due byte nulli: la sequenza 00h 00h, che indica anche la fine dell'environment, è ricercata dal primo ciclo for. Il secondo ciclo cerca il byte 01h, che segnala la presenza della stringa contenente nome e path dell'eseguibile: il byte 01h è seguito da un byte nullo, dopo il quale inizia la stringa; la clausola di inizializzazione 
ENVptr += 2;
del terzo ciclo for "scavalca" la sequenza 01h 00h; l'istruzione che costituisce il corpo del ciclo stesso può così copiare la stringa, un byte ad ogni iterazione, nel buffer statico exeName. La scelta dell'allocazione statica evita il ricorso a _exmgrab(); del resto non sarebbe possibile dichiarare il buffer come semplice variabile automatica in quanto l'area di memoria da esso occupata verrebbe rilasciata in uscita dalla funzione. 

La stringa è esplicitamente terminata da un byte nullo e il suo indirizzo è restituito a Clipper mediante _retc()

La chiamata a cl_exename() in Clipper può essere effettuata come segue: 
EXEPATH = ""
....
EXEPATH = CL_EXENAME()
@ 10,12 say "Questo programma è " + EXEPATH
....
Qualora il programma Clipper deallochi il proprio environment (eventualmente utilizzando una funzione scritta in C), è indispensabile che la chiamata a cl_exename() avvenga prima di detta operazione. 

Va ancora sottolineato che il sorgente di cl_exename() può essere compilato con il compilatore Borland: d'altra parte non referenzia alcuna funzione di libreria C. 

Vediamo un ultimo esempio: una funzione in grado di suddividere un numero in virgola mobile in parte intera e parte frazionaria, basata sulla funzione di libreria C modf()
/********************

    BARNINGA_Z! - 1994

    CL_MODF.C - cl_modf()

    void pascal cl_modf(void);

    Sintassi per Clipper:

    double cl_modf(double n,double @ipart);
    double n       numerico in virgola mobile che si vuole suddividere in parte
                   intera e parte frazionaria
    double @ipart  reference a numerico in virgola mobile destinato a contenere
                   la parte intera

    Restituisce: la parte frazionaria di n

    COMPILABILE CON MICROSOFT C 5.1

        CL /c /AL /Oalt /FPa /Gs /Zl cl_modf.c

********************/
#include <extend.h>
#include <math.h>

#define  PNUM    2
#define  ERROR   0.0

CLIPPER cl_modf(void)
{
    double ip, fp;

    if(PCOUNT != PNUM) {  // la proc. Clipper chiamante passa 2 param.
        _retnd(ERROR);
        return;
    }
    if(! ISNUM(1)) {
        _retnd(ERROR);
        return;
    }
    if(!(ISNUM(2) && ISBYREF(2))) {
        _retnd(ERROR);
        return;
    }
    fp = modf(_parnd(1),&ip);
    if(!_storni(ip,2)) {
        _retnd(ERROR);
        return;
    }
    _retnd(fp);
    return;
}
Segue esempio di chiamata alla cl_modf() in una routine Clipper: 
DOUBLE_NUM = 12.5647
INT_PART = 0.0
FRAC_PART = CL_MODF(DOUBLE_NUM,@INT_PART)
@ 10,12 SAY "PARTE INTERA: "+STR(INT_PART)+"   PARTE FRAZIONARIA: "+STR(FRAC_PART)
Il programma Clipper visualizza il numero 12 come parte intera e il numero 0.5647 come parte frazionaria. La restituzione di due valori (parte intera e parte frazionaria di DOUBLE_NUM) alla routine chiamante è resa possibile dal passaggio per reference della variabile INT_PART alla CL_MODF(): questa, mediante la _stornd() è in grado di memorizzare all'indirizzo della INT_PART, cioè nella INT_PART stessa, il valore che, a sua volta, la modf() ha scritto all'indirizzo di ip, cioè nella ip medesima. 

E' evidente che l'utilizzo delle macro PCOUNT, ISNUM() e ISBYREF(), nonché della funzione _storni(), rende la CL_MODF() utilizzabile esclusivamente da programmi compilati da Clipper 5. 
OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...