plik


ÿþV. Kod wynikowy 83 V. Kod wynikowy Niniejszy rozdziaB ró|ni si troch od reszty ksi|ki. Rozwa|ania w nim zawarte "le| na ni|szym poziomie abstrakcji", nie dotycz bowiem skBadni ani bibliotek jzyka C lecz procesu powstawania programu wynikowego i sposobów ingerencji w ten proces. Podczas gdy w pozostaBych rozdziaBach staraBem si opisywa jzyk C w sposób niezale|ny zarówno od kompilatora jak i sprztu, ten rozdziaB bdzie dotyczyB tylko komputerów kompatybilnych z IBM PC, pracujcych pod nadzo- rem systemu DOS. Ka|dy, kto zetknB si z jzykiem C, zastanawiaB si zapewne, dlaczego naj- prostszy program: main() {} daje tak du|y kod wynikowy. Otó| dzieje si tak nie dlatego, |e kompilatory C daj bardzo nieoptymalny kod, tylko dlatego, |e bardzo powa|nie traktuj ka|dy program. W wyniku takiego powa|nego podej[cia, do ka|dego programu doBcza- ny jest spory fragment kodu (w dalszej cz[ci rozdziaBu bd okre[laB go mianem Startup), który po pierwsze przygotowuje pewne dane dla programu gBównego a po drugie robi wszystko co mo|liwe, |eby zabezpieczy system przed zmianami jakie mo|e poczyni program. Startup zachowuje szereg informacji o stanie syste- mu w chwili uruchomienia programu: adres zmiennych [rodowiskowych, wersj DOS-u, wektory niektórych przerwaD, itp. Kolejn wykonywan czynno[ci jest przygotowanie pewnych danych i funkcji wykorzystywanych przez program gBówny. Instalowana jest na przykBad standardowa funkcja obsBugi bBdu dzielenia przez zero, funkcje arytmetyki zmiennoprzecinkowej, zapamitywana jest warto[ zegara BIOS-u w celu ewentualnego u|ycia przez funkcj clock. Jedn z wa|niejszych funkcji wykonywanych przez Startup jest przygotowanie argumentów dla funkcji main. Funkcja ta mo|e mie trzy argumenty: main(int argc, char *argv[], char *envp[]) Oczywi[cie, nie ka|dy program musi u|ywa argumentów, a nie ka|dy, który ich u|ywa, musi u|ywa wszystkich. Poprawne s równie| nastpujce nagBówki funkcji main: main() main(int argc) main(int argc,char *argv[]) W pierwszym argumencie, oznaczonym tutaj argc, Startup przekazuje do progra- mu gBównego liczb argumentów w wierszu wywoBania programu plus jeden. Drugi argument, oznaczony argv, jest wskaznikiem do tablicy wskazników wska- zujcych kolejne argumenty wywoBania. Na przykBad, je|eli program zostaB wy- woBany w nastpujcy sposób: 84 WgBb jzyka C program -u book a:\*.c b:\*.c to argumenty funkcji main bd miaBy nastpujce warto[ci: argc=5 argv[0]="program" /* DOS 3.0 i wyzsze */ argv[1]="-u" argv[2]="book" argv[3]="a:\*.c" argv[4]="b:\*.c" Ostatni argument funkcji main, oznaczony envp, jest wskaznikiem do tablicy wskazników do zmiennych [rodowiskowych. Ostatnim elementem tablicy jest wskazanie puste NULL. Poni|szy program u|ywa trzeciego argumentu funkcji main, do wypisywania warto[ci zmiennych [rodowiskowych w chwili uruchomienia programu. #include <stdio.h> void main(int c, char *v[], char *env[]) { while(*env) printf("%s\n",*env++); } Po wykonaniu wszystkich opisanych czynno[ci, Startup wywoBuje program gBów- ny, czyli funkcj main. Po powrocie z funkcji main, czyli po zakoDczeniu dzia- Bania programu, Startup odtwarza zapamitane przed wywoBaniem programu informacje (na przykBad wektory przerwaD) i wraca do DOS-u. 1.Zmieniamy Startup Czsto pisze si proste programu, nie wymagajce zachowywania wektorów prze- rwaD, instalowania procedur obsBugi bBdów i wykonywania wszystkich tych ope- racji, które robi Startup. Chcieliby[my, |eby takie programy byBy krótkie a mog one by krótkie, a nawet bardzo krótkie. {eby tak si staBo, trzeba pozby si zbdnego kodu, a wic trzeba usun oryginalny Startup. Spróbujmy stworzy wBasn wersj Startup-u, która bdzie ograniczaBa si jedynie do wywoBania funkcji main, umo|liwiaBa zrobienie z prostego programu maBego COM-a. W tym miejscu trzeba kilka sBów po[wici sposobowi w jaki kompilator (i konsolidator) doBczaj Startup do programu. W przypadku kompilatorów firmy Borland jest to realizowane w sposób bardzo prosty i przejrzysty. Kod Startup zawarty jest w osobnych plikach o nazwach c0?.obj (? symbolizuje tu liter oznaczajc model pamici np. s - model small). Dla ka|dego modelu pamici istnieje osobny kod Startup i osobny moduB c0?.obj. V. Kod wynikowy 85 Konsolidacja programu w [rodowiskach firmy Borland wyglda nastpujco: tlink c0s.obj program ... , program , , cs.lib (kropki oznaczaj ewentualne kolejne moduBy programu). Dziki takiemu rozwizaniu zastpienie Startup-a sprowadza si do zastpienia moduBu c0s.obj z powy|szego przykBadu innym, np.: tlink tsr16.obj program ... , program , , cs.lib W [rodowisku Microsoft C nie ma oddzielnych moduBów Startup. Kod startowy "zaszyty" jest w bibliotekach i automatycznie doBczany do ka|dego programu Bczonego z bibliotek. Aby standardowy kod Startup nie zostaB doBczony do programu i mógB by zastpiony innym, w programie nale|y zdefiniowa zmien- n o nazwie _acrtused (dlatego we wszystkich programach przedstawionych w tym rozdziale taka zmienna jest zdefiniowana): int _acrtused=0; i dokona konsolidacji programu z opcj /NOE, np. tak: link /NOE tsr16.obj program ... , program , , slibce.lib Wszystkie opisane w tym rozdziale przykBady kodów startowych sBu| do otrzymywania programów typu COM. Punkt wej[cia programu typu COM musi mie przemieszczenie 100h. Nasz pierwszy, najprostszy Startup od razu wywoBa funkcj _main, a po powrocie z tej funkcji wróci do DOS-u. Przypominam, |e identyfikatory globalne w jzyku C s automatycznie poprzedzane podkre[leniem. Dlatego funkcja main nazywa si w rzeczywisto[ci _main. ; plik c0.asm .MODEL SMALL extrn _main:near .CODE ORG 100h start: call _main ; wywolaj funkcje main mov ah,4Ch int 21h ; wróc do DOS-a end start Prosz zwróci uwag, |e przed powrotem do DOS-u ustawiamy tylko zawarto[ rejestru AH, natomiast rejestr AL bdzie zawieraB warto[ zwrócon przez funkcj main. Ta wBa[nie bdzie zwrócona przez program. Po asemblacji pliku c0.asm otrzymamy moduB c0.obj. Mo|emy teraz napisa pierwszy program, w którym oryginalny Startup zastpi- my nasz minimaln wersj. Program bdzie zamieniaB ze sob porty drukarki, a wic po jego wykonaniu port pierwszy stanie si drugim i vice versa. Progra- 86 WgBb jzyka C mik taki jest przydatny np. gdy mamy uszkodzony port LPT1 i chcemy "podsta- wi" go portem LPT2. /* plik lptport.c */ int _acrtused=0; void main() /* program wymienia adresy portów LPT1 i LPT2 */ { int x; x=*(int far *)0x408; /* 40h:08h adres LPT1 */ *(int far *)0x408=*(int far *)0x40A; /* 40h:0Ah adres LPT2 */ *(int far *)0x40A=x; } Program zamienia ze sob adresy portów drukarki umieszczone w obszarze da- nych BIOS-u. ZaBó|my, |e program znajduje si w pliku lptport.c. Po skompi- lowaniu w modelu Small1) otrzymamy moduB lptport.obj. Mo|emy teraz utworzy program wykonywalny: tlink /t c0.obj lptport.obj , lptport.com lub dla Microsoft C link /NOE c0.obj lptport.obj , lptport , , slibce exe2bin lptport.exe W bie|cym katalogu zostanie utworzony program LPTPORT.COM . Jego dBu- go[ w zale|no[ci od kompilatora i ustawionych opcji wyniesie okoBo 50 bajtów! Wprawdzie nie robi on wiele, ale ten sam program skompilowany "standardowo" zajmuje przecie| prawie 4 tysice bajtów. Nastpny programik bdzie sBu|yB do wyboru opcji w programach wsadowych (typu .bat). Bdzie on pobieraB z klawiatury odpowiedz na zadane pytanie i zwracaB odpowiedni warto[. Warto[ zwracan przez program mo|na w programach wsadowych testowa przy pomocy warunku: if ERRORLEVEL liczba Warunek ten jest speBniony, gdy warto[ zwrócona przez ostatnio wywoBany program jest równa lub wiksza od warto[ci liczba. Poniewa| funkcje obsBugi wej[cia w standardowych bibliotekach C s do[ ob- szerne, bdziemy czyta klawiatur bezpo[rednio przy pomocy funkcji BIOS-u. Odpowiedni do naszego programu bdzie funkcja 0 przerwania 16h. Funkcj 1 Wszystkie programy nale|y kompilowa bez informacji dla debuggera, a w kompila- torach firmy Microsoft z opcj /Gs V. Kod wynikowy 87 _getch czytajc znak z klawiatury zdefiniujemy w osobnym pliku getch.asm, gdy| bdziemy jej u|ywa tak|e w innych programach2). ; plik getch.asm .MODEL SMALL .CODE proc __getch ; funkcja pobiera znak z bufora klawiatury mov ah,0 ; i zwraca jako rezultat int 16h ; je|eli bufor jest pusty czeka na znak ret endp public __getch end Wykorzystana funkcja 0 przerwania 16h czeka na naci[nicie klawisza i zwraca w rejestrze AX kod naci[nitego klawisza. Warto[ci tej nie trzeba nigdzie przepi- sywa, poniewa| program w jzyku C spodziewa si przekazania warto[ci funkcji typu int wBa[nie w rejestrze AX. Program ma pobiera odpowiedz na pytanie. ZaBó|my, |e po naci[niciu klawi- sza T (odpowiedz twierdzca) program bdzie zwracaB warto[ 116 (kod znaku 't') a w przeciwnym wypadku warto[ 0. /* plik tak_nie.c */ int _acrtused=0; main() { char c=_getch(); /* pobierz znak z klawiatury */ if(c=='t'||c=='T') return 't'; /* jezeli 't' lub 'T' */ else return 0; /* w przeciwnym razie */ } Teraz kompilujemy nasz program w modelu Small i tworzymy program wyko- nywalny. tlink /t c0.obj getch.obj tak_nie.obj , tak_nie lub link /NOE c0.obj getch tak_nie , tak_nie , , slibce exe2bin tak_nie.exe Programik ma tylko 50 bajtów dBugo[ci. Mo|na go wykorzysta w programach wsadowych w nastpujcy sposób: 2 ModuBy asemblerowe, w których definiowane s symbole u|ywane w programie w C nale|y kompilowa programem TASM z opcj /ml 88 WgBb jzyka C @echo "Zainstalowa cache-a (t/n) ? " @tak_nie @if ERRORLEVEL 116 SUPERPCK /EP /T+ Po uruchomieniu powy|szego programu na ekranie zostanie wy[wietlone pytanie: Zainstalowa cache-a (t/n) ? Je|eli u|ytkownik naci[nie klawisz T, zostanie uruchomiony program SUPERPCK, w przeciwnym wypadku uruchomienie programu nie nastpi. 2.Programy rezydentne W rozdziale tym zaprezentuj "zastpczy" Startup, który nie bdzie, jak po- przednio, uproszczeniem Startup-a oryginalnego, lecz bdzie peBniB caBkowicie odmienn funkcj. Zamiast uruchamia program gBówny (funkcj main), bdzie on instalowaB go jako program rezydentny, przechwytujcy wybrane przerwanie. W ten sposób zamienienie zwykBego programu na program rezydentny bdzie bar- dzo proste i bdzie sprowadza si do jego powtórnej konsolidacji. Zanim przejd do meritum, chciaBbym przypomnie po krótce czym s programy rezydentne, do czego sBu| i jak si je tworzy. Programy rezydentne to programy, które po zakoDczeniu dziaBania pozostawiaj cz[ swojego kodu w pamici i ustawiaj wektor wybranego przerwania tak, |eby wskazywaB na ten kod. Po wygenerowaniu tego przerwania nastpuje uru- chomienie rezydujcej w pamici cz[ci programu. 2.1. Jakie s zastosowania programów rezydentnych ? Program rezydentny mo|e by rozszerzeniem systemu operacyjnego, dostarczajcym innym programom usBug, których nie zapewnia system. PrzykBadem takiego progra- mu jest sterownik myszki. Programy, które chc skorzysta z myszki (np. odczyta poBo|enie kursora myszki, stan klawiszy) wywoBuj przy pomocy odpowiedniego przerwania rezydentny program sterownika myszki i przekazuj mu w rejestrach procesora informacj o jak usBug prosz. Innym zastosowaniem programów rezydentnych jest zmiana obsBugi standardo- wego przerwania. Program taki mo|e na przykBad przej przerwanie klawiatury w celu niestandardowej interpretacji niektórych klawiszy (np. ALT+A jako ). Przy pomocy programów rezydentnych realizuje si tak|e w systemie DOS na- miastk procesów dziaBajcych w tle. Program przejmuje w tym celu jakie[ cz- sto wywoBywane przez system przerwanie (np. przerwanie zegara 1Ch lub przerwanie obsBugi klawiatury 16h), dziki czemu jest odpowiednio czsto wy- woBywany. Takie "procesy" mog albo wykonywa w tle jak prost prac albo tylko czuwa i ujawnia si dopiero po zaistnieniu okre[lonego warunku (upBy- wie okre[lonego czasu, naci[niciu jakiego[ klawisza itp.). V. Kod wynikowy 89 2.2. Tworzenie programów rezydentnych Cz[ rezydujca programu jest wywoBywana jako procedura obsBugi przerwa- nia. Przerwanie, a w szczególno[ci przerwanie sprztowe, mo|e pojawi si praktycznie w dowolnym momencie, a wic w dowolnym momencie mo|e prze- rwa dziaBanie innego programu. Aby po powrocie z procedury obsBugi przerwa- nia (programu rezydentnego) przerwany program mógB wznowi poprawnie dziaBanie, stan procesora musi pozosta niezmieniony. Dlatego te| ka|dy pro- gram rezydentny powinien zachowywa zawarto[ wszystkich rejestrów proce- sora. Ponadto, je|eli program rezydentny korzysta z usBug systemu DOS (za pomoc przerwanie 21h), musi sprawdza, czy w momencie jego wywoBania nie byBa wykonywana jak[ funkcja DOS-u. Wynika to z faktu, |e do funkcji DOS-u nie mo|na wchodzi dwukrotnie (DOS nie jest wielobie|ny ( ang. reentrant)). KBo- potu tego mo|na unikn albo przez nieu|ywanie usBug systemowych w programie rezydentnym, albo wykorzystujc przerwanie, które jest wywoBy- wane tylko wtedy, gdy aktywacja programu rezydentnego jest "bezpieczna" (ide- alne jest tu przerwanie BIOS-u 16h). Przerwania, przejmowane przez programy rezydentne bardzo czsto peBni wa|- n funkcj w systemie. Je|eli tak jest, program rezydentny powinien równie| wywoBywa oryginaln (tzn. zastan w momencie instalacji) procedur obsBugi przerwania. 2.3. Funkcja main jako program rezydentny Funkcja main nie zachowuje oczywi[cie zawarto[ci rejestrów procesora. Dlatego wektor przerwania nie mo|e wskazywa bezpo[rednio na ni, lecz na fragment ko- du, który zachowa rejestry na stosie, wywoBa funkcj _main, a po jej zakoDczeniu odtworzy zawarto[ rejestrów ze stosu. Kod ten powinien tak|e zapewnia wyko- nanie oryginalnej procedury obsBugi przerwania. {eby zabezpieczy si przed zaptleniem wywoBaD programu rezydentnego (wywoBaniem programu w trakcie jego dziaBania), powinien on posiada jaki[ wskaznik aktywno[ci. Przed aktywacj program sprawdzaBby czy wskaznik jest wyzerowany i tylko w takim przypadku uaktywniaBby si ustawiajc jednocze- [nie wskaznik na 1. Nale|y jeszcze zastanowi si nad zawarto[ci rejestrów segmentowych w chwili wywoBania programu rezydujcego w pamici. Poniewa| obsBuga prze- rwania przez procesor polega na wykonaniu dalekiego wywoBania procedury, rejestr segmentowy kodu CS bdzie wskazywaB segment, w którym znajduje si program rezydentny. Rejestry segmentowe danych DS i ES, bd oczywi[cie wskazywa na segment(-y) danych programu przerwanego. ZakBadamy, |e pro- gram w C bdziemy kompilowa w modelu Small (tworzenie rezydentów wik- szych ni| 64K nie ma sensu). W takim przypadku dane znajduj si w tym 90 WgBb jzyka C samym segmencie co kod a wic do rejestrów DS oraz ES nale|y przed wywoBa- niem funkcji _main przepisa zawarto[ rejestru CS. Poni|ej przedstawiam fragment kodu, realizujcy wszystkie opisane powy|ej dziaBania. Interrupt16 proc far cli ; zablokuj przerwania dopóki wskaznik ; aktywno[ci nie jest ustawiony cmp cs:TSR, 1 ; czy program aktywny ? je end mov cs:TSR, 1 ; ustaw wskaznik aktywno[ci sti ; mo|na odblokowa przerwania push ax ; zachowaj rejestry na stosie push bx push cx push dx push di push si push ds push es push bp push cs ; inicjuj rejestry segmentowe danych pop ds push cs pop es call _main ; wywoBaj program gBówny pop bp ; odtwórz zawarto[ rejestrów pop es pop ds pop si pop di pop dx pop cx pop bx pop ax mov cs:TSR, 0 ; wyzeruj wskaznik aktywno[ci end: sti jmp cs:Original16 ; skocz do oryginalnej procedury endp ; obsBugi przerwania 16h OdwoBania do zmiennych (TSR i Original16) na pocztku i koDcu programu s prefiksowane rejestrem CS, gdy| w tych miejscach rejestr DS, u|ywany domy[lnie do adresowania pamici, wskazuje na segment danych programu przerwanego. Powy|szy kod bdzie jedn z cz[ci nowego Startup-a. Druga cz[ Startup-a musi zapamita zastany wektor przerwania 16h w zmiennej Original16, ustawi wektor tego przerwania tak, |eby wskazywaB na zdefiniowan powy|ej procedur Interrupt16, i powróci do DOS-u pozostawiajc program w pamici. V. Kod wynikowy 91 Do pozostawienia programu rezydujcego w pamici, u|yjemy funkcji DOS-u 31h (Keep). Funkcji tej nale|y poda w rejestrze DX liczb paragrafów 16- bajtowych które bdzie zajmowa program rezydujcy. Warto[ ta jest definio- wana przez programist w zmiennej globalnej size w programie w C. extrn _main:near extrn _size:word extrn _autor:near .model TINY .CODE org 100h ; offset kodu 100h gdy| ; program typu COM Start: ;--- zachowaj oryginalny wektor przerwania 16h mov ax, 3516h ;pobierz wektor 16h int 21h mov word ptr Original16, bx ;zachowaj offset wektora mov word ptr Original16+2, es ;zachowaj segment wektora ;--- zaBaduj nowy wektor mov ax, 2516h ;funkcja 25h i wektor 16h mov dx, offset Interrupt16 ;offset w dx, segment w ds int 21h ;--- terminate and stay resident mov ax, 3100h ;funkcja 31h kod powrotu 0 mov dx,_size ;ile paragrafów zostawi int 21h Mogliby[my ju| poBczy obie cz[ci i utworzy nowy Startup. Zanim jednak to zrobimy dodajmy jeszcze dwie proste i po|yteczne funkcje. Przed uruchomieniem ka|dego programu system operacyjny tworzy kopi [ro- dowiska. Kopia taka zostanie utworzona tak|e dla programu rezydentnego; co gorsza, pozostanie ona, zupeBnie niepotrzebnie, w pamici. Dopiszmy wic do naszego Startup-a |danie zwolnienia pamici zajtej przez kopi [rodowiska. Adres zawierajcego j segmentu umieszczony jest w sBowie przesunitym o 2Ch bajtów wzgldem pocztku prefiksu segmentu programu (PSP). mov bx,2Ch ;zwolnij pami zajmowan przez mov ax,[word ptr bx] ;kopi [rodowiska mov es,ax ;adres segmentu [rodowiska do ES mov ah,49h ;zwolnij segment pamici int 21h ;wskazywany przez ES Drugim zadaniem kodu startowego bdzie wypisanie wizytówki programu rezy- dentnego w czasie jego instalacji. ZaBó|my, |e bdzie to BaDcuch wskazywany przez globaln zmienn programu w C o nazwie autor. Do wypisania tego BaDcu- cha u|yjemy funkcji 09h DOS-u (funkcja ta oczekuje, |e BaDcuch bdzie zakoD- czony znakiem $). mov ah,09h 92 WgBb jzyka C mov dx,[word PTR _autor] ;wypisz BaDcuch int 21h Oto peBna wersja Startup-a instalujcego funkcj main jako program rezydentny przechwytujcy przerwanie 16h : ; plik TSR16.asm extrn _main:near extrn _size:word extrn _autor:near .model TINY .CODE org 100h Start: ;--- zachowaj oryginalny wektor przerwania 16h mov ax, 3516h ;funkcja 35h i wektor 16h int 21h mov word ptr Original16, bx ;zachowaj offset wektora mov word ptr Original16+2, es ;zachowaj segment wektora ;--- zaBaduj nowy wektor mov ax, 2516h ;funkcja 25h i wektor 16h mov dx, offset Interrupt16 ;offset w dx, segment w ds int 21h ;--- wypisz wizytówk mov ah,09h mov dx,[word PTR _autor] ;wypisz BaDcuch int 21h ;--- zwolnij pami [rodowiska mov bx,2Ch ;zwolnij pami zajmowan mov ax,[word ptr bx] ;przez kopi [rodowiska mov es,ax mov ah,49h ;funkcja zwolnij pami int 21h ;--- terminate and stay resident mov ax, 3100h ;funkcja 31h kod powrotu 0 mov dx,_size ;ile paragrafów zostawi int 21h Interrupt16 proc far cli ; zablokuj przerwania dopóki wskaznik ; aktywno[ci nie jest ustawiony cmp cs:TSR, 1 ; czy aktywny ? je end mov cs:TSR, 1 ; ustaw wskaznik aktywno[ci sti ; mo|na odblokowa przerwania push ax ; rejestry na stos push bx push cx push dx push di push si push ds V. Kod wynikowy 93 push es push bp push cs ; inicjuj rejestry segmentowe danych pop ds push cs pop es call _main ; wywoBaj program gBówny pop bp ; odtwórz zawarto[ rejestrów pop es pop ds pop si pop di pop dx pop cx pop bx pop ax mov cs:TSR, 0 ; zaznacz, |e nieaktywny end: sti jmp cs:Original16 ; skocz do oryginalnej procedury endp ; obsBugi przerwania 16h TSR label BYTE db 0 Original16 label Dword dw ? ;offset dw ? ;segment END Start W powy|szej wersji funkcja main zostaje "podpita" do przerwania 16h. Na dyskietce doBczonej do ksi|ki znajduj si tak|e wersje wykorzystujca przerwa- nie zegarowe 1Ch i przerwanie sprztowe klawiatury 09h. Wersja dla przerwania zegarowego jest dokBadnym odpowiednikiem wersji dla przerwania 16h. W przypadku przerwania klawiatury 09h, przed wywoBaniem funkcji main wy- woBywana jest oryginalna procedura obsBugi tego przerwania. Inaczej funkcja main nie mogBaby odczyta kodu klawisza, którego naci[nicie spowodowaBo jej uaktywnienie. Dodatkowo dyskietka zawiera wszystkie wy|ej wspominane programy rozbu- dowane o mo|liwo[ deinstalacji programu rezydentnego. Deinstalacja nastpuj po wywoBaniu programu z opcj /u lub /U i jest mo|liwa tylko wtedy, gdy po zainstalowaniu programu jegoprzerwanie nie zostaBo przejte (np. przez inny pro- gram rezydentny). {eby uBatwi korzystanie ze zdefiniowanych wy|ej moduBów przedstawiam po- ni|ej dwa programiki wsadowe tworzce automatycznie program rezydentny ko- rzystajcy z wybranego przerwania. REM plik TSRMS.BAT dla [rodowiska Microsoft C @if %1==1c goto 1c @if %1==16 goto 16 @if %1==9 goto 9 94 WgBb jzyka C @echo Syntax : tsr 1c|16|9 plik.obj [ pliki.obj ] @goto koniec :16 link /NOE tsr16 %2 %3 %4 %5 %6 %7 %8 %9, %2 , , slibce.lib @goto end :1c link /NOE tsr1c %2 %3 %4 %5 %6 %7 %8 %9, %2 , , slibce.lib @goto end :9 link /NOE tsr9 %2 %3 %4 %5 %6 %7 %8 %9, %2 , , slibce.lib @goto end :end exe2bin %2 :koniec REM program TSRTC.BAT dla [rodowiska Turbo C @if %1==1c goto 1c @if %1==16 goto 16 @if %1==9 goto 9 @echo Syntax : tsr 1c|16|9 plik.obj [ pliki.obj ] @goto end :16 tlink /t tsr16 %2 %3 %4 %5 %6 %7 %8 %9 , %2 , , cs.lib @goto end :1c tlink /t tsr1c %2 %3 %4 %5 %6 %7 %8 %9 , %2 , , cs.lib @goto end :9 tlink /t tsr9 %2 %3 %4 %5 %6 %7 %8 %9 , %2 , , cs.lib @goto end :end Nazwy plików nale|y uzupeBni ewentualnymi [cie|kami dostpu. PrzykBadowe wywoBanie powy|szych programów mo|e wyglda nastpujco (zakBadam ogóln nazw TSR zamiast TSRMS czy TSRTC): tsr 16 program 3.PrzykBadowe programy rezydentne U|ywajc zdefiniowanego powy|ej kodu Startup, programista nie musi martwi si o |adne kwestie wynikajce z faktu, |e program ma dziaBa jako rezydent. Program napisany w normalny sposób i uruchomiony przy pomocy debuggera, np. w [rodowisku zintegrowanym kompilatora, wystarczy jedynie ponownie skonsolidowa, np.: tlink /t tsr16.obj program , program W ten sposób powstanie program typu COM (u|yto opcji /t programu TLINK) o nazwie program.com. Po uruchomieniu tego programu na ekranie zostanie wypisany komunikat przypisany zdefiniowanej w programie zmiennej globalnej autor, a program zostanie zainstalowany w pamici jako program rezydentny wy- woBywany przerwaniem 16h. Od tego momentu ka|de wygenerowaniu przerwania 16h spowoduje wywoBanie funkcji main programu. V. Kod wynikowy 95 Tak wic teoretycznie pisanie programów rezydentnych nie ró|ni si niczym od pisania innych programów. W praktyce, programom takim stawia si troch inne wymagania. Przede wszystkim zajmuj one stale miejsce w pamici komputera, a wic po- winny by maBe. Z tego powodu niedopuszczalne jest u|ycie wielu funkcji z bibliotek standardowych, które nie byBy projektowane do zastosowania w programach rezydentnych i daj du|y kod wynikowy. Dotyczy to szczególnie funkcji wej[cia/wyj[cia, które trzeba zastpi wBasnymi. Prost funkcj wej[cio- wa zdefiniowali[my ju| we wcze[niejszej cz[ci tego rozdziaBu; byBa to funkcja _getch zwracajca kod znaku wprowadzonego z klawiatury. Funkcja ta w razie potrzeby czeka na naci[nicie klawisza. Poni|ej przedstawiam drug przydatn funkcj wej[cia. ; plik checkch.asm .model SMALL .CODE proc _checkch ; funkcja zwraca znak z bufora klawiatury mov ah,1 ; lub -1 jezeli bufor jest pusty int 16h ; nie usuwa znaku z bufora jnz koniec mov ax,-1 koniec: ret endp public _checkch end Funkcja checkch zwraca kod znaku znajdujcego si buforze klawiatury (nie usuwajc go z bufora) lub warto[ -1 gdy w buforze nie ma |adnego znaku. Jako mechanizm wyj[cia najlepiej u|ywa w programach rezydentach bezpo- [rednich odwoBaD do pamici ekranu. Aby program byB uniwersalny, powinien on samodzielnie ustala adres pamici ekranu w zale|no[ci od karty graficznej. Mo|na to zrobi umieszczajc na pocztku funkcji main nastpujc sekwencj: #define MK_FP(segment,offset) ((void far*)\ (((unsigned long)(segment) << 16 ) | (unsigned)(offset))) /* ... */ main() { if(*(char far *)0x449==7) scr=MK_FP(0xb000,0x0000); else scr=MK_FP(0xb800,0x0000); /* ... */ } Warto[ci makrodefinicji MK_FP jest daleki wskaznik do miejsca w pamici o adresie podanym w postaci segment:offset. W warunku if sprawdzana jest war- to[ zmiennej z obszaru danych BIOS-u okre[lajcej bie|cy tryb graficzny. 96 WgBb jzyka C W trybie o numerze 7, odpowiadajcym kartom monochromatycznym, pami ekranu zaczyna si od adresu 0xb000:0000; w pozostaBych trybach - od adresu 0xb800:0000 Je|eli program rezydentny ma peBni funkcj procesu dziaBajcego w tle, nale|y tak|e zadba by byB on wystarczajco szybki i nie spowalniaB pracy komputera. PrzykBad pierwszy Z takimi zaBo|eniami mo|emy napisa pierwszy, prosty program rezydentny. Jak przystaBo na ksi|k po[wicon jzykowi C, program bdzie pomocny przy programowaniu w tym jzyku. Bdzie to nakBadka na edytor, pod[wietlajca sBowa kluczowe jzyka C w tek[cie wy[wietlanym na ekranie. Program, wyko- rzystujcy przerwanie zegarowe 1Ch bdzie regularnie przegldaB ekran w poszukiwaniu sBów kluczowych C, a w przypadku znalezienia takiego sBowa bdzie odpowiednio zmieniaB atrybuty ekranu. Zacznijmy od definicji globalnych. Dla potrzeb kodu startowego musimy zdefi- niowa zmienn size, okre[lajc rozmiar programu w paragrafach i zmienn au- tor, wskazujc na BaDcuch wy[wietlany podczas instalacji. int size=0x50; char *autor="Adam Sapek C Key Words$"; Nale|y pamita, by BaDcuch wskazywany przez zmienn autor koDczyB si znakiem $. Warto[ zmiennej size dobiera si w nastpujcy sposób. Gotowy program nale|y skompilowa i doBczy do niego odpowiedni kod startowy. Do dBugo[ci otrzyma- nego programu *.COM nale|y doda 256, do tego doda sumaryczn dBugo[ (w bajtach) zmiennych globalnych, którym nie jest jawnie nadawana w definicji warto[ (wygodniej i bezpiecznij jest jawnie nada warto[ wszystkim zmiennym). Tak otrzyman warto[ nale|y podzieli przez 16 (zaokrglajc w gór). Je|eli po zainstalowaniu programu nastpuje zawieszenie systemu, prawdopo- dobne jest, |e warto[ zmiennej size jest za maBa. Je|eli program rezydentny po zainstalowaniu dziaBa niepoprawnie, ale nie powoduje zaBamania systemu, nale|y raczej poszuka bBdu w programie, ni| zwiksza warto[ zmiennej size. Czstym powodem niepoprawnego dziaBania programu rezydentnego, podczas gdy wersja nierezydentna dziaBa bez zarzutu, jest pominicie inicjacji zmiennych globalnych w funkcji main. W normalnym programie zmienne globalne maj na pocztku funkcji main warto[ nadan w definicji lub zero, natomiast w programie rezy- dentnym zmienne te mog zawiera warto[ci przypadkowe pozostaBe po poprzed- nim wywoBaniu. SBowa kluczowe jzyka C s zgromadzone w tablicy wskazników znakowych. char *listtab[]={"for","int","if","char","while","case","do", "else","return","void","default","struct", "switch","far","extern","break","goto", "float","const","continue","double","union", V. Kod wynikowy 97 "unsigned","static","sizeof","long","short", "auto","register","typedef",0},**list; Kolejno[ sBów w tablicy jest taka, by sBowa cz[ciej wystpujce znajdowaBy si bli|ej pocztku tablicy. Dziki temu zmniejsza si [redni czas przeszukiwania tablicy. {eby odnalez na ekranie sBowa kluczowe jzyka C, program bdzie szukaB maBych li- ter, sprawdzaB czy s one pocztkiem sBowa (czy nie s poprzedzone liter lub podkre- [leniem) i porównywaB kolejne sBowa kluczowe z tablicy ze znalezionym sBowem. Je|eli jedno ze znajdujcych si w tablicy sBów bdzie odpowiadaBo sBowu znale- zionemu na ekranie, program sprawdzi jeszcze, czy sBowo na ekranie koDczy si w tym miejscu. Jest to konieczne aby nie wyró|ni na przykBad pocztku nazwy zmiennej: char inteligencja; jako sBowa kluczowego int. Jak wida z powy|szego skróconego opisu algorytmu, program ma do wykona- nia dosy du|o pracy. Poniewa| bdzie on wykorzystywaB przerwanie zegarowe, caB t prac bdzie wykonywaB okoBo 18 razy na sekund. Je|eli kto[ uwa|a, |e to za du|o, aby program zostaB niezauwa|ony, to ma racj: na wolnym kompute- rze zainstalowanie go spowoduje wyrazne spowolnienie pracy. Pojawia si tutaj zasygnalizowany wcze[niej problem szybko[ci programów rezydentnych dzia- Bajcych w tle. W tym przypadku mo|na próbowa zmienia algorytm przeszu- kiwania tablicy sBów kluczowych , np. zamiast przeszukiwania liniowego zastosowa przeszukiwanie rozproszone. Moim zdaniem istnieje jednak prostsze rozwizanie: skoro program wywoBywany jest za czsto |eby wykona tak du| prac, to nale|y podzieli j na fragmenty, które bd wykonywane w kolejnych wywoBaniach. W naszym przypadku program mo|e jednorazowo przeglda tyl- ko jedn czwart ekranu. W ten sposób caBy ekran bdzie aktualizowany mniej wicej cztery razy na sekund, co w zupeBno[ci wystarczy. Aby w kolejnych wywoBaniach program przegldaB kolejne wiartki ekranu, zmienna sBu|ca do adresowania pamici ekranu nie bdzie nigdy inicjowana, a jedynie cigle zwikszana i dzielona modulo 4000. W ten sposób kolejne wy- woBanie programu zacznie przeszukiwanie ekranu w tym miejscu, w którym skoDczyBo poprzednie, a zmienna adresujca ekran nie wyjdzie nigdy poza do- zwolony zakres. Kompletny tekst programu przytoczono poni|ej. /* plik cwords.c */ #define MK_FP(seg,ofs) ((void far*)\ (((unsigned long)(seg) << 16 ) | (unsigned)(ofs))) char *autor="Adam Sapek C Key Words (93)$"; int size=65, /* rozmiar TSR-a w paragrafach */ x,i,off=0, 98 WgBb jzyka C _acrtused=0; /* potrzebne przy Microsoft C */ unsigned char far *screen,far *scr; char *listtab[]={"for","int","if","char","while","case","do", "else","return","void","default","struct", "switch","far","extern","break","goto", "float","const","continue","double","union", "unsigned","static","sizeof","long","short", "auto","register","typedef",0},**list; void main() { char c; if(*(char far *)0x449==7) /* gdzie jest pami video ? */ scr=MK_FP(0xb000,0x0000); /* Hercules, MDA -> B000:0 */ else scr=MK_FP(0xb800,0x0000); /* VGA, EGA, itp. -> B800:0 */ for(x=0;x<500;x++,off=(off+2)%4000) { screen=scr+off; if((unsigned char)(*screen-'a')<='z'-'a') /* je|eli maBa litera */ { c=screen[-2]; /* poprzedni znak */ if((unsigned char)(c-'a')>'z'-'a' && /* nie jest liter ani _ */ (unsigned char)(c-'A')>'Z'-'A' && c!='_' ) { list=listtab; while(*list) { i=0; while(screen[2*i]==(*list)[i]) /* porównaj napis na ekranie */ i++; /* z kolejnym sBowem kluczowym */ if(!(*list)[i]) /* je|eli zgodne */ { c=screen[2*i]; /* nastpny znak na ekranie */ if((unsigned char)(c-'a')>'z'-'a' && /* je|eli nie jest liter, */ (unsigned char)(c-'A')>'Z'-'A' && /* cyfr ani podkre[leniem */ (unsigned char)(c-'0')>'9'-'0' && c!='_' ) { screen++; while(i) screen[2*(--i)]|=0xF; /* to rozja[nij znalezione sBowo */ break; } } V. Kod wynikowy 99 list++; /* sprawdz nastpne sBowo z listy */ } } } } } Program nale|y skompilowa w modelu Small a nastpnie utworzy program COM poleceniem: tsr 1c cwords Po uruchomieniu uzyskanego w ten sposób programu, na ekranie pojawi si napis: Adam Sapek C Key Words (93) i od tego momentu ka|de sBowo kluczowe jzyka C bdzie wypisywane si na ekranie rozja[nionym atrybutem. PrzykBad drugi Poprzedni program nale|y do klasy "rezydentów" wykonujcych w tle jak[ pra- c. Inn kategori s programy "czuwajce", które uaktywniaj si tylko w konkretnej sytuacji. Bardzo czsto powodem uaktywnienia programu rezy- dentnego jest naci[nicie jakiego[ klawisza. W takim przypadku istnieje dwie mo|liwo[ci: program mo|e przechwyci przerwanie sprztowe klawiatury 09h lub przerwanie BIOS-u 16h. Przerwanie 09h jest przerwaniem sprztowym, generowanym zawsze przy naci- [niciu (i puszczeniu) klawisza. Standardowa procedura obsBugi tego przerwania sprawdza, który klawisz zostaB naci[nity i wpisuje kod odpowiedniego znaku do bufora klawiatury. Z kolei przerwanie 16h jest przerwaniem programowym. Procedura obsBugi tego przerwania realizuje midzy innymi funkcje pobierania danych wprowadzanych z klawiatury. W przeciwieDstwie do obsBugi wyprowadzenia danych na ekran, gdzie BIOS jest czsto omijany a programy odwoBuj si bezpo[rednio do pa- mici video, zdecydowana wikszo[ (praktycznie wszystkie) programy reali- zuj odczytywanie klawiatury przy u|yciu funkcji przerwania 16h. Funkcje przerwania 16h umo|liwiaj sprawdzenie, czy w buforze klawiatury znajduje si jaki[ znak i pobranie znaku z bufora. Je|eli funkcja pobierajca znak zostanie wywoBana gdy bufor jest pusty, to bdzie ona czekaBa na wprowadzenie znaku z klawiatury. Aatwo sobie wyobrazi, |e gdyby programy, chcc pobra znak z klawiatury, wywoBywaBy od razu funkcj pobrania znaku, to instalowanie pro- gramu rezydentnego wykorzystujcego przerwanie 16h byBoby pozbawione sen- su. Na szcz[cie, powszechnie przestrzegana jest zasada, |e najpierw w ptli wywoBuje si funkcj sprawdzajc, czy w buforze klawiatury jest znak do po- brania, a dopiero po pojawieniu si znaku pobiera si go funkcj 00h. Dziki te- 100 WgBb jzyka C mu mo|emy by prawie pewni, |e przerwanie 16h bdzie wywoBywane dosy regularnie. Wrómy do wyboru przerwania dla programu uaktywnianego naci[niciem kla- wisza. Uwa|am, |e zawsze, gdy to jest mo|liwe, nale|y unika przejmowania przerwaD sprztowych. Mo|liwo[ uruchomienia w ka|dej chwili programu "podwieszonego" pod takie przerwanie w wikszo[ci przypadków jest wad, a nie zalet. W praktyce, program wykorzystujcy przerwanie 16h nie zostanie wywoBany tylko w czasie trwania operacji dyskowych. Uaktywnienie programu w takiej chwili jest raczej niepo|dane, gdy| niepotrzebnie zwiksza ryzyko utraty danych (zwiksza si prawdopodobieDstwo wystpienia awarii, gdy plik nie jest caBkowicie zapisany). Istniej oczywi[cie sytuacje, w których trzeba wy- korzysta przerwanie sprztowe. MiaBem okazj kiedy[ pisa program rezydent- ny, bdcy nakBadk na pewien nieprofesjonalny program. "Rezydent" miaB umo|liwia wpisywanie czsto powtarzajcych si danych przy pomocy naci- [nicia jednego klawisza. Po napisaniu programu okazaBo si, |e wspóBpracuje on ze wszystkim z wyjtkiem programu, dla którego byB przeznaczony. Powo- dem byB fakt, |e program ten nie wywoBywaB funkcji sprawdzajcej w buforze obecno[ znaku, lecz od razu pobieraB ten znak. W ten sposób znaki wpisywane z klawiatury byBy od razu odczytywane z bufora przez funkcj czytajc, która na nie caBy czas czekaBa, za[ program rezydentny nie miaB szans na "zauwa|enie" |adnego znaku. Po zamianie przerwania 16h na przerwanie sprztowe klawiatury 09h programy wspóBpracowaBy bez zarzutu. Instalujc program korzystajcy z przerwania klawiatury trzeba pamita, |e mo|e on zosta wywoBany w dowolnym momencie, a wic tak|e z wntrza DOS-u. WywoBanie programu wykorzystujcego funkcje usBugowe DOS-u (przerwanie 21h) w trakcie wykonywania której[ z nich spowoduje najprawdo- podobniej zaBamanie systemu. Jednym ze sposobów zabezpieczenia si przed ta- k sytuacj, jest sprawdzanie przed uruchomieniem programu rezydujcego, czy wykonywana jest jaka[ funkcja DOS-u. Wykorzystuje si do tego wewntrzny znacznik DOS-u InDOS, którego adres zwraca funkcja 34h w rejestrach ES:BX. Warto[ tego znacznika równa zero oznacza, |e nie jest wykonywana |adna funk- cja DOS-u. Znacznik InDOS jest ustawiony tak|e wtedy, gdy DOS, nic nie robic, czeka na naci[nicie klawisza. Poniewa| system znajduje si wówczas w jaBowej ptli, uruchomienie programu rezydentnego nie mo|e mu zaszkodzi. Aby umo|- liwi wywoBanie programu w takiej sytuacji, wykorzystuje si przerwanie 28h, ge- nerowane przez system w trakcie oczekiwania na wprowadzenie danych z klawiatury. Zasygnalizowane powy|ej problemy zwizane z uruchamianiem programów re- zydentnych, szczególnie wykorzystujcych przerwanie 09h, s bardzo rozlegBym zagadnieniem. Po wyczerpujce informacje radz sign do specjalistycznej li- teratury dotyczcej systemu DOS. Program, który poni|ej przedstawi, nie bdzie uaktywniaB si po naci[niciu klawisza, a mimo to bdzie stale czytaB klawiatur i bdzie wywoBywany prze- V. Kod wynikowy 101 rwaniem 16h. Jego zadaniem jest wygaszenie ekranu je|eli przez trzy minuty nie zostanie naci[nity |aden klawisz. Program ten bdzie korzystaB z kilku funkcji napisanych w asemblerze (w tym ze zdefiniowanych wcze[niej w tym rozdziale funkcji _getch i checkch), a tak|e ze standardowych funkcji C. Wygaszenie ekranu mo|e polega na wpisaniu odpowiednich warto[ci do obsza- ru pamici video, natomiast do wygaszenia kursora konieczne jest skorzystanie z funkcji BIOS-u. Poni|ej przedstawiam definicje dwóch funkcji sBu|cych do wygaszenia i wy[wietlania kursora. ; plik kursor.asm .MODEL SMALL .CODE proc _hide_cursor ; funkcja chowa kursor mov ah,03h mov bh,0h int 10h ; pobierz opis kursora or ch,20h ; ustaw atrybuty na "niewidoczny" mov ah,1h int 10h ; zapisz opis kursora ret endp proc _show_cursor ; funkcja pokazuje kursor mov ah,03h mov bh,0h int 10h ; pobierz opis kursora and ch,0dfh ; kasuj atrybut "niewidoczny" mov ah,1h int 10h ; zapisz opis kursora ret endp public _show_cursor, _hide_cursor end Funkcja hide_cursor pobiera sBowo opisujce ksztaBt i atrybuty kursora i ustawia atrybuty tak by kursor byB niewidoczny, natomiast funkcja show_kursor przywraca widoczno[ kursora. W ten sposób para wywoBaD funkcji: hide_cursor(); /* ... */ show_cursor(); nie powoduje zmiany ksztaBtu kursora. Jak ju| wspomniaBem, w programie zostan u|yte tak|e funkcje z bibliotek stan- dardowych jzyka C. W tym przypadku bdzie to generator liczb pseudoloso- wych. 102 WgBb jzyka C WzmiankowaBem wcze[niej, |e wiele funkcji standardowych daje zbyt dBugi kod jak na programy rezydentne. Nie dotyczy to jednak wszystkich funkcji. Je|eli istnieje potrzeba zastosowania funkcji bibliotecznych nale|y wybiera funkcj jak najni|szego poziomu. Na przykBad do obsBugi plików lepiej u|y "bliskich systemowi" funkcji zdefiniowanych w pliku io.h ni| funkcji zdefiniowanych w stdio.h. Przy próbie doBczania niektórych funkcji z bibliotek standardowych konsolida- tor mo|e zgBosi komunikat o braku definicji pewnych symboli. Prawdopodob- nie bd to zmienne definiowane w oryginalnym Startup-ie. Mo|na w takim przypadku spróbowa zdefiniowania zmiennych o takich nazwach w programie gBównym i zobaczy, czy program bdzie dziaBaB poprawnie (zwykle bdzie). WspominaBem ju| wcze[niej, |e wyj[cie na ekran najlepiej realizowa w programach rezydentnych przez bezpo[rednie odwoBania do pamici video. Pami ekranu mo|na reprezentowa na wiele sposobów. W poni|szym programie zmienna screen jest zadeklarowana nastpujco: char far *screen; a wic pami ekranu jest reprezentowana przez daleki wskaznik znakowy, co umo|liwia Batwe przepisanie jego zawarto[ci. W razie potrzeby mo|na definio- wa bardziej wyrafinowane struktury odwzorowujce ekran, np. int (far *screen)[80]; W zasigu tej definicji mo|na odwoBywa si do konkretnego znaku na ekranie przy pomocy jego wspóBrzdnych, np. screen[10][40]=0x0F41; Nale|y przy tym pamita, |e pierwszy indeks odpowiada wspóBrzdnej Y, a starszy bajt wpisywanego sBowa okre[la atrybuty wy[wietlanego znaku. {eby jeszcze bardziej zbli|y si do "normalnego spojrzenia" na ekran, mo|na zdefiniowa go jako tablic struktur. Struktura bdzie opisywa ka|dy znak na ekranie jako kod znaku i atrybut: typedef struct { char code,attr; }character; character (far *screen)[80]; W zasigu takiej definicji mo|na odwoBywa si zarówno do atrybutów, jak i kodów konkretnych znaków przez ich wspóBrzdne, np.: for(x=0;x<80;x++) if(screen[10][x].code=='A')screen[10][x].attr=0x0f; /* rozja[nij litery 'A' w jedenastym wierszu od góry */ V. Kod wynikowy 103 Oto peBny tekst programu saver wygaszajcego ekran, po okoBo 3 minutach od ostatniego naci[nicia klawisza. /* plik saver.c */ #include <stdlib.h> #define MK_FP(seg,ofs) ((void far*)\ (((unsigned long)(seg) << 16 ) | (unsigned)(ofs))) int size=80, /* rozmiar TSR-a w paragrafach */ _acrtused=0; /* potrzebne w Microsoft C */ char *autor="Adam Sapek Screen Saver (C) 92$"; unsigned char far *timer_ticks=MK_FP(0x0040,0x006c), /* zegar */ far *screen, far *screen2; struct { unsigned char x,y; }snake[4]={{4,4},{3,3},{2,2},{1,1}}; char dx[8]={0,1,2,1,0,-1,-2,-1}, dy[8]={-1,-1,0,1,1,1,0,-1}; unsigned off,time=300; void at(char x,char y) /* ustaw wspóBrzdne (x,y) */ { off=160*y+2*x; } void print(char *s) /* pisz na ekranie cig dwóch znaków */ { screen[off]=s[0]; screen[off+2]=s[1]; } void move(unsigned char far* e1,unsigned char far *e2) /* przepisz e2 do e1 */ { int x; for(x=0;x<4000;x++) e1[x]=e2[x],e2[x]=(x%2)?7:32; } void main() { char direct=3,y,x,a; if(checkch()!=-1||time==300) /* pocztek lub naci[nito klawisz */ { time=timer_ticks[1]; srand(*timer_ticks); } /* zapamitaj czas posiej */ /* generator liczb losowych */ if((unsigned char)(timer_ticks[1]-time)>=13) /* czy minBy 3 min. ? */ { 104 WgBb jzyka C if(*(char far *)0x449==7) /* gdzie pami video ? */ screen=MK_FP(0xb000,0x0000); /* Hercules -> B000:0 */ else screen=MK_FP(0xb800,0x0000); /* VGA itp. -> B000:0 */ screen2=screen+4000; /* screen2 - druga strona */ move(screen2,screen); /* przepisz zawarto[ ekranu */ hide_cursor(); /* schowaj kursor */ while(checkch()==-1) /* a| nie zosatnie naci[nity*/ { /* jakis klawisz */ do{ if(rand()%11==0)direct=rand()%8; /* [rednio co 11 kroków nowy */ x=snake[0].x+dx[direct]; /* kierunek */ y=snake[0].y+dy[direct]; }while(x<0||x>=79||y<0||y>24); /* czy mo|na w tym kierunku */ at(x,y); print("ˆ%ˆ%"); /* rysuj w|a*/ at(snake[0].x,snake[0].y); print("“%“%"); at(snake[1].x,snake[1].y); print("’%’%"); at(snake[2].x,snake[2].y); print("‘%‘%"); at(snake[3].x,snake[3].y); print(" "); for(a=3;a>=1;a--) /* przesuD w|a */ { snake[a].x=snake[a-1].x; snake[a].y=snake[a-1].y; } snake[0].x=x; snake[0].y=y; time=*timer_ticks; while(*timer_ticks-time<4); /* poczekaj 4 takty zagara */ } _getch(); /* pobierz znak z bufora */ move(screen,screen2); /* odtwórz ekran */ time=timer_ticks[1]; show_cursor(); /* poka| kursor */ } } W celu uzyskania programu SAVER.COM, instalowanego jako program rezy- dentny przechwytujcy przerwanie 16h, nale|y skompilowa plik SAVER.C w modelu Small, po czym poBczy otrzymany plik SAVER.OBJ z odpowiednim kodem startowym i bibliotecznymi: tsr 16 saver getch checkch kursor f& f& f& f& l .

Wyszukiwarka

Podobne podstrony:
11 (311)
ZADANIE (11)
Psychologia 27 11 2012
359 11 (2)
11
PJU zagadnienia III WLS 10 11
Wybrane przepisy IAAF 10 11
06 11 09 (28)
info Gios PDF Splitter And Merger 1 11

więcej podobnych podstron