Assembly Base con MASM

Capitolo 31: Interfaccia tra Pascal e Assembly


In questo capitolo vengono descritte le convenzioni che permettono di interfacciare il linguaggio Pascal con il linguaggio Assembly; si presuppone che chi legge abbia una adeguata conoscenza della programmazione in Pascal.

Il linguaggio Pascal è stato creato nel 1968 dal Prof. Niklaus Wirth alla Eidgenossische Technische Hochschule di Zurigo; l'obiettivo di Wirth era quello di creare un linguaggio di programmazione di uso generale (general purpose) finalizzato esplicitamente all'insegnamento dell'arte della programmazione ai principianti.
La maggiore preoccupazione di Wirth fu quella di eliminare tutte le terrificanti caratteristiche che resero tristemente famoso il BASIC (nato anch'esso come linguaggio didattico); tra gli aspetti più negativi delle prime versioni del BASIC si possono ricordare lo scarso risalto dato alla tipizzazione dei dati e l'impossibilità di poter creare programmi decentemente strutturati.
Il risultato ottenuto da Wirth fu un linguaggio di programmazione che ancora oggi può essere definito come uno dei più semplici ed eleganti mai realizzati; in particolare, il Pascal presenta una robustissima tipizzazione dei dati e una sintassi che favorisce uno sviluppo fortemente strutturato dei programmi.
Queste caratteristiche fecero subito conquistare al Pascal il ruolo di linguaggio di programmazione più usato nelle scuole e, in particolare, nelle Università di tutto il mondo; inizialmente, il Pascal ottenne anche un notevole successo tra i programmatori professionisti.

A differenza di quanto accade per il C, non esiste alcuno standard di linguaggio per il Pascal; proprio per questo motivo è possibile trovare in circolazione diverse varianti di questo linguaggio come, ad esempio, l'IBM Pascal, il Microsoft Quick Pascal, il Borland Turbo Pascal, etc.
Molto spesso tali varianti del Pascal possono differire tra loro per alcuni dettagli sintattici, per la presenza di estensioni del linguaggio e per il procedimento che porta alla generazione dei programmi eseguibili; in ogni caso, come molti sanno, il Borland Turbo Pascal può essere definito il vero responsabile dell'enorme successo ottenuto da questo linguaggio di programmazione, tanto da meritarsi il titolo di standard "virtuale" di riferimento del linguaggio Pascal.

Con il passare degli anni, il Pascal ha subito un lento declino dovuto, in particolare, all'avvento del linguaggio C; sul finire degli anni 80 il C ha iniziato a prendere il sopravvento sul Pascal, sia negli ambienti universitari, sia soprattutto tra gli sviluppatori professionisti. Ciò è accaduto in quanto, come è stato spiegato nel precedente capitolo, le rigidissime regole sintattiche del Pascal possono andare bene per i principianti, ma finiscono per creare notevoli problemi a quegli sviluppatori professionisti che spesso ricorrono a tecniche di programmazione piuttosto smaliziate.

La situazione comunque è in continua evoluzione e il Pascal conta ancora numerosi appassionati in tutto il mondo, tanto che è possibile trovare compilatori Pascal destinati ai più diffusi SO; inoltre, bisogna anche osservare che le limitazioni del Pascal possono essere facilmente aggirate interfacciando tale linguaggio con l'Assembly.

Tutte le considerazioni esposte in questo capitolo si riferiscono al compilatore Borland Turbo Pascal versione 7.0, scaricabile dalla sezione Compilatori assembly, c++ e altri dell’ Area Downloads di questo sito; tale compilatore permette lo sviluppo di programmi Pascal destinati all'ambiente operativo a 16 bit del DOS.
Come è stato spiegato in precedenza, il Turbo Pascal viene considerato, di fatto, lo standard di riferimento per questo linguaggio di programmazione; l'implementazione del Turbo Pascal, pur presentando numerose innovazioni, rispetta quasi interamente la struttura sintattica originale del linguaggio, così come è stata definita dal suo creatore Niklaus Wirth.

31.1 Tipi di dati del Pascal

Il Pascal attribuisce una notevole importanza alla tipizzazione dei dati e obbliga il programmatore a tenere ben distinti quei dati che non appartengono allo stesso insieme numerico o alla stessa categoria; i tipi di dati interi forniti dal Pascal sono elencati in Figura 31.1. Questi tipi di dati interi vengono chiamati ordinali in quanto tra due qualsiasi numeri interi può essere stabilita una relazione d'ordine (maggiore, minore, uguale); i tipi ordinali comprendono anche il tipo Boolean che può assumere uno tra i due valori costanti False (cioè, 0) e True (cioè, 1). I tipi Enumerated e Subrange appartengono anch'essi alla categoria dei tipi ordinali.

Anche il Turbo Pascal supporta i tre tipi fondamentali di dati in floating point espressi nel formato standard IEEE; tali tre tipi sono il single a 32 bit, il double a 64 bit e l'extended a 80 bit. A questi tre tipi si aggiungono anche il tipo real a 48 bit e il tipo comp a 64 bit; le caratteristiche di questi tipi di dati sono elencate in Figura 31.2. Come al solito, gli estremi Min. e Max. si riferiscono ai numeri reali positivi; per ottenere gli estremi negativi basta mettere il segno meno davanti agli estremi positivi.
Il tipo comp è un tipo intero con segno a 64 bit che può essere gestito solo attraverso la FPU o attraverso il software di emulazione della FPU.

Il Pascal dispone anche del tipo stringa che, come già sappiamo, permette di definire vettori di BYTE dove ogni BYTE contiene un codice ASCII; in un precedente capitolo abbiamo anche visto che il Pascal utilizza il BYTE di indice 0 della stringa per contenere la lunghezza della stringa stessa. Possiamo affermare quindi che la lunghezza massima di una stringa Pascal è pari al valore massimo rappresentabile con 8 bit e cioè, 255 caratteri.

Se scriviamo:
const str1[4] = 'ciao';
il compilatore assegna a str1 un vettore di 5 BYTE formato da un BYTE di valore 4 (lunghezza della stringa) e da 4 BYTE contenenti i codici ASCII dei 4 caratteri della stringa; se, invece, scriviamo:
const str1 = 'ciao';
il compilatore assegna a str1 un vettore di 256 BYTE formato da un BYTE di valore 4 (lunghezza della stringa), da 4 BYTE contenenti i codici ASCII dei 4 caratteri della stringa e da altri 251 BYTE vuoti (i quali, in genere, contengono il valore 0).

Il programma mostrato in Figura 31.3 permette di verificare in pratica le caratteristiche dei vari tipi di dati del Pascal. La funzione SizeOf restituisce la dimensione in byte del suo argomento; la funzione Ord restituisce il valore numerico (ordinale) del suo argomento (il quale deve appartenere ai tipi ordinali).

31.2 Convenzioni per i compilatori Pascal

Nel Capitolo 29 sono state illustrate le convenzioni seguite dai compilatori nella gestione della struttura interna dei programmi; analizziamo ora tali convenzioni in riferimento ai compilatori Pascal.

31.2.1 Segmenti di programma

I compilatori Pascal capaci di generare il codice oggetto (come l'IBM Pascal), seguono per i segmenti di programma le stesse convenzioni illustrate nei precedenti capitoli; la possibilità di avere a disposizione il codice oggetto di un modulo Pascal facilita notevolmente il lavoro di interfacciamento con l'Assembly.
Il compilatore Turbo Pascal, invece, produce un codice intermedio chiamato P-Code, che viene tenuto nascosto al programmatore; questa situazione rende più difficoltose le comunicazioni tra moduli Turbo Pascal e moduli Assembly.
Si tenga presente, inoltre, che il Turbo Pascal utilizza una segmentazione la quale segue solo in parte le convenzioni che già conosciamo; per rendersene conto basta richiedere al compilatore la generazione del map file del programma.
A tale proposito, bisogna selezionare il menu
Options - Linker - Map File
Attraverso il map file si può constatare quanto segue:

Esiste un unico blocco per i dati statici comprendente quindi i dati inizializzati, non inizializzati e costanti; tale blocco presenta le seguenti caratteristiche: Esiste un unico blocco stack che naturalmente viene predisposto dal compilatore e presenta le seguenti caratteristiche: Ciascun modulo appartenente ad un programma Pascal utilizza un proprio segmento di codice; i vari segmenti di codice differiscono tra loro solo per il nome.
Il segmento di codice relativo al modulo principale di un programma Pascal contiene l'entry point e quindi ricopre il ruolo di segmento principale di codice; il nome utilizzato da questo segmento è legato alla presenza o meno della parola riservata Program all'inizio del modulo principale.
Se non utilizziamo tale parola riservata, il blocco di codice principale assume le seguenti caratteristiche: Se, come nell'esempio DATATYPE.PAS di Figura 31.3, il programma inizia con:
Program DataTypes;
allora il blocco di codice principale assume le seguenti caratteristiche: Ogni unit del Turbo Pascal utilizza un segmento di codice che ha il nome della unit stessa; nel caso, ad esempio, della unit Crt della libreria Pascal, viene utilizzato il seguente segmento di codice: Da queste considerazioni emerge chiaramente il fatto che un programma Turbo Pascal è dotato di un unico segmento di dati, un unico segmento di stack e due o più segmenti di codice; le procedure presenti nelle unit si trovano in segmenti di codice diversi da quello in cui si trova il caller e quindi possono essere chiamate solo attraverso FAR call.
Nel caso dei moduli Pascal tutti questi aspetti vengono gestiti dal compilatore; nel caso dei moduli Assembly, invece, tutto è nelle mani del programmatore.
In particolare, il programmatore deve indicare chiaramente al compilatore Pascal il tipo NEAR o FAR delle procedure definite nei moduli Assembly; il procedimento da seguire viene illustrato nel seguito del capitolo.

Il compilatore Pascal, subito dopo l'entry point, genera tutto il codice necessario affinché, al momento di caricare il programma in memoria, il SO ponga:
SS = STACK
Lo stesso SO inizializza il registro CS con il segmento principale di codice che contiene, ovviamente, l'entry point; nel caso del precedente esempio DATATYPE.PAS, si ha:
CS = DataTypes
Da parte sua, il compilatore genera il codice macchina che pone automaticamente:
DS = DATA
Esistendo un unico blocco di dati, non viene creato alcun gruppo DGROUP.

31.2.2 Stack frame

Il Pascal mette a disposizione due tipi di procedure definibili attraverso le parole riservate procedure e function; a differenza di quanto accade con una procedure, la function ha sempre un valore di ritorno.
Nel seguito del capitolo viene utilizzato il termine procedura per indicare genericamente, sia una procedure, sia una function.

La convenzione Pascal prevede che gli eventuali argomenti da passare ad una procedura vengano inseriti nello stack a partire dal primo (sinistra destra); all'interno dello stack gli argomenti di una procedura vengono quindi posizionati in ordine inverso rispetto a quello indicato dalla intestazione della procedura stessa.
Il compito di ripulire lo stack dagli argomenti spetta, come sappiamo, alla procedura chiamata; questo lavoro viene generalmente effettuato attraverso l'istruzione:
RET n
dove n indica il numero di byte da sommare a SP.

Come al solito, l'accesso ai parametri di una procedura avviene attraverso BP; questa volta, però, bisogna ricordarsi che i parametri sono posizionati in ordine inverso nello stack e quindi, muovendoci in avanti rispetto a BP, incontreremo per primo l'ultimo parametro ricevuto dalla procedura.
Nel caso di NEAR call l'ultimo parametro si trova a BP+4; nel caso, invece, di FAR call l'ultimo parametro si trova a BP+6.

31.2.3 Valori di ritorno

In relazione, infine, alle locazioni utilizzate per contenere gli eventuali valori di ritorno delle funzioni, valgono tutte le convenzioni illustrate nella Figura 25.10 del Capitolo 25; bisogna prestare attenzione al fatto che il tipo comp, pur appartenendo agli interi, viene gestito attraverso la FPU e quindi viene restituito nel registro ST0.

31.3 Le classi di memoria del Pascal

In relazione alle classi di memoria bisogna premettere che nel caso più generale, un programma Pascal è formato da un modulo principale Pascal, da una o più unit Pascal e da uno o più moduli Assembly; si tenga presente che nel caso del Turbo Pascal, la unit System contenente le procedure di sistema (come New, Dispose, Write, Sin, Cos, etc), viene incorporata automaticamente nel modulo principale e in tutte le eventuali altre unit del programma.
Osservando, infatti, il map file di un qualunque programma Turbo Pascal, si nota sempre la presenza di un blocco codice: Il Pascal permette di creare procedure innestate all'interno di altre procedure; una procedura A innestata in una procedura B è visibile solamente in B e quindi non può essere chiamata da altre procedure.
In sostanza, i compilatori Pascal permettono di trattare le procedure innestate come se fossero delle variabili locali create nello stack; è ovvio però che in realtà, una qualunque procedura innestata o non innestata, viene sempre creata in un segmento di codice del programma.

Tutte le procedure non innestate presenti nel modulo principale Pascal hanno automaticamente linkaggio esterno; queste procedure quindi sono visibili anche negli eventuali moduli Assembly facenti parte del programma.

In relazione alle unit il discorso è analogo al caso delle procedure static e non static del C; tutte le procedure dichiarate nella sezione interface hanno automaticamente linkaggio esterno, mentre tutte le procedure dichiarate solo nella sezione implementation hanno automaticamente linkaggio interno.
Questa distinzione tra procedure pubbliche e private di una unit viene gestita dal compilatore e quindi vale solamente per i moduli Pascal che fanno uso della unit stessa.
Un modulo Assembly, invece, è anche in grado di vedere tutte le procedure non innestate presenti nella sezione implementation di una unit; se vogliamo attenerci alle regole di visibilità del Pascal, nei moduli Assembly possiamo evitare di dichiarare EXTRN le procedure private di una unit.

Come è stato spiegato in precedenza, l'eccezione è rappresentata dalla unit System; questa unit speciale viene automaticamente collegata al modulo principale e a tutte le altre unit attraverso un procedimento che viene nascosto al programmatore (si può anche constatare che non esiste alcun file SYSTEM.TPU).
La conseguenza è che le procedure della unit System non risultano visibili nei moduli Assembly; nei moduli Pascal, inoltre, è proibito passare queste procedure come argomenti di altre procedure.

Gli identificatori delle procedure innestate presenti in un qualunque modulo Pascal hanno linkaggio interno e non risultano visibili negli altri moduli, compresi i moduli Assembly.

Le etichette dichiarate con label in una procedura sono visibili solo all'interno della procedura stessa e il loro identificatore può essere quindi ridefinito in altri punti del programma; le etichette dichiarate con label al di fuori di qualsiasi procedura sono visibili in tutto il modulo di appartenenza e il loro identificatore può essere quindi ridefinito solo in altri moduli.

Per quanto riguarda le variabili globali dei moduli Pascal e cioè, le variabili definite al di fuori di qualsiasi procedura, valgono le stesse regole esposte per le procedure; tutte le variabili definite al di fuori di qualsiasi procedura del modulo principale Pascal hanno automaticamente linkaggio esterno e risultano quindi visibili anche in eventuali moduli Assembly facenti parte del programma.
In relazione alle unit, la distinzione tra variabili globali pubbliche e private viene gestita dal compilatore e quindi vale solamente per i moduli Pascal che fanno uso della unit stessa; in pratica, un modulo Pascal che fa uso di una unit è in grado di vedere solamente le variabili globali presenti nella sezione interface della unit stessa.
Un modulo Assembly, invece, è in grado di vedere tutte le variabili globali presenti, sia nella sezione interface, sia nella sezione implementation di una unit; anche in questo caso, se vogliamo attenerci alle regole di visibilità del Pascal, nei moduli Assembly possiamo evitare di dichiarare EXTRN le variabili globali private di una unit.

I moduli Assembly non sono, invece, in grado di vedere le costanti prive di tipo dichiarate in un modulo Pascal come, ad esempio:
const ALTEZZA = 10;
Come è stato spiegato in precedenza, tutte le variabili globali di un programma Pascal (pubbliche o private), vengono sistemate in un unico segmento dati definito come: Il compilatore assegna automaticamente il valore 0 a tutte le variabili globali non inizializzate; analogamente, le stringhe non inizializzate vengono riempite con zeri.

Tutte le variabili definite in una procedura Pascal sono considerate variabili locali; le variabili locali vengono create nello stack e risultano visibili solo all'interno della procedura di appartenenza.
Le variabili locali possono essere inizializzate, sia con valori costanti, sia con il contenuto di altre variabili; come al solito, in assenza di inizializzazione il contenuto di una variabile locale è casuale.

31.4 Gestione degli indirizzamenti in Pascal

Anche il Pascal supporta i puntatori che, come sappiamo, sono delle variabili intere senza segno il cui contenuto rappresenta un indirizzo di memoria; il problema che si presenta è dato dal fatto che il Pascal è un linguaggio di programmazione destinato ai principianti, per cui tende a nascondere tutti i dettagli relativi alla gestione a basso livello di un programma.
La conseguenza pratica è che in Pascal la gestione dei puntatori è piuttosto contorta e tende a creare parecchi problemi ai programmatori provenienti dal C o dall'Assembly; il modo migliore per aggirare questi problemi consiste naturalmente nel ricorrere, in caso di necessità, all'interfacciamento con l'Assembly o all'uso dell'Assembly inline supportato in modo molto efficiente dal Turbo Pascal.

Il primo aspetto da osservare riguarda il fatto che nel Turbo Pascal i puntatori sono sempre di tipo FAR e rappresentano quindi coppie Seg:Offset da 16+16 bit; come al solito, in base alla convenzione Intel la componente Seg deve sempre trovarsi nei 16 bit più significativi del puntatore.
Tutti gli aspetti relativi ai puntatori del Pascal coincidono con quanto esposto nel precedente capitolo a proposito della gestione dei puntatori in C; per maggiore chiarezza, consideriamo il seguente blocco di variabili globali di un modulo principale Pascal: Quando un compilatore Pascal incontra questo blocco dati: All'interno del blocco di codice principale e cioè, all'interno del blocco delimitato da begin e end, procediamo alla inizializzazione dei puntatori; in presenza dell'istruzione:
ptSho^:= c1;
il compilatore carica in ptSho l'indirizzo Seg:Offset di c1.

In presenza dell'istruzione:
ptInt^:= i1;
il compilatore carica in ptInt l'indirizzo Seg:Offset di i1.

In presenza dell'istruzione:
ptLon^:= l1;
il compilatore carica in ptLon l'indirizzo Seg:Offset di l1.

In presenza dell'istruzione:
ptStr^:= s1;
il compilatore carica in ptStr l'indirizzo Seg:Offset del primo elemento di s1.

Come si può notare, la sintassi utilizzata dal Pascal con i puntatori lascia molto a desiderare e tende spesso a creare una certa confusione; molti programmatori provenienti dal C, ad esempio, seguendo la logica sono portati a scrivere:
ptInt:= ^i1;
Nel tentativo di rendere meno confusa la gestione dei puntatori, il Turbo Pascal ha introdotto un nuovo operatore rappresentato dal simbolo @ (chiocciola); questo operatore equivale al simbolo & del C e deve essere letto come "indirizzo di ...".
Impiegando questo nuovo operatore possiamo scrivere assegnamenti del tipo:
ptInt:= @i1;
Come si può notare, questa nuova sintassi appare molto più chiara e logica di quella utilizzata dal Pascal classico; la precedente istruzione, infatti, indica chiaramente che stiamo assegnando a ptInt l'indirizzo di i1.

Il Turbo Pascal mette a disposizione anche i puntatori generici rappresentati dal tipo Pointer; tali puntatori equivalgono ai puntatori a void del C.
Un puntatore di tipo Pointer può puntare a qualsiasi variabile di qualsiasi tipo; nella sezione var del blocco dati illustrato in precedenza, possiamo scrivere, ad esempio:
ptVoid: Pointer;
A questo punto, nel blocco di codice principale possiamo scrivere:
ptVoid:= @c1;
In qualsiasi altro punto del programma possiamo anche far puntare ptVoid altrove; possiamo scrivere, ad esempio:
ptVoid:= @ptStr;
Come accade per i puntatori a void del C, anche i Pointer del Turbo Pascal non possono essere dereferenziati; il loro scopo è solamente quello di memorizzare un indirizzo che spesso viene poi passato ad una procedura Assembly la quale può gestirlo senza avere tra i piedi lo stretto controllo di tipo dei compilatori Pascal.

Il Turbo Pascal mette a disposizione anche altre procedure di basso livello per i puntatori; in particolare, possono tornale utili le function come Seg, Ofs, Ptr e Addr.

Seg richiede un solo argomento che deve essere l'identificatore pubblico di una variabile, di una function o di una procedure; il valore restituito da Seg è una Word contenente la componente Seg dell'indirizzo dell'argomento.

Ofs richiede un solo argomento che deve essere l'identificatore pubblico di una variabile, di una function o di una procedure; il valore restituito da Ofs è una Word contenente la componente Offset dell'indirizzo dell'argomento.

Addr richiede un solo argomento che deve essere l'identificatore pubblico di una variabile, di una function o di una procedure; il valore restituito da Addr è un Pointer contenente la coppia Seg:Offset dell'indirizzo dell'argomento.

Ptr richiede due argomenti di tipo Word che devono rappresentare una coppia Seg:Offset; il valore restituito da Ptr è un Pointer contenente la coppia Seg:Offset costituita dalle due Word passate come argomenti.

Tutte queste estensioni del Pascal sono state introdotte con l'intento di rendere il linguaggio più potente e più flessibile; molto spesso, però, si ottiene come risultato un notevole aumento della confusione.
Consideriamo, ad esempio, la seguente procedura Pascal che si serve del blocco dati visto in precedenza: Questa procedura richiede un Integer passato per indirizzo; se vogliamo passare a questa procedura la variabile i1 per indirizzo, dobbiamo scrivere:
WriteInteger(i1);
Il Pascal, come al solito, tende a nascondere tutti i dettagli relativi alla gestione a basso livello del programma; la variabile i1 ci appare quindi come se venisse passata per valore.
Un programmatore abituato a lavorare con il C, dopo aver fatto puntare ptInt a i1 cercherebbe di scrivere:
WriteInteger(ptInt);
Il compilatore genera, però, un messaggio di errore per indicare che WriteInteger richiede un Integer e non un puntatore ad Integer; se vogliamo utilizzare per forza ptInt dobbiamo scrivere allora:
WriteInteger(ptInt^);
Secondo la sintassi del Pascal, infatti, ptInt^ è la variabile puntata da ptInt e cioè, i1 (che è un Integer); queste assurdità sono legate in parte agli stretti vincoli sulla compatibilità dei tipi di dati del Pascal e in parte al fatto che il linguaggio Pascal è stato progettato proprio per impedire ai programmatori di ricorrere a questi "giochetti".

La procedura WriteInteger può essere resa più flessibile in questo modo: In questo caso, supponendo che ptInt stia puntando a i1, possiamo effettuare la seguente chiamata:
WriteInteger(ptInt); {passaggio indiretto dell'indirizzo di i1}
Alternativamente, grazie alle estensioni del Turbo Pascal si può anche scrivere:
WriteInteger(@i1); {passaggio diretto dell'indirizzo di i1}
oppure:
WriteInteger(Addr(i1)); {passaggio diretto dell'indirizzo di i1}
Appare chiaro, però, che per poter scrivere codice Pascal di questo genere è necessario avere una adeguata conoscenza dell'Assembly; in altre parole, il programmatore deve conoscere tutti i dettagli sul funzionamento a basso livello di un programma Pascal.

Come è stato spiegato in precedenza, per aggirare tutti questi problemi conviene spesso ricorrere all'interfacciamento con l'Assembly in modo da avere a disposizione una sintassi molto più chiara e semplice, soprattutto per i puntatori; come vedremo nel seguito del capitolo, si rivela estremamente efficace anche il ricorso all'Assembly inline.
Una procedura Assembly che riceve come argomento un puntatore Pascal, si trova ad avere a che fare con una normalissima coppia Seg:Offset; a questo punto, la gestione di questa coppia Seg:Offset si svolge nell'identico modo già illustrato nei precedenti capitoli.
È importante solo ricordare che tutti i puntatori del Turbo Pascal sono di tipo FAR; di conseguenza, una procedura Assembly che restituisce un puntatore ad un modulo Pascal, deve restituire la coppia completa Seg:Offset (in DX:AX).

Un'ultima considerazione riguarda il fatto che anche il Pascal permette di passare una procedura A come argomento di un'altra procedura B; come abbiamo visto nei precedenti capitoli, in un caso del genere viene passato come argomento l'indirizzo di memoria da cui inizia il corpo della procedura A e cioè, l'indirizzo della prima istruzione della procedura A.
Per gestire questa situazione, la sintassi utilizzata dal Turbo Pascal si discosta da quella definita nel Pascal classico; vediamo a tale proposito un esempio pratico.
Supponiamo di voler scrivere una procedura che riceve come argomento un'altra procedura avente le caratteristiche della WriteInteger vista in precedenza; prima di tutto dobbiamo creare un apposito tipo di dato e quindi nella sezione type del programma possiamo scrivere, ad esempio:
ptProcInt = procedure(var i: Integer);
In questo modo abbiamo dichiarato un nuovo tipo di dato ptProcInt che rappresenta un tipo puntatore a una procedure la quale richiede un argomento di tipo Integer da passare per indirizzo; a questo punto possiamo creare la nostra procedura che avrà una intestazione del tipo:
procedure ProcTest(pp1: ptProcInt, var i: Integer);
Nel blocco delle istruzioni principali possiamo ora effettuare chiamate del tipo:
ProcTest(WriteInteger, i1);
In un caso del genere quindi, la procedura ProcTest riceve come primo argomento la coppia Seg:Offset che rappresenta l'indirizzo di memoria da cui inizia il corpo di WriteInteger; come secondo argomento ProcTest riceve in modo "occulto" l'indirizzo Seg:Offset di i1 e non il valore di i1.

31.5 Protocollo di comunicazione tra Pascal e Assembly

Analizziamo ora le regole che permettono a due o più moduli Pascal e Assembly di comunicare tra loro; l'insieme di queste regole definisce il protocollo di comunicazione tra Pascal e Assembly.

Un modulo Assembly che fa parte di un programma Pascal può definire le sue eventuali variabili globali in uno o più blocchi di dati che possono avere caratteristiche scelte a piacere dal programmatore; tali blocchi, infatti, vengono totalmente ignorati dal compilatore Pascal.
La conseguenza pratica di tutto ciò è data dal fatto che le variabili globali presenti in un modulo Assembly risultano invisibili nei moduli Pascal; se proviamo a dichiarare PUBLIC tali variabili, otteniamo un messaggio di errore da parte del compilatore Pascal!

Un modulo Assembly che fa parte di un programma Pascal può definire le sue procedure in uno o più blocchi di codice che possono avere caratteristiche scelte a piacere dal programmatore; anche in questo caso, però, tali blocchi di codice vengono ignorati dal compilatore Pascal e non possono dichiarare procedure PUBLIC.
Se vogliamo che le procedure presenti in un modulo Assembly risultino visibili anche nel modulo principale Pascal o nelle unit Pascal, dobbiamo inserire le definizioni di tali procedure in un blocco di codice che deve chiamarsi obbligatoriamente CODE; in generale, le caratteristiche di questo segmento di codice sono le seguenti: Tutte le procedure dichiarate PUBLIC in questo blocco di codice, risultano visibili anche nei moduli Pascal; alternativamente è possibile definire il blocco CODE codice anche in questo modo: Ciò significa che se vogliamo utilizzare le caratteristiche avanzate di MASM, possiamo servirci della direttiva semplificata .CODE; in ogni caso, si tenga presente che è fondamentale l'utilizzo del nome CODE o _TEXT.
Queste strane regole sono una diretta conseguenza del fatto che il compilatore Turbo Pascal non genera alcun object file; questo fatto ci obbliga ad effettuare il linking di un modulo Assembly secondo uno schema imposto dal compilatore stesso.

Il Turbo Pascal utilizza due soli modelli di memoria che equivalgono al modello SMALL e al modello LARGE; per abilitare il modello LARGE è necessario selezionare il menu:
Options - Compiler - Force far calls
Alternativamente è anche possibile inserire la direttiva {$F+} all'inizio di tutti i moduli Pascal; analogamente, la direttiva {$F-} disabilita le FAR call.

Se scriviamo un programma Pascal che fa un uso massiccio dei puntatori, possiamo imbatterci in messaggi di errore del tipo:
Invalid procedure or function reference
Questo messaggio di errore è legato spesso al fatto che abbiamo disabilitato le FAR call; per evitare qualsiasi problema, si raccomanda vivamente quindi di abilitare sempre le FAR call, soprattutto quando si interfaccia il Pascal con l'Assembly.
Di conseguenza, è importantissimo che tutte le procedure PUBLIC presenti in un modulo Assembly siano di tipo FAR; se stiamo utilizzando le caratteristiche avanzate di MASM, dobbiamo servirci necessariamente della direttiva:
.MODEL LARGE, PASCAL
I moduli Pascal che intendono utilizzare le procedure PUBLIC definite nel blocco CODE di un modulo Assembly, devono dichiarare tali procedure con il qualificatore external; la sintassi del Turbo Pascal prevede, inoltre, che sia utilizzata la direttiva {$L nomefile.obj} per indicare in quale object file si trova la procedura Assembly esterna.
Per ogni object file è necessaria una sola direttiva {$L}; l'object file deve essere in formato OMF (Object Module Format).

Supponiamo, ad esempio, di aver creato in un modulo ASMMOD.ASM una procedura PUBLIC denominata AreaCerchio ed avente il seguente prototipo Pascal:
function AreaCerchio(r: double): double;
Nel modulo Pascal che intende utilizzare questa function dobbiamo prima di tutto inserire la direttiva:
{$L asmmod.obj}
A questo punto, nello stesso modulo Pascal, dobbiamo dichiarare AreaCerchio come:
function AreaCerchio(r: double): double; external;
Nel caso di una unit Pascal, la precedente dichiarazione deve trovarsi nella sezione implementation; se vogliamo che AreaCerchio sia visibile anche all'esterno della unit dobbiamo inserire nella sezione interface la dichiarazione:
function AreaCerchio(r: double): double;
Come si può notare, la dichiarazione inserita nella sezione interface è priva del qualificatore external.

Tutte le procedure contenute nei moduli Assembly da linkare al Pascal, devono preservare rigorosamente il contenuto dei registri CS, DS, SS, SP e BP; se si utilizzano le caratteristiche avanzate di MASM, all'interno delle procedure dotate di stack frame il contenuto dei registri SP e BP viene automaticamente preservato dall'assembler.
Tutti gli altri registri sono a completa disposizione del programmatore; per tali registri quindi non è necessario preservarne il contenuto.

Il Pascal è un linguaggio case-insensitive e quindi i compilatori Pascal non distinguono tra maiuscole e minuscole; possiamo affermare quindi che in Pascal i nomi come varword1, VarWord1, VARWORD1, etc, rappresentano tutti lo stesso identificatore. Se in un modulo Pascal definiamo una variabile chiamata varword1 e poi proviamo a definire una seconda variabile chiamata VARWORD1, otteniamo quindi un messaggio di errore da parte del compilatore; in osservanza a queste regole, al momento di assemblare un modulo Assembly da linkare ad un modulo Pascal, non dobbiamo assolutamente utilizzare le opzioni come /Cp per il MASM.

31.6 Esempi pratici

In base alle considerazioni appena esposte e a ciò che abbiamo visto nei precedenti capitoli, possiamo facilmente scrivere programmi di qualunque complessità, formati da un numero arbitrario di moduli Pascal e moduli Assembly; vediamo allora un unico esempio formato complessivamente da 4 moduli distinti denominati: PASMOD.PAS, ASMMODA.ASM, UNITMOD.PAS e ASMMODB.ASM.

Il modulo PASMOD.PAS ricopre il ruolo di modulo principale del programma e contiene quindi anche l'entry point e il blocco begin end che rappresenta il blocco di codice principale; inoltre, il modulo PASMOD.PAS si serve delle procedure definite nel modulo ASMMODA.ASM.
Il modulo UNITMOD.PAS è una unit contenente una serie di procedure utilizzate anch'esse da PASMOD.PAS; la unit UNITMOD.PAS a sua volta si serve delle procedure definite nel modulo ASMMODB.ASM.

Il modulo principale PASMOD.PAS assume l'aspetto mostrato in Figura 31.4. Prima di tutto notiamo le direttive {$F+} (FAR call abilitate), {SG+} (set di istruzioni 286), {$N+} (set di istruzioni 287); è presente anche una direttiva {$L} che richiede il linking di PASMOD.PAS con il modulo ASMMODA.OBJ.

Il modulo PASMOD.PAS contiene l'intestazione:
Program PascalModule;
Si può affermare quindi che tutto il codice di questo modulo sarà inserito in un segmento di programma avente le seguenti caratteristiche: Nella sezione uses vengono incluse le unit Crt e UnitMod; come vedremo in seguito, l'inclusione di Crt è necessaria per permettere anche al modulo ASMMODA.ASM di utilizzare le procedure definite in tale unit.

Nella sezione type viene dichiarato un tipo di dato vStrType che rappresenta un vettore di 10 stringhe formate ciascuna da 8 BYTE; il compilatore, come sappiamo, assegna in realtà 9 BYTE ad ogni stringa in quanto il primo BYTE è destinato a contenere la lunghezza della stringa stessa.
Complessivamente, quindi, ogni variabile di tipo vStrType richiede:
10 * 9 = 90 byte di memoria
Successivamente viene dichiarato un tipo di dato recCliente che è un record formato da un campo codice a 16 bit, da un campo telefono a 32 bit e da un campo nome che è una stringa da 42 BYTE (cioè, 41 BYTE più il BYTE iniziale contenente la lunghezza della stringa stessa); complessivamente, una variabile di tipo recCliente richiede:
2 + 4 + 42 = 48 byte di memoria
Le sezioni const e var dichiarano una serie di variabili che verranno utilizzate dal programma; come sappiamo, tutte queste variabili sono visibili anche all'esterno del modulo PASMOD.PAS.

Subito dopo l'entry point, viene chiamata una procedura InitScreen definita nel modulo ASMMODA.ASM; questa procedura richiede due parametri di tipo Byte che vengono utilizzati per inizializzare lo schermo con un colore di sfondo (bgColor) e un colore di primo piano (fgColor).
Le costanti predefinite come Yellow, Blue, etc, vengono create nella unit Crt; a tale proposito, si può consultare il file CRT.INT presente nella cartella DOC del Turbo Pascal.

Il compito successivo svolto dal modulo principale consiste nell'inizializzare una variabile recCli2 di tipo recCliente; questa variabile viene poi utilizzata per inizializzare un'altra variabile recCli1 sempre di tipo recCliente.
A tale proposito, viene chiamata una procedura InitRecCli definita nel modulo ASMMODA.ASM; come si può notare, InitRecCli richiede due argomenti di tipo puntatore a recCliente.
Il parametro prc1 punta al record destinazione, mentre il parametro prc2 punta al record sorgente; per dimostrare che InitRecCli ha veramente copiato recCli2 in recCli1, vengono visualizzati tutti i campi dello stesso record recCli1.
Nella chiamata:
InitRecCli(@recCli1, @recCli2);
vengono passati direttamente gli indirizzi dei due argomenti; tutto ciò equivale a scrivere:
InitRecCli(Addr(recCli1), Addr(recCli2));
Se vogliamo passare indirettamente gli indirizzi dei due argomenti, dobbiamo servirci delle variabili puntatore; definendo due variabili Ptr1 e Ptr2 di tipo Pointer, dopo aver fatto puntare Ptr1 a recCli1 e Ptr2 a recCli2 possiamo scrivere semplicemente:
InitRecCli(Ptr1, Ptr2);
L'ultimo compito svolto dal modulo principale consiste nel chiamare le due procedure UnitProcedure e AreaCerchio definite nella unit UnitMod; la procedura UnitProcedure richiede due argomenti di tipo puntatore a vStrType.
Il contenuto della locazione puntata dal secondo puntatore (sorgente) viene copiato nella locazione puntata dal primo puntatore (destinazione); anche in questo caso, per dimostrare che UnitProcedure ha effettivamente copiato vColors in vCol, vengono visualizzate tutte le 10 stringhe che formano lo stesso vCol.

La procedura AreaCerchio richiede un Double come argomento e restituisce un altro Double; il valore restituito rappresenta l'area di un cerchio avente come raggio l'argomento passato a AreaCerchio.

Per sapere come lavorano le due procedure InitScreen e InitRecCli, esaminiamo il modulo ASMMODA.ASM che assume l'aspetto mostrato in Figura 31.5. Bisogna ribadire che è fondamentale l'utilizzo del nome CODE o _TEXT per il blocco di codice contenente le procedure da rendere visibili nei moduli Pascal; se non si utilizzano questi nomi, si ottiene un messaggio di errore da parte del compilatore.

Nel blocco delle direttive EXTRN osserviamo che ASMMODA.ASM è in grado di vedere tutte le variabili e le procedure globali definite in PASMOD.PAS; inoltre, ASMMODA.ASM può anche vedere tutte le variabili e le procedure globali definite nelle varie units del Turbo Pascal.
Notiamo, infatti, che ASMMODA.ASM utilizza tre procedure appartenenti alla unit Crt; a tale proposito, il modulo PASMOD.PAS deve richiedere, nella sezione uses, l'inclusione di Crt nel programma.

Come abbiamo visto in precedenza, il primo compito svolto dal modulo PASMOD.PAS consiste nell'effettuare la chiamata:
InitScreen(Blue, Yellow);
Ricordando che il Pascal passa gli argomenti a partire dal primo e tenendo conto della FAR call, possiamo affermare che il secondo parametro fgColor viene a trovarsi a BP+6, mentre il primo parametro bgColor viene a trovarsi a BP+8; questi due parametri sono di tipo BYTE ma, ovviamente, ciascuno di essi è inserito nello stack negli 8 bit meno significativi di una WORD.

La procedura InitScreen svolge il proprio lavoro chiamando in successione le tre procedure TextBackground, TextColor e ClrScr definite nella unit Crt; per conoscere i prototipi di queste procedure si può consultare l'help in linea del Turbo Pascal.
Nel caso, ad esempio, di TextBackground abbiamo:
procedure TextBackground(Color: Byte);
L'unico argomento richiesto da TextBackground è di tipo Byte; naturalmente, in un modulo Assembly è compito del programmatore passare a TextBackground un argomento a 16 bit con gli 8 bit più significativi che, in genere, valgono 0.
Bisogna anche ricordare che, nel rispetto delle convenzioni Pascal, la pulizia dello stack viene delegata alla procedura chiamata; nel nostro caso, TextBackground provvederà a togliere 2 byte dallo stack.

Prima di terminare, InitScreen visualizza la stringa strPas attraverso la procedura WriteString; sia strPas che WriteString vengono definite nel modulo PASMOD.PAS.
Il prototipo di WriteString è:
procedure WriteString(var s: String; d: Word);
Il parametro s è l'indirizzo di una stringa, mentre il parametro d è la colonna dello schermo nella quale verrà stampato l'ultimo carattere della stringa; nel nostro caso la stringa viene visualizzata in modo che termini a 62 caratteri di distanza dal bordo sinistro dello schermo.
Siccome stiamo lavorando con i puntatori FAR, la procedura WriteString deve ricevere l'indirizzo completo Seg:Offset di strPas; come al solito, è importantissimo ricordare che la componente Seg deve essere inserita per prima nello stack.
Osserviamo, inoltre, che nel rispetto delle convenzioni Pascal, gli argomenti da passare a WriteString vengono inseriti nello stack a partire dal primo; infatti, prima viene inserito l'indirizzo FAR di strPas e poi viene inserito il valore immediato 62 (a 16 bit).

La seconda procedura chiamata da PASMOD.PAS è InitRecCli che richiede come argomenti due puntatori a record di tipo recCliente; questa procedura copia il record puntato da prc2 (sorgente) nel record puntato da prc1 (destinazione).
Anche in questo caso osserviamo subito che abbiamo a che fare con puntatori FAR contenenti ciascuno una coppia Seg:Offset da 16+16 bit; di conseguenza, il parametro prc2 si viene a trovare a BP+6, mentre il parametro prc1 si viene a trovare a BP+10.
Per gestire questi due parametri vengono utilizzati i registri DS:SI come sorgente e ES:DI come destinazione; in questo modo possiamo utilizzare REP MOVSB per effettuare la copia ad altissima velocità.
È importante ricordarsi di utilizzare l'istruzione CLD in modo da abilitare l'incremento automatico dei puntatori SI e DI; il contatore CX contiene il valore 48 che, come abbiamo visto in precedenza, è la dimensione in byte dei record di tipo recCliente.
Notiamo che 48 è divisibile per 4 (48 / 4 = 12) per cui, all'interno della procedura InitRecCli possiamo caricare il valore 12 in CX e scrivere l'istruzione:
rep movsd
Questa istruzione, come sappiamo, è mediamente 4 volte più veloce dell'analoga istruzione che utilizza MOVSB!
Tutto ciò dimostra che nonostante il Turbo Pascal sia un compilatore a 16 bit, nei moduli Assembly possiamo sfruttare ugualmente il set di istruzioni a 32 bit; questo è un ulteriore motivo per interfacciare il Turbo Pascal con l'Assembly.

Per concludere l'analisi di InitRecCli, osserviamo che questa procedura modifica il registro DS, per cui è importantissimo preservarne il contenuto originale; in caso contrario, al termine di InitRecCli il programma si pianta.
La procedura InitRecCli ha ricevuto due argomenti da 4 byte ciascuno e quindi deve terminare rimuovendo 8 byte dallo stack.

Le ultime due procedure chiamate dal modulo principale PASMOD.PAS, sono UnitProcedure e AreaCerchio; queste procedure vengono definite nel modulo UNITMOD.PAS. Per capire il funzionamento di UnitProcedure e di AreaCerchio analizziamo, in Figura 31.6, il contenuto della unit UnitMod. Anche UNITMOD.PAS specifica le direttive {$F+}, {$G+} e {$N+}; è presente, inoltre, la direttiva {$L} che richiede il linking con il modulo ASMMODB.OBJ il quale contiene diverse procedure utilizzate da UNITMOD.PAS.

L'intestazione della unit è:
unit UnitMod;
Come sappiamo, il nome della unit deve coincidere con il nome del file contenente la unit stessa; nel nostro caso il compilatore produrrà una unit memorizzata in un file chiamato UNITMOD.TPU.
In base all'intestazione che abbiamo utilizzato, tutto il codice di UNITMOD.PAS viene inserito in un segmento di programma avente le seguenti caratteristiche: La sezione interface contiene le definizioni delle variabili globali pubbliche e le dichiarazioni di tutte le procedure pubbliche fornite da questa unit; la sezione implementation contiene le definizioni delle variabili globali private e le definizioni di tutte le procedure pubbliche e private fornite da questa unit.
Come già sappiamo, i moduli Pascal che usano UnitMod sono in grado di vedere solo gli identificatori presenti nella sezione interface di questa unit; i moduli Assembly facenti parte del programma possono, invece, vedere tutti gli identificatori globali, pubblici e privati presenti in UnitMod.

La prima procedura di UNITMOD.PAS chiamata da PASMOD.PAS è UnitProcedure; il prototipo di questa procedura è il seguente:
procedure UnitProcedure(vs1, vs2: ptvStrType);
Questa procedura richiede quindi due argomenti di tipo puntatore a vStrType; il contenuto dell'area di memoria puntata da vs2 (sorgente), viene copiato nell'area di memoria puntata da vs1 (destinazione).
Per svolgere tale lavoro viene chiamata una apposita procedura InitvColors che viene definita nel modulo ASMMODB.ASM; prima di chiamare InitvColors, la procedura UnitProcedure mostra in pratica un esempio di chiamata ad una procedura che riceve come argomento un'altra procedura.
A tale proposito, viene chiamata la procedura StringLength definita sempre nel modulo ASMMODB.ASM; si tratta di una function che riceve come argomento l'indirizzo di una procedura di tipo ptPrcType e restituisce poi la lunghezza della stringa strUnit definita in UNITMOD.PAS.

Per analizzare il funzionamento di StringLength e di InitvColors dobbiamo fare riferimento al modulo ASMMODB.ASM; questo modulo presenta l'aspetto mostrato in Figura 31.7. La procedura StringLength presenta il seguente prototipo:
function StringLength(ptp: ptPrcType): Integer;
Questa procedura riceve l'indirizzo Seg:Offset di un'altra procedura; in particolare, nel modulo PASMOD.PAS notiamo la chiamata:
StringLength(WriteString2);
All'interno di StringLength il parametro ptp rappresenta quindi l'indirizzo Seg:Offset di WriteString2; la chiamata di WriteString2 è di tipo indiretto intersegmento, per cui è rappresentata dall'istruzione:
call dword ptr ptp
Come si può notare, WriteString2 riceve come argomento la coppia Seg:Offset della stringa strUnit definita in PASMOD.PAS; a questo punto WriteString2 provvede a visualizzare strUnit chiamando WriteLn.
Il compito successivo svolto da StringLength consiste nel caricare in AX il suo valore di ritorno rappresentato dalla lunghezza di strUnit e cioè, dal valore a 8 bit contenuto in strUnit[0].
Come si può notare, l'istruzione MOVZX azzera gli 8 bit più significativi di AX; prima di terminare, StringLength toglie dallo stack 4 byte che rappresentano la dimensione della coppia Seg:Offset ricevuta come argomento.

La procedura UnitProcedure riceve in AX la lunghezza di strUnit; per dimostrarlo notiamo che UnitProcedure chiama WriteLn per visualizzare il valore di ritorno di StringLength.

La seconda procedura chiamata da UnitProcedure è InitvColors; tale procedura copia un dato di tipo vStrType in un altro dato di tipo vStrType.
Nel modulo ASMMODB.ASM possiamo notare che InitvColors è del tutto simile alla procedura InitRecCli definita nel modulo ASMMODA.ASM; ciò dimostra che in Assembly possiamo gestire allo stesso modo un puntatore a recCliente e un puntatore a vStrType senza avere tra i piedi il controllo di tipo dei linguaggi di alto livello.
È chiaro, infatti, che in ogni caso, un puntatore FAR non è altro che una coppia Seg:Offset; in pratica, potremmo riunire InitvColors e InitRecCli in un'unica procedura che riceve come terzo argomento la dimensione in byte dei blocchi da copiare.
In relazione a InitvColors osserviamo, inoltre, che i dati di tipo vStrType occupano ciascuno 90 byte di memoria; invece di usare MOVSB con CX=90, possiamo allora utilizzare MOVSW con CX=45.

Il programma termina con la chiamata da parte di PASMOD.PAS della procedura AreaCerchio dichiarata in UNITMOD.PAS e definita in ASMMODB.ASM; si tratta di una function che riceve come argomento il raggio r di un cerchio e restituisce come valore di ritorno l'area del cerchio di raggio r.
Come si può notare in ASMMODB.ASM, questi calcoli vengono effettuati con la FPU; rispetto all'esempio presentato nel precedente capitolo, l'unica novità è rappresentata dall'uso delle istruzioni FST op e FLDPI. L'operando op di FST può essere anche un altro registro della FPU; il numero irrazionale ℼ = 3.14159267... in ST(0) è in formato Temporary Real a 80 bit.
Per il calcolo dell'area del cerchio viene utilizzata, ovviamente, la formula:
Area = ℼ * r2
Al termine del calcolo il risultato ottenuto è a disposizione del caller nel registro ST0 della FPU; per dimostrarlo, nel modulo PASMOD.PAS viene visualizzato con WriteLn il valore di ritorno di AreaCerchio.
Osserviamo, infine, che AreaCerchio ha ricevuto come argomento un Double il quale occupa 8 byte nello stack (QWORD); di conseguenza, AreaCerchio deve terminare passando il valore immediato 8 all'istruzione RET.

31.6.1 Generazione dell'eseguibile

Come è stato spiegato in precedenza, la fase di generazione dell'eseguibile è condizionata dalle regole imposte dal compilatore Turbo Pascal; in generale, il procedimento da seguire si sviluppa nelle seguenti fasi: È importantissimo seguire quest'ordine per soddisfare tutte le dipendenze relative agli identificatori presenti nei vari moduli.

Per quanto riguarda l'assemblaggio dei moduli Assembly con MASM, è fondamentale ricordarsi che gli object file prodotti dall'assembler devono essere in formato OMF; con il MASM32 non bisogna utilizzare quindi l'opzione /coff. Inoltre, l'assemblaggio deve essere case-insensitive e quindi non bisogna utilizzare opzioni come /Cp.

La fase di compilazione dei moduli Pascal si può svolgere dall'interno dell'IDE fornito dalla Borland; dal prompt del DOS bisogna eseguire:
C:\TP\BIN\TURBO.EXE
A questo punto possiamo selezionare il menu:
Compile
Alternativamente è anche possibile utilizzare il compilatore a linea di comando; questo strumento è disponibile nella cartella BIN del Turbo Pascal ed è rappresentato dal file TPC.EXE. Digitando TPC dalla linea di comando e premendo [Invio] si ottiene l'elenco completo delle opzioni di configurazione del compilatore.

Partendo con la fase di assemblaggio, con il comando:
ml /c asmmoda.asm
generiamo il moduloASMMODA.OBJ;

con il comando:
ml /c asmmodb.asm
generiamo il modulo ASMMODB.OBJ.

A questo punto, dall'interno dell'IDE procediamo alla configurazione del compilatore; attraverso il menu:
Options - Compiler
dobbiamo abilitare l'uso delle istruzioni dell'80286, l'uso della FPU 80287 e l'uso delle FAR call; tutte queste opzioni possono essere specificate direttamente nei moduli Pascal grazie, rispettivamente, alle direttive {$G+}, {$N+} e {$F+}.

Attraverso il menu:
Compiler - Destination Disk
chiediamo al Turbo Pascal di generare l'eseguibile su disco; in caso contrario, l'eseguibile viene generato solamente in memoria!

Terminata la fase di configurazione, attiviamo con il mouse la finestra contenente il modulo UNITMOD.PAS e selezioniamo il menu:
Compile - Compile
In questo modo otteniamo una unit memorizzata nel file UNITMOD.TPU.

Attiviamo ora con il mouse la finestra contenente il modulo PASMOD.PAS e selezioniamo il menu:
Compile - Compile
In questo modo otteniamo un modulo intermedio in formato P-Code che, come è stato spiegato in precedenza, viene nascosto al programmatore. L'ultima fase consiste nel selezionare il menu:
Compile - Make
che produce l'eseguibile finale chiamato PASMOD.EXE.

Se vogliamo lavorare solo dalla linea di comando, dopo l'assemblaggio dei due moduli Assembly possiamo impartire i comandi:
tpc unitmod.pas
e:
tpc -GD pasmod.pas
(l'opzione -GD richiede al Turbo Pascal la generazione di un dettagliato map file a cui sarà assegnato il nome PASMOD.MAP).

Si tenga presente che, per il corretto svolgimento delle varie fasi, i moduli ASMMODA.OBJ, ASMMODB.OBJ, UNITMOD.PAS e PASMOD.PAS devono trovarsi tutti nella directory di lavoro del Turbo Pascal; può essere anche necessario aggiungere il percorso C:\TP\BIN alla linea PATH del file C:\AUTOEXEC.BAT.

31.6.2 Allineamento dei dati al BYTE e alla WORD

Anche il Turbo Pascal permette di selezionare l'allineamento dei dati al BYTE o alla WORD; in questo modo è possibile ottimizzare l'accesso ai dati da parte delle CPU con Data Bus formato da 16 o più linee.
Per stabilire il tipo di allineamento per i dati è necessario selezionare il menu:
Options - Compiler
Nella finestra che compare bisogna attivare o disattivare la voce Word align data. In alternativa si può inserire direttamente nel programma la direttiva {$A+} che abilita l'allineamento dei dati alla WORD; viceversa, la direttiva {$A-} disabilita ogni forma di allineamento, in modo che i dati vengano disposti in memoria in modo consecutivo e contiguo.

Consideriamo, ad esempio, il seguente blocco dati di un programma Pascal: In presenza della direttiva {$A-}, la variabile i1 a 16 bit viene disposta all'offset 0000h del blocco dati, la variabile b1 a 8 bit viene disposta all'offset 0002h del blocco dati, mentre il vettore vw1 viene fatto partire dall'offset 0003h dello stesso blocco dati; in questo caso vw1 parte da un offset dispari e le CPU a 16 bit hanno bisogno di due accessi in memoria per leggere o scrivere ogni WORD del vettore.
In presenza, invece, della direttiva {$A+}, la variabile i1 a 16 bit viene disposta all'offset 0000h del blocco dati, la variabile b1 a 8 bit viene disposta all'offset 0002h del blocco dati, mentre il vettore vw1 viene fatto partire dall'offset 0004h dello stesso blocco dati; in questo caso vw1 parte da un offset pari e le CPU a 16 bit hanno bisogno di un solo accesso in memoria per leggere o scrivere ogni WORD del vettore.

Nei moduli Pascal tutti questi aspetti vengono gestiti direttamente dal compilatore; nei moduli Assembly, invece, è compito del programmatore tenere conto dell'attributo di allineamento che è stato selezionato per i dati EXTRN definiti in un modulo Pascal.
In generale, è vivamente sconsigliabile scrivere programmi Assembly che cercano di dedurre in modo empirico l'offset di una variabile; utilizzando, invece, l'istruzione LEA o l'operatore OFFSET si ottiene questa informazione in modo assolutamente sicuro ed affidabile.

31.7 Assembly inline

L'enorme successo ottenuto dal Borland Turbo Pascal è dovuto anche alla eccezionale semplicità ed efficienza che caratterizza il supporto dell'Assembly inline da parte di questo compilatore; il lavoro del programmatore viene ulteriormente agevolato dal fatto che il potente editor integrato del Turbo Pascal permette di evidenziare, con una apposita sintassi a colori, i blocchi di istruzioni Assembly inline inseriti direttamente nel codice Pascal.

Le istruzioni Assembly inline del Turbo Pascal devono trovarsi all'interno di un blocco delimitato dalle due parole riservate asm e end; i blocchi di istruzioni Assembly inline possono comparire anche nel blocco di codice principale del programma.
Come è stato spiegato nel precedente capitolo, non si può pretendere che l'Assembly inline abbia la stessa potenza e flessibilità dell'Assembly vero e proprio; in ogni caso, un programmatore Assembly esperto può fare veramente di tutto, anche con l'Assembly inline!

Vediamo un semplice esempio pratico rappresentato da una procedura che copia una stringa sorgente in una stringa destinazione e converte poi la stringa destinazione in maiuscolo; questo esempio è contenuto nel modulo INLINE.PAS mostrato in Figura 31.8. La procedura strToUpperCase richiede due argomenti che rappresentano gli indirizzi Seg:Offset di due dati di tipo String; il primo argomento è la stringa destinazione, mentre il secondo argomento è la stringa sorgente.
Il lavoro svolto da questa procedura consiste nel copiare s (sorgente) in d (destinazione); in seguito, la stringa destinazione viene convertita in maiuscolo attraverso l'algoritmo che già conosciamo. Il valore di ritorno di strToUpperCase è un Integer che rappresenta la lunghezza della stringa appena convertita.

Come è stato appena spiegato, siccome i due argomenti vengono passati per indirizzo, d e s rappresentano delle coppie Seg:Offset; l'indirizzo s (stringa sorgente) viene caricato in DS:SI, mentre l'indirizzo d (stringa destinazione) viene caricato in ES:DI.
La lunghezza della stringa sorgente e cioè, il contenuto del suo BYTE di indice 0, viene caricato in CX; osserviamo che l'Assembly inline del Turbo Pascal non supporta le istruzioni a 32 bit, per cui non possiamo utilizzare, ad esempio, MOVZX per caricare direttamente un valore a 8 bit in CX.
La lunghezza della stringa sorgente viene salvata nella variabile locale Lung che rappresenta anche il valore di ritorno della procedura; il contenuto di CX viene poi incrementato di 1 per tenere conto anche del BYTE di indice 0 della stringa.
A questo punto, la copia di s in d avviene con la solita istruzione REP MOVSB; nello svolgimento di tale fase è importantissimo ricordarsi di preservare il contenuto di DS.

Nella fase di conversione di d in maiuscolo possiamo notare che il Turbo Pascal permette di inserire anche etichette locali all'interno di un blocco asm end; tali etichette devono essere precedute dal simbolo @@ (doppia chiocciola).
Osserviamo, inoltre, che come al solito, gli eventuali commenti devono seguire la sintassi del linguaggio di alto livello e non quella dell'Assembly.

Nel precedente capitolo abbiamo visto che in C un qualsiasi vettore viene sempre passato per indirizzo ad una procedura; il Pascal, invece, permette anche di passare un intero vettore per valore.
Naturalmente, si tratta di una scelta sconsigliabile in quanto, oltre ad influire negativamente sull'efficienza di un programma, comporta anche il rischio di saturazione dello stack; nel caso, ad esempio, del passaggio per valore di un vettore di 200 Integer, vengono inserite nello stack ben 200 WORD per un totale di 400 byte di dati!

Nel caso della procedura strToUpperCase può capitare che il programmatore voglia passare la stringa sorgente per valore in modo da evitare che il contenuto originale di questa stringa possa essere modificato dalla procedura stessa; in un caso del genere il prototipo di strToUpperCase diventa:
function strToUpperCase(var d: String; s: String): Integer;
Analizziamo ora il significato dei due parametri d e s; il parametro d rappresenta, come al solito, l'indirizzo completo Seg:Offset della stringa destinazione (in qualsiasi modello di memoria). La stringa sorgente, invece, è stata passata per valore, per cui il compilatore ha creato nello stack una copia di tutti i 256 BYTE della stringa stessa; è chiaro quindi che la copia della stringa sorgente si trova nel segmento STACK referenziato da SS.
L'offset iniziale di questa stringa è rappresentato, di conseguenza, dall'offset del parametro s calcolato rispetto a SS; come già sappiamo, per ottenere questa informazione possiamo usare LEA scrivendo, ad esempio:
lea si, s
A questo punto possiamo accedere alla copia della stringa sorgente tramite SS:SI; di conseguenza, i vari BYTE della stringa saranno rappresentati da SS:[SI+0], SS:[SI+1], SS:[SI+2] e così via sino all'ultimo BYTE che sarà SS:[SI+255].
In sostanza, se la stringa sorgente viene passata per valore, la copia di s in d può essere ottenuta in questo modo: All'interno delle procedure che utilizzano l'Assembly inline si sconsiglia vivamente di utilizzare istruzioni del tipo:
lea si, [bp+6]
Infatti, quando si usa l'Assembly inline, lo stack frame delle procedure viene gestito direttamente dal compilatore che provvede anche a preservare il contenuto originale di BP; oltre a preservare BP il compilatore potrebbe anche inserire altre informazioni nello stack.
Ciò significa che non possiamo essere certi della esatta posizione nello stack dei parametri e delle variabili locali di una procedura; utilizzando, invece, in modo esplicito i nomi simbolici associati ai parametri e alle variabili locali di una procedura, possiamo metterci al riparo da qualsiasi problema.
Se al posto dell'istruzione precedente scriviamo, ad esempio:
lea si, s
siamo sicuri che LEA caricherà in SI l'esatta posizione (offset) di s all'interno dello stack.

Le stesse considerazioni valgono, ovviamente, per i valori restituiti dalle function; nella fase di generazione dell'epilog code, infatti, il compilatore potrebbe sovrascrivere i registri AX e DX che stiamo utilizzando per contenere un valore di ritorno.
Per evitare problemi, le istruzioni per la restituzione del valore da parte di una function devono essere scritte in Pascal e non con l'Assembly inline; nel caso della procedura strToUpperCase possiamo notare che la lunghezza della stringa da convertire viene salvata nella variabile locale Lung in modo da poter scrivere alla fine:
strToUpperCase:= Lung;
In questo modo stiamo delegando al compilatore il compito di caricare Lung nel registro AX.

Un'ultima considerazione riguarda il fatto che anche con l'Assembly inline è proibito qualsiasi riferimento agli identificatori definiti nella unit System; non è possibile quindi scrivere istruzioni del tipo:
mov bx, seg WriteLn