16. A Turbo Pascal speciális lehetőségei A fejezetben a teljesség igénye nélkül bemutatunk néhány a Turbo Pascal hatékonyabb felhasználását lehetővé tevő megoldást. Elsősorban azok számára ajánljuk a fejezet áttanulmányozását, akik az előző részek feldolgozásával eljutottak a Turbo Pascal középhaladó szintű megismeréséig. 16.1. Overlay használata Mint ahogy már említettük a Turbo Pascal modulok kódjának maximális mérete 64 kbyte, azonban a felhasznált modulok száma nem korlátozott. Ily módon elképzelhető olyan program előállítása, amely kódja már nem fér be a memóriába. Hasonló problémával találkozunk akkor is, ha nagyobb méretű programból sok adatot kell feldolgoznunk. Ezen problémák áthidalására a Turbo Pascal az overlay (átlapolás) technikáját tartalmazza. Az overlay az operatív memória használatának olyan módja, amely során a memóriának ugyanazon részét (overlay puffer) más-más modulok kódjai foglalják el. Az overlay modulok a lemezen kerülnek tárolásra, és a program igényének megfelelően onnan töltődnek be. A 16.1. ábra szemlélteti a különbséget az overlay megoldást felhasználó és az overlay nélküli programok szerkezete között. Tegyük fel, hogy a programunk három modulból áll: MAIN (a főprogram), A és B. Legyen az egyes modulok kódjának hossza LMAIN, LA és LB (LA > LB). Igy az overlay nélküli program kódja LMAIN+LA+LB memóriát foglal le, míg az overlay megoldás használatával a program kódjának memóriaigénye LMAIN+LA-ra csökken. {EMBED MSDraw \* mergeformat|} 16.1. ábra Az overlay nélküli (a) és az overlay-t használó (b) program szerkezete. A program indításakor valamelyik modul azonnal betöltődik a memóriába a (fordító határozza meg), például az A. Ha valamely B-ben tárolt rutinra van szükség akkor a B betöltődésével az A részben felülíródik. Majd, ha újra A-beli rutinra hivatkozunk, akkor az A visszatöltődik. A Turbo Pascal-ban a fenti séma szerinti működés bonyolultabban valósul meg. A ciklikus szervezésű overlay puffert az overlay manager kezeli, amely egyszerre több overlay modult is képes a memóriában tartani. A Turbo Pascal 5.5-ös verziójától kezdve az overlay manager az overlay rutinok hívási gyakorisága alapján dönt arról, hogy a puffer betelte után melyik modult törölje. Az overlay modulok lemezről való betöltés elég időigényes, ezért a Turbo Pascal támogatja az expanded memória (EMS) felhasználását az overlay file tárolására, ahonnan a modulok betöltése sokkal gyorsabban elvégezhető. Az overlay file (.OVR) egyszerű másolással hozzákapcsolható az .EXE file-hoz a: COPY /B PROGRAM.EXE + PROGRAM.OVR DOS parancs alkalmazásával. Ebben az esetben a programot a {$D-,L-} kapcsolókkal ajánlott lefordítani. Az overlay rendszer működéséhez az OVERLAY szabványos modul szükséges. A unit-ban található nagy számú konstans, változó, eljárás és függvény ismertetését a függelék tartalmazza. Tekintsük át az overlay program kialakításához szükséges lépéseket: 1. Első lépésben el kell különítenünk a főprogramot és az overlay modulok tartalmát. Érdemes az egyes részeket úgy összeállítani, hogy minimális mennyiségű töltögetésre legyen szükség. Meg kell jegyeznünk, hogy overlay modul nem tartalmazhat megszakítást kezelő rutint (interrupt). 2. A főprogram elején a {$O modulnév} fordítási direktíva felhasználásával fel kell sorolnunk az overlay modulokat, a uses parancsban pedig meg kell adnunk az overlay nevű modult is: Program Main; uses DOS,Overlay, UnitA, UnitB; {$O DOS} {$O UnitA} {$O UnitB} A szabványos modulok közül a DOS unit használható egyedül mint overlay modul. 3. A főprogram és minden overlay modul elején a {$F+,O+} fordítási direktívákat kell elhelyeznünk. 4. Végezetül az overlay rendszer inicializálását az OvrInit(overlay_file_neve); eljárás hívásával kell elvégeznünk. Ha az overlay modulok egyike sem tartalmaz inicializációs rész, akkor ezt a hívást általában a főprogramból hajtjuk végre. Ellenkező esetben azonban a második példánkban bemutatott megoldást kell használnunk, ahol az OvrInit eljárás hívása az OvrStart modulból történik. (A főprogram a uses sorában az overlay modulok neve előtt kell megadnunk ezt a modult.) Az EMS memória használata az OvrInit hívását követő OvrInitEMS hívással kérhető. Ajánlott minden, az OVERLAY szabványos unit-ban definiált, eljárás hívása után a OvrResult változó tesztelésével meggyőződnünk a művelet eredményességéről. Az alábbiakban egy rövid példa felhasználásával mutatjuk be az overlay-es programszerkezet kialakításához szükséges lépéseket: { O_MAIN.PAS } Program Overlay_demo; {$F+,O+} uses overlay,o_unita,o_unitb; {$O o_unita} {$O o_unitb} begin OvrInit('O_main.ovr'); ProcA; end. {O_UNITA.PAS} unit o_unita; {$F+,O+} interface uses O_UnitB; procedure proca; implementation procedure proca; const st='Procedure'; begin writeln(st,' A'); procb(st); end; end. {O_UNITB.PAS} unit o_unitb; {$F+,O+} interface procedure procb(s:string); implementation procedure procb; begin writeln(s,' B'); end; end. A második példánk inicializációs részt tartalmazó overlay modulokból álló program ajánlott felépítését mutatja be: {OVR_1.PAS} unit ovr_1; {$O+,F+} interface procedure msg1; implementation var s:string; procedure msg1; begin writeln(s); end; begin s := '1. üzenet'; end. {OVR_2.PAS} unit ovr_2; {$O+,F+} interface procedure msg2; implementation var s:string; procedure msg2; begin writeln(s); end; begin s := '2. üzenet'; end. {OVRSTART.PAS} unit ovrstart; interface uses overlay; implementation var ovrhiba:boolean; ovrhstr:string; begin ovrinit('ovrtest.ovr'); ovrhiba:=true; case ovrresult of ovrerror : ovrhstr:='Definiálatlan hiba'; ovrnotfound:ovrhstr:= 'Az overlay file nem található'; ovrnomemory: ovrhstr:='Nincs elég memória'; ovrIOerror :ovrhstr:= 'Az overlay file olvasási hiba'; ovrnoEMSdriver:ovrhstr:= 'Az EMS driver nincs betöltve'; ovrnoEMSmemory: ovrhstr:= 'Az EMS emória mérete kicsi'; else ovrhiba:=false; end; if ovrhiba then begin writeln('OVR Hiba: ',ovrhstr); halt; end; end. {OVRTEST.PAS} program ovrtest; {$F+,O+} uses overlay, ovrstart, ovr_1, ovr_2; {$O ovr_1} {$O ovr_2} begin msg1; msg2; end. 16.2. Rendszerhívások Az IBM PC számítógépek rendszerprogramja alapvetően két részből tevődik össze. A perifériák vezérlését végző BIOS (Basic I/O System) programból, és az erre ráépülő operációs rendszerből, például az MS- DOS. A Turbo Pascal lehetőségeket biztosít a programozó számára, hogy a rendszerprogram bizonyos rutinjait (megszakítások - interrupt) közvetlenül meghívja. Maximálisan 256 ilyen rutin érhető el, amelyek belépési pontja (címe) a memória első 1 kbyte-jában került tárolásra, 4 byte-os elemeket tartalmazó tömbben. Ezek a megszakítások közvetlenül nem hívhatók, mivel a paraméterezésükhöz a processzor regisztereit kell használnunk. A DOS szabványos modul felhasználásával ezek a megszakítások elérhetők. A unit-ban a Registers nevű struktúra lefedi a processzor regisztereinek egy részét: type Registers = record case Integer of 0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags: Word); 1: (AL,AH,BL,BH,CL,CH,DL,DH : Byte); end; Registers típusú változó felhasználásával paraméterezhetjük a rendszerhí-vásokat, illetve ebben kapjuk vissza bizonyos szoftveres megszakítások eredményét. A 256 különböző megszakítás közül tetszőleges aktivizálható az intr(intno:byte;var regs:registers); rutin segítségével, ahol az intno paraméterben a megszakítás rutin indexét kell megadni. A $21 indexű megszakítás az operációs rendszer funkcióit tartalmazza (DOS funkciók), ezér ennek hívására külön rutint definiáltak: msdos(var regs:registers); (Ez a hívás ekvivalens a intr($21,regs); hívással) Ahhoz, hogy tudjunk ilyen megoldásokat használni, rendelkeznünk kell a rendszerhívásokat tartalmazó leírással, amely alapján a paraméterezés egyszerűen elvégezhető. Azok a megszakítások, amelyek a Registers struktúrában nem közölt regiszteren keresztül paraméterezhetők, ily módon nem érhető el. Az alábbi példában a Kursor eljárással a $10-s BIOS megszakítást aktivizálva a szövegkurzor kikapcsolható vagy bekapcsolható. A Verzio eljárás pedig a DOS funkciók közül a $30-ast használja a DOS verziójának lekérdezésére. program intr_dos; uses crt,dos; const ki=false; be=true; { Szövegkurzor ki- /bekapcsolása} procedure kurzor(ki_be:boolean); { ki_be: TRUE - bekapcsolás FALSE - kikapcsolás } const cur: word=$0607; var r : registers; begin case ki_be of false : { kikapcsolás } begin with r do begin {a kurzoralak lekérdezése} ah:=3; bh:=0; intr($10,r); cur:=cx; {a kurzoralak állítása} ah:=1; cx:=$2000; intr($10,r); end; end; true : { bekapcsolás } begin {a kurzoralak állítása} r.ah:=1; r.cx:=cur; intr($10,r); end; end; end; {DOS verziószán kiírása} procedure dosver; var r:registers; begin r.ah:=$30; MsDos(r); writeln('Az operációs rendszer verziószáma: ', r.al, '.', r.ah); end; begin clrscr; dosver; gotoxy(10,10); kurzor(ki); write('Hol a kurzor? '); delay(2000); gotoxy(50,20); kurzor(be); write('Ihol ni…'); delay(2000); end. 16.3. Megszakítási rutinok készítése - TSR programok Az előző részben azt néztük meg, hogyan lehet rendszermegszakításokat aktívizálni Turbo Pascal programból. Most azonban a másik oldalról közelítjük meg a kérdést, nevezetesen, hogyan tudunk megszakítási (interrupt) rutint írni Turbo Pascal nyelven. A Turbo Pascal támogatja az ún. interrupt rutin írását az alábbi struktúrával: {$F+} procedure azonosito [( Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP : Word )]; interrupt; {$F-} begin . . end; Az interrupt eljárást mindig távoli (far) hívási konvencióval kell definiálni ({$F+}). A paramétersort, vagy annak egy részét akkor kell használni, ha a rutinunk paramétereket fogad regiszterekben, illetve értéket ad vissza, szintén regiszterekben. Az interrupt rutinunk csak az Intr hívással illetve a rendszer által aktívizálható. Ehhez azonban a rutinunk címét el kell helyezni a megszakítások táblázatában. A DOS unit-ban találunk eljárásokat ezen lépés elvégzésére: a SetIntVec(IntNo,Vector); rutin a pointer típusú Vector értéket a megszakítástábla IntNo-adik indexű elemébe másolja. Sok esetben az átírás előtt szükséges az ott tárolt címet elmenteni. A mentést a GetIntVec(Intno,Vector); hívással végezhetjük el, ahol a címet a Vector pointer típusú változóban kapjuk vissza. Az interrupt rutinok használatának három esetét példaprogramokkal ismertetjük. 1. Példa paraméterezett interrupt rutin készítésére. A példában a my_it nevű eljárás az AX és BX regisztereket bemenő, míg a CX és DX regisztereket kimenő paraméterként használja. program it_pld; uses crt,dos; const it = $fd; {$F+} procedure my_it(AX,BX,CX,DX,SI,DI,DS,ES,BP:word); interrupt; begin writeln('my_it: AX=',ax:5); writeln('my_it: BX=',bx:5); CX:=6496; DX:=2308; end; {$F-} var r : registers; pit : pointer; begin getintvec(it,pit); setintvec(it,@my_it); with r do begin ax:=6512; bx:=294; end; intr(it,r); writeln('main : CX=',r.cx:5); writeln('main : DX=',r.dx:5); setintvec(it,pit); end. 2. A megszakítások egy része nem hívható az Intr eljárás segítségével. Ezeket mindig valamely hardver eszköz kérelmére aktívizálja a processzor (hardver interrupt). Nagyon fontos megjegyeznünk, hogy hardver interrupt rutinból semmilyen DOS művelet sem használható, mivel a DOS egyprogramos operációs rendszer. Ha alap I/O műveleteket kívánunk elvégezni, akkor a CRT unit rutinjai gond nélkül használhatók. A következő példában a $1C, az ún. felhasználói timer megszakítást definiáljuk át saját céljainkra. Ha a program nem fut végig, akkor az első példában definiált módon (a setintvec rutin segítségével) a régi interrupt rutin címét nem tudjuk visszaírni, így a számítógépünk "lefagy". Hogy lehet ezt a kellemetlen jelenséget kiküszöbölni? A System unit lehetőséget biztosít ún. saját kilépési eljárás definiálására, az exitproc nevű globális mutató felülírásával. Az exitproc pointerben tárolt rutin meg fog hívódni, ha a program valamilyen futás közbeni hiba miatt (run-time error) megáll, vagy egyszerűen akkor, ha a program véget ér. A rutinon belül az exitcode és erroraddr globális változókból lekérdezhető a hiba kódja illetve a hiba helyének címe a programban. Az erroraddr nil értékkel való törlésével letiltjuk a Turbo Pascal rendszer saját hibaüzenetének kiírását. Ezen rövid gondolatmenet után tekintsük magát a programot. program timer; uses crt, dos; const timerit = $1c; maxtime = 18; var exitsave : pointer; oldvect : pointer; lefutas : integer; key : char; {Az átdefiniált kilépési eljárás} {$f+} procedure exit_timer; {$f-} begin if (exitcode<>0) or (erroraddr<>nil) then begin writeln('hiba történt: ',exitcode); meml[0:4*timerit]:=longint(oldvect); port[$20]:=$20; end else writeln('normál kilépés.'); erroraddr:=nil; exitproc := exitsave; end; {A timer megszakítás rutin egy számlót dekrementál} procedure newtimer; interrupt; begin if( lefutas > 0 ) then lefutas := lefutas - 1; end; begin {Saját kilépési eljárás aktivizálása} exitsave:=exitproc; exitproc:=@exit_timer; getintvec( timerit, oldvect ); lefutas := maxtime; setintvec( timerit, @newtimer); while (keypressed = false) do { Törölte a számlálót a megszakítás?} if ( lefutas = 0 ) then begin writeln(#247, '1 mp. eltelt'); lefutas := maxtime; end; key := readkey; setintvec( timerit, oldvect); end. 3. Harmadik példánk ún. "Terminate and Stay Resident" TSR program készítését mutatja be. Ha a főprogramból a Keep(kilepesi_kod); hívással lépünk ki, akkor a programunk a kilépés után is bent marad a memóriában. A program a különböző billentyűk lenyomásának hatására különböző hangot ad. A működés a CTRL-ESC billentyűkkel vezérelhető. Meg kell jegyeznünk, hogy a TSR programok memóriából való kitörlés nem egyszerű feladat, amelynek tárgyalása meghaladja a könyvünk célkitűzését. {$M $800,0,0 } { 2K stack, nincs heap } program tsr_pld; uses Crt, Dos; var oldit_09 : Procedure; sc : byte; on_off : boolean; al,ah : byte; {Inline eljárás - a PUSHF gépikódú utasítás} procedure PUSHF; inline ($9C); { A új megszakítást kezelő rutin } {$F+} procedure newit_09; interrupt; begin { A scan kód beolvasása } sc:=port[$60]; if sc=1 then begin if (mem[0:$417] and $04)<>0 then {A CTRL-ESC kombináció hatására ki-/bekapcsol} begin on_off:= not on_off; {A billentyűzet vezérlő programozása} al:=port[$61]; ah:=al or $80; port[$61]:=ah; port[$61]:=al; {A megszakítás vezérlő programozása} port[$20]:=$20; exit; end; end; {Ha be van kapcsolva és lenyomtak billenyűt} if on_off and (sc<$80) then begin {Hanggenerálás} sound( Abs(880 + 60 * sc)) ; Delay(10); Nosound; end; { A régi megszakítás rutin hívása } PUSHF; oldit_09; end; {$F-} begin on_off:=false; {A régi belépési pont lekérdezése} GetIntVec($9,@oldit_09); {A IT-cím saját területre állítása} SetIntVec($9,Addr(newit_09)); {TSR - rezidens kilépés a programból } Keep(0); end. 16.4. A DOS unit file-kezelési lehetőségei A 11. fejezetben ismertetett file-kezelést az alaprendszer (SYSTEM unit) tartalmazza. A DOS unit alprogramok sorával fedi le az operációs rendszer file-ok kezelésére szolgáló funkcióit. A megfelelő rutinok részletes leírását a függelék tartalmazza, itt csak egy rövid ismertetést adunk róluk: - A DOS idő és dátum kezelése: GetDate, GetFTime, Gettime, PackTime, Setdate, SetFTime, SetTime, UnpackTime. - A lemezegység teljes méretének és szabad helyének lekérdezése: DiskFree, DiskSize - File-ok feldolgozása: FindFirst, FindNext, FSplit, FExpand, FSearch, - File-attribútumok lekérdezése és állítása: GetFAttr, SetFAttr. - A rutinok többsége a DOS unit doserror nevű globális változójában ad információt az adott művelet sikerességéről. A DIRTREE.PAS program a fenti funkciókat felhasználva, parancssorban megadott könyvtár adott file-jait keresi. A megtalált file-ok esetén kijelzi azok attribútumát. A program érdekessége, hogy a könyvtár fastruktú-rájának bejárását rekurzív eljárással (keres) oldottuk meg. program dirtree; uses Crt,Dos; const ch:char=#32; {A file-attribútumokat kiíró rutin} procedure writeattr(fn:string); const attrstr:string[6]='RHSVDA'; var fp : file; i : integer; as : string[7]; attr: word; begin assign(fp,fn); getfattr(fp,attr); as:='['; for i:=1 to 6 do if (attr and (1 shl (i-1)))<>0 then as:=as+attrstr[i] else as:=as+'.'; write(as,']'); end; {A file ellenőrzése az adott könyvtárban} procedure checkfile(fname:string); var dirinfo: Searchrec; begin FindFirst(fname, Anyfile, DirInfo); while DosError = 0 do begin if dirinfo.name[1]<>'.' then begin Write('.....',DirInfo.Name); gotoxy(30,wherey); writeattr(dirinfo.name); end; writeln; FindNext(DirInfo); end; end; {A teljes könyvtár bejárását végző rekurzív eljárás } procedure keres(dir,fname:string); var DirInfo: SearchRec; s1:string; begin if keypressed then ch:=readkey; if ch=#27 then exit; getdir(0,s1); {$I-} chdir(dir); {$I+} if ioresult<>0 then begin writeln('Hibás directory: ',dir); exit; end; writeln(dir); checkfile(fname); FindFirst('*.*', Anyfile,DirInfo); while DosError = 0 do begin if dirinfo.name[1]<>'.' then begin {$I-} chdir(dirinfo.name); {$I+} if ioresult=0 then begin if dir[ord(dir[0])]='\' then dec(dir[0]); chdir('..'); keres(dir+'\'+DirInfo.Name,fname); end; end; FindNext(DirInfo); end; chdir(s1); end; { A főprogram } begin assign(output,''); rewrite(output); if paramcount<2 then begin writeln; writeln('Használat: DIRTREE dir file'); writeln; exit; end else keres(paramstr(1),paramstr(2)); end. 16.5 Programok indítása A DOS unit-ban definiált Exec eljárás tetszőleges program paraméterezett indítását teszi lehetővé. Mivel minden Turbo Pascal program néhány megszakításvektort automatikusan átdefiniál, az Exec rutin hivása előtt ezeket a vektorokat vissza kell állítáni, majd a hívás után újra át kell definiálni. A megszakítási vektorok cseréjére a SwapVectors eljárás használható. Nem szabad megfeledkeznünk arról, hogy a hívott program futásához is memória szükséges, ezért a {$M} kapcsoló megfelelő megadása elengedhetetlen az Exec használatához. Az EXECPGM.PAS program a futtatandó program teljes nevének és a paramétersorának bekérése után megpróbálja azt elindítani. A probálkozás után végzett hibakezeléssel informál bennünket a futás eredményességéről. A DOS unit a doserror globális változóban közli a fellépő hiba okát. program execpgm; {$M 10000,0,0} uses dos; var command_line : string; program_path : string; key : char; begin writeln('DOS belső parncsok végrehajtása:'); writeln(' programnév : ''c:\command.com'''); writeln(' argumentumok: ''/c '''); repeat write( 'A végrehajtandó program: ' ); readln( program_path ); write( 'A program paraméterei : ' ); readln( command_line ); writeln('A ',program_path, ' program indítása...' ); { A program indítása } swapvectors; exec(program_path, command_line ); swapvectors; writeln('... kilépés.'); if doserror <> 0 then {hiba volt?} writeln('dos hiba #', doserror) else writeln('sikeres futtatás. ', 'a child process kilépési kódja = ', dosexitcode); writeln; write('Akar más parancsot végrehajtani (i/n) ? ' ); readln( key ); until upcase( key ) <> 'i'; end.