Si è detto che, normalmente, un TSR viene attivato mediante la pressione di una combinazione di tasti, detta hotkey sequence. Notizie dettagliate sulla tastiera, gli interrupt che la gestiscono ed i servizi da questi resi disponibili si trovano nei paragrafi dedicati all' int09h e all' int16h. Ulteriori esempi di gestori per l'int 09h sono quelli utilizzati nei programmi KBDPLUS e VIDEOCAP.
In questa sede vale la pena di riportare la descrizione di due servizi dell'int 21h, utili per pilotare le operazioni di I/O e di popup del TSR.
INT 21H, SERV. 0AH: INPUT DI UNA STRINGA MEDIANTE BUFFER
Input | AH | 0Ah |
DS:DX | indirizzo (seg:off) del buffer | |
Output | Nessuno; al ritorno il buffer contiene la stringa (vedere note) | |
Note | Il primo byte del buffer deve contenere, quando il servizio è invocato, la massima lunghezza consentita per la stringa (al massimo 254 caratteri, incluso il RETURN - ASCII 0Dh - finale). Il secondo byte contiene, in uscita, la lunghezza effettiva della stringa, escluso il RETURN terminatore. La stringa è memorizzata a pertire dal terzo byte del buffer. |
INT 21H, SERV. 0BH: CONTROLLO DELLO STATO DELL'INPUT
Input | AH | 0Bh |
Output | AL | FFh se un carattere è disponibile nello Standard Input (generalmente la tastiera);
00h se non vi è alcun carattere. |
Note | Questo servizio può essere utilizzato, tra l'altro, per intercettare la pressione di CTRLBREAK durante lunghi cicli di calcolo o altre elaborazioni che non attendono un input dalla tastiera, allo scopo di uscirne a richiesta dell'utente. E' superfluo aggiungere che, allo scopo, il programma (TSR o no) deve incorporare un gestore dell' int1Bh. |
Devono preoccuparsi di gestire il video tutti i TSR che, durante l'attività in foreground, ne modificano il contenuto: è indispensabile, infatti, che essi lo ripristinino prima di restituire il controllo all'applicazione interrotta. Di qui la necessità di salvare (in altre parole: copiare) il buffer video in un array di caratteri appositamente allocato, ed effettuare l'operazione opposta al momento opportuno. Le tecniche utilizzabili sono più di una: è possibile, ad esempio, ricorrere all' int10h oppure, se lo si preferisce[1], accedere direttamente alla memoria video. La tecnica di accesso diretto al buffer video può variare a seconda dello hardware installato e della modalità video selezionata; in questa sede ci occupiamo, in particolare, di alcuni servizi dell'int 10h limitandoci, per brevità, alla gestione della modalità testo.
INT 10H, SERV. 00H: STABILISCE NUOVI MODO E PAGINA VIDEO
Input | AH | 0Fh |
AL | modo video:
MODO COLONNE RIGHE COLORE 00h 40 25 grigi 01h 40 25 si 02h 80 25 grigi 03h 80 25 si 07h 80 25 16 EGA |
INT 10H, SERV. 02H: SPOSTA IL CURSORE ALLE COORDINATE SPECIFICATE
Input | AH | 02h |
BH | numero della pagina video:
PAGINE VALIDE MODI VIDEO 0-7 00h-01h 0-3 02h-03h 0 04h-07h 0-7 0Dh 0-3 0Eh 0-1 0Fh | |
DH | riga | |
DL | colonna |
INT 10H, SERV. 03H: LEGGE LA POSIZIONE DEL CURSORE
Input | AH | 03h |
BH | numero della pagina video | |
Output | CH | Scan line iniziale del cursore |
CL | Scan line finale | |
DH | Riga | |
DL | Colonna |
INT 10H, SERV. 05H: STABILISCE LA NUOVA PAGINA VIDEO
Input | AH | 05h |
AL | numero della pagina video |
INT 10H, SERV. 08H: LEGGE CARATTERE E ATTRIBUTO ALLA POSIZIONE DEL CURSORE
Input | AH | 08h |
BH | numero della pagina video | |
Output | AH | attributo del carattere. Vedere anche quanto detto circa i device driver. |
AL | codice ASCII del carattere |
INT 10H, SERV. 09H: SCRIVE CARATTERE E ATTRIBUTO ALLA POSIZIONE DEL CURSORE
Input | AH | 09h |
AL | codice ASCII del carattere | |
BH | numero della pagina video | |
BL | attributo del carattere | |
CX | numero di coppie car/attr da scrivere | |
Note | Non sposta il cursore. |
INT 10H, SERV. 0EH: SCRIVE UN CARATTERE IN MODO TTY
Input | AH | 0Eh |
AL | codice ASCII del carattere | |
BH | numero della pagina video | |
Note | Il carattere è scritto alla posizione del cursore, che viene spostato a destra di una colonna, portandosi a capo se necessario. |
INT 10H, SERV. 0FH: LEGGE IL MODO E LA PAGINA VIDEO ATTUALI
Input | AH | 0Fh |
Output | AH | numero di colonne |
AL | modo video | |
BH | pagina video |
INT 10H, SERV. 13H: SCRIVE UNA STRINGA CON ATTRIBUTO
Input | AH | 13h |
AL | subfunzione:
0 = usa l'attributo in BL; non sposta il cursore | |
BH | pagina video | |
BL | attributo colore (per servizi 0-1) | |
CX | lunghezza della stringa | |
DH | riga a cui scrivere la stringa | |
DL | colonna a cui scrivere la stringa | |
ES:BP | indirizzo della stringa da scrivere | |
Note | Le subfunzioni 2 e 3 interpretano la stringa come una sequenza di coppie di byte carattere/attributo: la stringa è copiata nel buffer video così come è.
Attenzione: modificare il valore di BP implica l'impossibilità di utilizzare le variabili automatiche (in quanto ad esse il compilatore accede con indirizzi relativi proprio a BP) fino al momento del suo ripristino. E' consigliabile salvarlo con una PUSH BP immediatamente prima di caricare l'indirizzo della stringa in ES:BP e ripristinarlo con una POP BP subito dopo la chiamata all'int 10h. |
Per un esempio di utilizzo del servizio 13h dell'int 10h vedere quanto detto circa i device driver.
Ecco, in dettaglio, un esempio di strategia operativa, ipotizzando il caso di un TSR che lavori esclusivamente in modo testo 80x25 (se il modo video non è appropriato il TSR rinuncia[2]):
Controllare il modo video tramite int 10h, serv. 0Fh. | |
Se il registro AL non contiene 2, 3 o 7 è impossibile effettuare il popup: uscire dalla routine segnalando (ad esempio con un bip) la situazione all'utente. | |
Altrimenti, se si prevede di modificare il contenuto di BH, salvarlo (esso è utilizzato nelle successive chiamate all'int 10h). | |
Eseguire l'int 10h, serv. 03h per conoscere la posizione attuale del cursore e salvare il contenuto di DX. | |
Portare il cursore alla posizione desiderata mediante il serv. 02h dell'int 10h (se si desidera salvare tutto il video andare in 0,0). | |
Eseguire un loop che, ad ogni iterazione, mediante il serv. 08h dell'int 10h salvi in un apposito array di interi una coppia attributo/carattere e muova il cursore mediante il serv. 02h: se si desidera salvare tutto il video il loop deve essere eseguito 2000 volte (80 x 25). | |
Se si desidera nascondere il cursore è sufficiente portarlo a riga 26. | |
Effettuare le operazioni di output connesse all'attività di foreground del TSR. | |
Portare il cursore nell'angolo superiore sinistro dell'area di video salvata in precedenza. | |
Ripristinarne il contenuto con un loop analogo a quello descritto al punto 6). | |
Riportare il cursore alla posizione originaria. |
Le numerose funzioni di libreria atte alla gestione dei file possono essere suddivise in due gruppi: del primo fanno parte quelle (come la fopen(), la fread(), etc.) che operano attraverso una struttura dati di tipo FILE associata al file (il cosiddetto stream); al secondo appartengono quelle operanti semplicemente attraverso lo handle associato al file (open(), read(), etc.). Nella parte transiente di un TSR è possibile utilizzare qualsiasi funzione senza correre alcun tipo di rischio, mentre, al contrario, nelle routine residenti è necessaria maggiore attenzione: l'uso delle funzioni appartenenti al primo gruppo è sconsigliato, in quanto esse utilizzano tecniche di allocazione dinamica della memoria "invisibili" al DOS. Quanto detto deve essere esteso anche a quelle del secondo gruppo quando si ricorra a funzioni come, ad esempio, la setbuf() per gestire attraverso un buffer la operazioni di I/O. Infine, ricordiamo che, in generale, l'uso di funzioni di libreria nelle routine residenti comporta alcuni problemi.
Meglio, allora, armarsi di santa pazienza e ricorrere direttamente ai servizi resi disponibili dall'int 21h, anche se ciò può comportare il ricorso allo inline assembly[3].
Sono, comunque, indispensabili alcune precauzioni importanti: in primo luogo va osservato che i file manipolati dalle routine residenti devono essere da queste gestiti con "cicli" completi durante l'attività in foreground. Ogni file deve, cioè, essere aperto, letto e/o scritto e, infine, richiuso prima di restituire il controllo all'applicazione interrotta. Vanno evitati nel modo più assoluto comportamenti pericolosi, come aprire i file in fase di installazione e lasciarli aperti a beneficio delle routine residenti, che gestiranno le operazioni di I/O senza mai chiuderli e riaprirli. Si tenga presente che il DOS è, di solito, in grado di mantenere un limitato numero di file aperti contemporaneamente: uno handle attivo ma inutilizzato rappresenta una risorsa preziosa sottratta al sistema. Inoltre, cosa ancor più importante, un TSR (salvo il caso in cui sia dotato di capacità di system monitoring particolarmente sofisticate) non può sapere che accade ai propri file per tutto il tempo in cui altre applicazioni sono attive in foreground: il tentativo (ed è solo un esempio tra i tanti possibili) di leggere dati da un file che non esiste più produrrebbe effetti analoghi a quelli di un tuffo in una piscina vuota[4].
In secondo luogo non bisogna dimenticare che molte delle opzioni accettate dalle funzioni di libreria inerenti la modalità di apertura dei file sono gestite dal DOS attraverso chiamate a servizi differenti. Di seguito, senza pretese di completezza, presentiamo alcuni schemi esplicativi.
INT 21H, SERV. 3CH: CREA UN NUOVO FILE O NE TRONCA UNO ESISTENTE
Input | AH | 3Ch |
CX | attributo del file:
00h = normale | |
DS:DX | indirizzo (seg:off) del nome del file (stringa ASCIIZ) | |
Output | AX | handle per il file se CarryFlag = 0, altrimenti codice dell'errore |
Note | Se il file specificato non esiste, viene creato; se esiste la sua lunghezza è troncata a 0 byte ed il contenuto distrutto. |
INT 21H, SERV. 3DH: APRE UN FILE ESISTENTE
Input | AH | 3Dh |
AL | modalità di apertura: campi di bit
BIT 0-2:
0 = sola lettura BIT 3: riservato (sempre 0) BIT 4-6:
0 = modo compatibilità BIT 7:
0 = utilizzabile da child process | |
DS:DX | indirizzo (seg:off) del nome del file (stringa ASCIIZ) | |
Output | AX | handle per il file se CarryFlag = 0, altrimenti codice dell'errore. |
Note | Se il file specificato non esiste, viene restituito un codice di errore; se esiste viene aperto e il puntatore è posizionato all'inizio del file. Su questo servizio si basa la funzione di libreria _open(). |
INT 21H, SERV. 3EH: CHIUDE UN FILE APERTO
Input | AH | 3Eh |
BX | handle del file | |
Output | AX | codice di errore se CarryFlag = 1 |
Note | I buffer associati al file sono svuotati e la directory viene aggiornata. |
INT 21H, SERV. 3FH: LEGGE DA UN FILE APERTO
Input | AH | 3Fh |
BX | handle del file | |
CX | numero di byte da leggere | |
DS:DX | indirizzo (seg:off) del buffer di destinazione | |
Output | AX | codice di errore se CarryFlag = 1, altrimenti numero di byte letti. |
INT 21H, SERV. 40H: SCRIVE IN UN FILE APERTO
Input | AH | 40h |
BX | handle del file | |
CX | numero di byte da scrivere | |
DS:DX | indirizzo (seg:off) del buffer contenente i byte da scrivere | |
Output | AX | codice di errore se CarryFlag = 1, altrimenti numero di byte scritti. |
INT 21H, SERV. 41H: CANCELLA UN FILE
Input | AH | 41h |
DS:DX | indirizzo (seg:off) del nome del file (stringa ASCIIZ) | |
Output | AX | codice di errore se CarryFlag = 1. |
INT 21H, SERV. 42H: MUOVE IL PUNTATORE ALLA POSIZIONE NEL FILE
Input | AH | 42h |
AL | punto di riferimento dell'offset:
0 = da inizio file | |
BX | handle del file | |
CX:DX | spostamento da effettuare in byte (long integer) | |
Output | AX | codice di errore se CarryFlag = 1, altrimenti DX:AX = nuova posizione nel file (long int). |
Note | E' possibile muovere il puntatore oltre la fine del file: in tal caso la lunghezza del file è aggiornata non appena è scritto almeno un byte. Muovere il puntatore ad un offset negativo rispetto all'inizio del file causa invece un errore. E' necessario effettuare almeno uno spostamento (anche se di 0 byte rispetto alla posizione corrente) tra una operazione di lettura ed una di scrittura, o tra una di scrittura ed una di lettura consecutive. |
Concludiamo il paragrafo con un suggerimento: l'algoritmo utile per gestire il file in append mode, cioè per aprirlo e scrivere in coda al medesimo:
Aprire il file mediante int 21h, serv. 3Dh: se il CarryFlag è 0 saltare al passo 3) | |
Aprire il file mediante int 21h, serv. 3Ch: se il CarryFlag è 1 l'operazione è fallita: uscire. | |
Muovere il puntatore al file di 0 byte rispetto alla fine del file mediante int 21h, serv. 42h (AL = 2; CX = 0; DX = 0;): se CarryFlag è 1 l'operazione è fallita: uscire. |
Se l'operazione 3) riporta CarryFlag = 0 è allora possibile effettuare le operazioni di scrittura, non trascurando di chiudere il file mediante int 21h, serv. 3Eh prima di restituire il controllo all'applicazione interrotta. Con la versione 3.0 del DOS è stato introdotto il servizio 5Bh, che agisce in maniera del tutto analoga al servizio 3Ch, ma con una importante differenza: fallisce se il file esiste (invece di troncarlo). Per un esempio pratico si vedano i programmi SHFVWRIT e VIDEOCAP.
Il DTA (Disk Transfer Address) è un buffer di 128 byte utilizzato da alcuni servizi dell'int 21h nelle operazioni di I/O con i dischi; per default esso è situato ad offset 80h nel PSP. Al momento del popup il TSR agisce nell'ambiente del programma interrotto e ne condivide, quindi, anche il DTA: le routine residenti, qualora utilizzino servizi basati sul DTA, devono salvare l'indirizzo del DTA dell'applicazione interrotta, comunicare al DOS quello del proprio e ripristinare l'indirizzo originale prima di cedere nuovamente il controllo all'applicazione. I servizi DOS che effettuano tali operazioni sono i seguenti:
INT 21H, SERV. 2FH: OTTIENE DAL DOS L'INDIRIZZO DEL DTA ATTUALE
Input | AH | 2Fh |
Output | ES:BX | Indirizzo (seg:off) del DTA attuale (se il servizio è richiesto dalla porzione residente di un TSR, normalmente è quello del programma interrotto). |
INT 21H, SERV. 1AH: COMUNICA AL DOS L'INDIRIZZO DEL DTA
Input | AH | 1Ah |
DS:DX | Indirizzo (seg:off) del DTA |
Infine, presentiamo l'elenco dei servizi dell'int 21h che fanno uso del DTA e dunque impongono al TSR le precauzioni di cui si è detto:
SERVIZI DELL'INT 21H UTILIZZANTI IL DTA
11h | FindFirst (Espansione wildcard) mediante FCB |
12h | FindNext (Espansione wildcard) mediante FCB |
14h | Lettura sequenziale mediante FCB |
15h | Scrittura sequenziale mediante FCB |
21h | Lettura Random mediante FCB |
22h | Scrittura Random mediante FCB |
27h | Lettura Random a Blocchi mediante FCB |
28h | Scrittura Random a Blocchi mediante FCB |
4Eh | FindFirst (Espansione wildcard) |
4Fh | FindNext (Espansione wildcard) |
Ad eccezione degli ultimi due in elenco, si tratta di servizi
dedicati alla gestione dei file mediante File Control Block[5]:
dal momento che le funzionalità da essi offerte si ritrovano
nei più recenti (e preferibili) servizi basati sulla tecnica
degli handle[6], solitamente non
vi è motivo per utilizzarli nella scrittura di programmi
per i quali non sia richiesta la compatibilità con versioni
di DOS anteriori alla 2.0.
Non ci ho capito niente! Ricominciamo...