|
Appunti informatica |
|
Visite: 1697 | Gradito: | [ Medio appunti ] |
Leggi anche appunti:Scrivere funzioni di libreriaScrivere funzioni di libreria La scrittura di un programma C implica sempre Il flusso elaborativoIl flusso elaborativo Qualsiasi programma può venire codificato in un linguaggio Numeri a casoNumeri a caso La libreria C include alcune funzioni per la generazione di |
In questo esempio presentiamo un programma TSR che consente di scrivere in un file specificato dall'utente il contenuto del video (in modo testo) quando vengano premuti contemporaneamente i due tasti di shift. Il testo del buffer video è aggiunto ai dati eventualmente già presenti nel file indicato; al termine di ogni riga (e prima di quella iniziale) è aggiunta una sequenza CR LF; in coda al buffer è inserito un carattere ASCII FormFeed o salto pagina), rendendo in tal modo il tutto particolarmente idoneo al successivo editing mediante programmi di videoscrittura.
Barninga_Z! - 1991
SHFVWRIT.C - TSR che scrive su un file il buffer video CGA, EGA,
VGA in modo testo formattato mediante l'aggiunta di
CR+LF in testa e a fine riga, e di FF in coda. Il nome
del file deve essere specificato sulla command line.
I bit del byte attributo (eccetto il bit del blink)
sono invertiti per segnalare lo svolgimento
dell'operazione e sono nuovamente invertiti al termine
della scrittura su file (effettuata in append). Se
l'applicazione interrotta 'ridisegna' una parte del
video durante la scrittura, i bit dei bytes attibuto
di tale porzione del video non vengono invertiti al
termine dell'operazione. Il TSR si attiva premendo
contemporaneamente i due SHIFT.
Compilato sotto TURBO C++ 1.01
tcc -O -d -rd -k- -Tm2 shfvwrit.c
#pragma inline
#pragma option -k-
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#define PRG 'SHFVWRIT'
#define YEAR '1991'
#define _BLANK_ ((char)32)
#define _FF_ ((char)12)
#define _LF_ ((char)10)
#define _CR_ ((char)13)
#define _NROW_ 25
#define _NCOL_ 80
#define _VBYTES_ _NROW_*_NCOL_
#define _NCR_ (_NROW_+1)
#define _NLF_ _NCR_
#define _NFF_ 1
#define _BUFDIM_ (_VBYTES_+_NCR_+_NLF_+_NFF_)
#define _MONO_VIDADDR_ ((char far *)0xB0000000L)
#define _COLR_VIDADDR_ ((char far *)0xB8000000L)
#define _SHFMASK_ ((char)3)
#define _MASK_ ((char)127)
#define _TSR_TEST_ 0x97 /* shfvwrit e' residente ? */
#define _TSR_YES_ 0xABFE /* risposta = si, e' residente */
#define _PSPENVOFF_ 0x2C /* off del seg ptr all'env.in psp */
#define _FNMLEN_ 80 /* lungh. max path con NULL finale */
#define int09h ((void (interrupt *)())(*((long *)TSRdata))) /*dd*/
#define int28h ((void (interrupt *)())(*(((long *)TSRdata)+1))) /*dd*/
#define int2Fh ((void (interrupt *)())(*(((long *)TSRdata)+2))) /*dd*/
#define fnameptr ((char far *)(*(((long *)TSRdata)+3))) /*dd*/
#define strptr ((char far *)(*(((long *)TSRdata)+4))) /*dd*/
#define shfflag (*(((int *)TSRdata)+10)) /*dw*/
#define exeflag (*(((int *)TSRdata)+11)) /*dw*/
#define shfstat (*((char far *)(*(((int *)TSRdata)+12)))) /*dw*/
#define vidstr (((char *)TSRdata)+26) /*arr*/
#define vidatr (((char *)TSRdata)+26+_BUFDIM_) /*arr*/
#define fname (((char far *)TSRdata)+26+_BUFDIM_+_VBYTES_) /*arr*/
#define int09h_asm TSRdata
#define int28h_asm TSRdata+4
#define int2Fh_asm TSRdata+8
#define fnameptr_asm TSRdata+12
#define strptr_asm TSRdata+16
#define clear_stack() asm
void TSRdata(void); /* consente l'uso del nome prima della definiz. */
void filewrit(void)
_OPENED:
asm
void vidstrset(void)
switch(_AL)
vidstop = (vidptr += (_BH*_VBYTES_));
strptr = vidstr;
*strptr++ = _CR_;
*strptr++ = _LF_;
for(i = 0;i < _NROW_;i++)
*strptr++ = _CR_;
*strptr++ = _LF_;
}
*strptr++ = _FF_;
filewrit();
for(;vidptr > vidstop;vidptr--)
*vidptr = (*(--vidptr) == (*(--atrptr) ^= _MASK_)) ? *vidptr :
*atrptr;
void interrupt newint28h(void)
clear_stack(); /* macro per uscire da funzione interrupt */
asm jmp dword ptr int28h_asm;
void interrupt newint09h(void)
void far newint2Fh(void) /* non interrupt: non serve salvare regs */
_CHAIN:
asm jmp dword ptr int2Fh_asm;
_ANSWER:
asm
void TSRdata(void)
int tsrtest(void)
_RESIDENT:
return(_AX);
void release_env(void)
void install(void)
int filetest(char *argv1)
_OPENED_NEW:
asm
_FNAME_OK:
asm xor ax,ax;
_EXITFUNC:
asm pop ds;
return(_AX);
void main(int argc,char **argv)
La struttura del programma non è particolarmente complessa. La main() controlla che sia stato fornito, via command line, un nome di file per l'output: in caso negativo l'elaborazione viene interrotta. La funzione tsrtest() verifica l'eventuale presenza in RAM del TSR, utilizzando l'int 2Fh (la tecnica è descritta a pag. ). Se il programma non è già residente, filetest() controlla la validità del nome di file specificato dall'utente: il servizio 60h dell'int 21h (vedere anche pag. ) è utilizzato per ricavarne il pathname completo, onde evitare che variazioni dei default relativi a drive e directory di lavoro determinino la scrittura dei dati in luoghi imprevisti. La main() invoca poi install(), che completa la fase di installazione: essa, in primo luogo, salva i vettori degli interrupt utilizzati dal programma e sostituisce ad essi quelli dei nuovi gestori; in seguito invoca release_env(), che libera la RAM occupata dall'environment fornito dal DOS al TSR (vedere pag. ), dal momento che questo non ne fa uso. Infine install() calcola il numero di paragrafi che devono rimanere residenti (l'algoritmo di calcolo è descritto a pag. ) e installa SHFVWRIT mediante l'int 21h, servizio 31h, 'cuore' della funzione di libreria keep() (pag. ), qui invocato direttamente per rendere il codice più compatto.
Tutti i dati globali necessari al programma sono gestiti nello spazio ad essi riservato, all'interno del code segment, dalla funzione jolly TSRdata(), che chiude il gruppo delle routine residenti. Le macro atte a facilitare i riferimenti a detti dati sono definite, nel sorgente, al termine delle direttive #define relative alle costanti manifeste. Disorientati? Niente paura: vedere a pag.
La funzione newint2Fh() è il nuovo gestore dell'int 2Fh: essa è dichiarata far in quanto la semplicità della sua struttura rende inutile il salvataggio automatico dei registri. Il concatenamento al gestore originale (nel caso in cui SHFVWRIT non sia ancora installato) è effettuato mediante una istruzione JMP; il ritorno al processo chiamante (SHFVWRIT installato) mediante una IRET. In entrambi i casi è indispensabile ripristinare lo stack quale esso appare al momento dell'ingresso nella funzione: a seconda della versione del compilatore può essere necessario, a tal fine, effettuare una POP del registro BP
La funzione newint09h() è il nuovo gestore dell'int 09h: esso si occupa semplicemente di analizzare il byte di stato degli shift per intercettare lo hotkey. Se l'utente preme i due shift e, al tempo stesso, il TSR non è attivo (exeflag ) viene posto a il flag indicante la richiesta di popup, cioè di attivazione. Anche in questo caso il concatenamento al gestore originale è effettuato con una JMP; lo stack è ripristinato dalla macro clear_stack(), precedentemente definita. La newint09h() è dichiarata interrupt per sicurezza. Essa, infatti, gestisce un interrupt hardware che viene invocato da un evento asincrono; inoltre contiene quasi esclusivamente codice C, con la conseguenza che non è possibile sapere a priori quali registri vengano da essa modificati. In questo caso appare prudente (e comodo) lasciare al compilatore il compito di salvare in modo opportuno i registri della CPU.
La newint28h() gestisce l'int 28h: essa è pertanto invocata dal DOS quando questo è in attesa di un input dalla tastiera. Se è stato richiesto il popup e il TSR non è attivo, viene posto a il flag che ne indica l'attivazione ed è invocata vidstrset(). Al rientro sono azzerati i flag e viene concatenato il gestore originale. Anche newint28h() è scritta quasi interamente in linguaggio C, pertanto è dichiarata interrupt
La funzione vidstrset() pilota le operazioni di lettura del buffer video e di preparazione alla scrittura nel file. Essa verifica la modalità video attuale mediante il servizio 0Fh dell'int 10h: se è grafica o testo a 40 colonne il controllo è restituito a newint28h() senza intraprendere alcuna azione, altrimenti viene opportunamente determinato l'indirizzo del buffer video.
Mediante indirezioni e incrementi di puntatori, il testo contenuto nel buffer è copiato nello spazio ad esso riservato nella TSRdata(), inserendo al tempo stesso i caratteri necessari per dare al testo il formato voluto; inoltre gli ASCII sono trasformati in ASCII (spazio). I byte attributo del buffer, e dunque i colori del testo visualizzato, dopo essere stati anch'essi salvati in un array collocato nella TSRdata(), sono modificati tramite un'operazione di XOR con un byte che funge da maschera: lo scopo è segnalare visivamente che SHFVWRIT è in azione. Al termine di queste operazioni è invocata filewrit() e al rientro da questa una nuova operazione di XOR, identica alla precedente, ripristina i colori originali del testo nelle sole aree di video non modificate da altre applicazioni durante l'attività di filewrit()
Quest'ultima si occupa delle operazioni di output nel file specificato dall'utente: dal momento che il testo deve essere aggiunto al contenuto del file, è necessario che esso sia creato se non esiste, ma non distrutto se è già presente. Il servizio 5Bh dell'int 21h tenta l'apertura di un nuovo file: se questo esiste l'operazione fallisce (il servizio 3Ch ne avrebbe troncato a la lunghezza) e il file può essere aperto con il servizio 3Dh. Dopo l'operazione di scrittura il file viene chiuso: in tal modo il DOS aggiorna correttamente FAT e directory. La filewrit() è scritta quasi per intero in assmbly inline non solo per ragioni di velocità e compattezza del codice, ma anche (e soprattutto) per evitare l'uso di funzioni di libreria e, di conseguenza, i problemi descritti a pag.
Concludiamo il commento al codice di SHFVWRIT evidenziandone le carenze, dovute alla necessità di presentare un esempio di facile comprensione. Le routine residenti non segnalano all'utente il verificarsi di eventuali errori (modalità video non prevista, disco pieno, etc.) e mancano gestori per gli int 23h e 1Bh (CTRL‑C CTRL‑BREAK) e per l'int 24h (errore critico), la gestione dei quali è quindi lasciata all'applicazione interrotta. Il limite più evidente è però l'utilizzo dell'int 28h come punto di attivazione: detto interrupt è infatti generato dal DOS nei loop interni alle funzioni 01h‑0Ch dell'int 21h, cioè, in prima approssimazione, quando esso è in attesa di un input da tastiera. Se l'applicazione interrotta non si avvale del DOS per gestire la tastiera ma, piuttosto, del BIOS (int 16h), newint09() può ancora registrare nell'apposito flag la richiesta di popup, ma questo non avviene sino al termine dell'applicazione stessa.
Presentiamo di seguito il listato del programma VIDEOCAP, che rappresenta una evoluzione di SHFVWRIT. In particolare VIDEOCAP utilizza ancora quale punto di attivazione l'int 28h, ma incorpora un gestore dell'int 16h (newint16h()), il quale simula con un loop sul servizio 01h/11h le richieste di servizio 00h/10h; in tale loop viene invocato un int 28h (se non sono in corso servizi DOS) consentendo così l'attivazione anche sotto programmi che gestiscono la tastiera via int 16h. La routine di copia del buffer video individua il modo video attuale (ammessi testo 80 colonne colore o mono), il numero di righe video attuali e la pagina corrente, risultando così più flessibile e completa di quella incorporata da SHFVWRIT. L'offset della pagina video corrente nel buffer video è determinato moltiplicando 160 (80 % 2) per il numero di righe correnti e sommando un correttivo (individuato empiricamente) descritto in una tabella hard-coded, alla quale è riservato spazio dalla funzione fittizia rowFactors()
Si noti che le funzioni fittizie sono dichiarate DUMMY, così come il tipo di parametro richiesto. L'identificatore di tipo DUMMY è, in realtà, definito e reso equivalente a void dalla riga
typedef void DUMMY;
Si tratta dunque di uno stratagemma volto ad aumentare la leggibilità del programma (lo specificatore typedef definisce sinonimi per i tipi di dato; vedere pag.
VIDEOCAP, a differenza di SHFVWRIT, impiega una funzione fittizia per ogni variabile globale residente: in tal modo è più semplice referenziarle mediante operazioni di cast e si evitano alcune #define
Anche la modalità in cui il programma segnala di avere eseguito il proprio compito è radicalmente diverso: invece di modificare il colore dei caratteri a video durante l'operazione, VIDEOCAP emette un breve beep a 2000 Hz non appena chiuso il file. La funzione beep2000Hz() esemplifica come pilotare l'altoparlante del PC in una routine residente.
VIDEOCAP.C - Barninga_Z! - 09/11/92
Utility TSR per salvataggio del video su file. Invocare con un nome
di file per installare in memoria. Il dump su file si ottiene
premendo contemporaneamente i due tasti di shift. Per disinstallare
invocare con un asterisco come parametro sulla command line.
Compilato sotto BORLAND C++ 3.1:
bcc -Tm2 -O -d -rd -k- videocap.c
#pragma inline
#pragma warn -pia
#include <stdio.h> // la #include <io.h> deve essere DOPO tutte le
#include <dos.h> // funzioni DUMMY contenenti asm dup()
#include <string.h> // perche' in io.h e' definita int dup(int)
#define PRG 'VIDEOCAP'
#define VER '1.0'
#define YEAR '1992'
#define CRLF 0A0Dh // backwords
#define UNINSTALL '*' // opzione disinstallazione
#define BLANK 32
#define FORMFEED 12
#define _NCOL_ 80
#define _MAXROW_ 50
#define _BUFDIM_ ((_NCOL_*_MAXROW_)+(2*_MAXROW_)+3) // b*h+b*CRLF+CRLFFF
#define _MONO_V_SEG_ 0B000h
#define _COLOR_V_SEG_ 0B800h
#define _SHFMASK_ 3
#define _TSR_TEST_ 0xA1 // videocap e' residente ?
#define _TSR_YES_ 0xFF16 // risposta = si, e' residente
#define _FNAMELEN_ 81 // lungh. max pathname compreso NULL finale
#define BADCHARS ';,:|><' // caratteri illeciti nei nomi di file
typedef void DUMMY;
int pathname(char *path,char *src,char *badchrs);
char far *getInDOSaddr(void);
DUMMY resPSP(DUMMY)
DUMMY inDosFlagPtr(DUMMY)
DUMMY old09h(DUMMY)
DUMMY old16h(DUMMY)
DUMMY old28h(DUMMY)
DUMMY old2Fh(DUMMY)
DUMMY opReq(DUMMY)
DUMMY inOp(DUMMY)
DUMMY rowFactors(DUMMY) // fattori di offset per pagine video su VGA
DUMMY fileName(DUMMY)
DUMMY bufVid(DUMMY)
#include <io.h> // definisce dup(int); va incluso DOPO tutte le asm XX dup(Y)
void beep2000Hz(void)
void writebufVid(int rows)
int getOffsetByRow(void)
int setbufVid(void)
void far new09h(void)
void far new16h(void)
void interrupt new28h(void)
void s2Funinstall(void)
void far new2Fh(void)
void releaseEnv(void)
void install(void)
int tsrtest(void)
int filetest(char *fname)
void uninstall(void)
void main(int argc,char **argv)
VIDEOCAP chiama due funzioni delle quali, per brvità, il codice comprende esclusivamente i prototipi: si tratta di pathname(), utilizzata per ricavare il path completo del file fornito come parametro sulla riga di comando, e di getInDOSaddr(), che restituisce l'indirizzo dell'InDOS Flag (pag. ). I listati completi e commentati delle due funzioni si trovano, rispettivamente, a pag. e pag.
Infine, si noti che lo header file IO.H è incluso dopo la definizione di tutte le funzioni fittizie, per evitare che il compilatore interpreti come chiamate alla funzione dup(), in esso dichiarata, le direttive assembly DUP utilizzate per riservare spazio alle variabili globali residenti (vedere pag.
Appunti su: |
|