You are on page 1of 23

V.

Kod wynikowy

V.

83

Kod wynikowy
Niniejszy rozdzia rni si troch od reszty ksiki. Rozwaania w nim zawarte
"le na niszym poziomie abstrakcji", nie dotycz bowiem skadni ani bibliotek
jzyka C lecz procesu powstawania programu wynikowego i sposobw ingerencji
w ten proces. Podczas gdy w pozostaych rozdziaach staraem si opisywa jzyk
C w sposb niezaleny zarwno od kompilatora jak i sprztu, ten rozdzia bdzie
dotyczy tylko komputerw kompatybilnych z IBM PC, pracujcych pod nadzorem systemu DOS.
Kady, kto zetkn si z jzykiem C, zastanawia si zapewne, dlaczego najprostszy program:
main()
{}

daje tak duy kod wynikowy. Ot dzieje si tak nie dlatego, e kompilatory C
daj bardzo nieoptymalny kod, tylko dlatego, e bardzo powanie traktuj kady
program. W wyniku takiego powanego podejcia, do kadego programu doczany jest spory fragment kodu (w dalszej czci rozdziau bd okrela go mianem
Startup), ktry po pierwsze przygotowuje pewne dane dla programu gwnego
a po drugie robi wszystko co moliwe, eby zabezpieczy system przed zmianami
jakie moe poczyni program. Startup zachowuje szereg informacji o stanie systemu w chwili uruchomienia programu: adres zmiennych rodowiskowych, wersj
DOS-u, wektory niektrych przerwa, itp. Kolejn wykonywan czynnoci jest
przygotowanie pewnych danych i funkcji wykorzystywanych przez program
gwny. Instalowana jest na przykad standardowa funkcja obsugi bdu dzielenia
przez zero, funkcje arytmetyki zmiennoprzecinkowej, zapamitywana jest warto
zegara BIOS-u w celu ewentualnego uycia przez funkcj clock.
Jedn z waniejszych funkcji wykonywanych przez Startup jest przygotowanie
argumentw dla funkcji main. Funkcja ta moe mie trzy argumenty:
main(int argc, char *argv[], char *envp[])

Oczywicie, nie kady program musi uywa argumentw, a nie kady, ktry ich
uywa, musi uywa wszystkich. Poprawne s rwnie nastpujce nagwki
funkcji main:
main()
main(int argc)
main(int argc,char *argv[])

W pierwszym argumencie, oznaczonym tutaj argc, Startup przekazuje do programu gwnego liczb argumentw w wierszu wywoania programu plus jeden.
Drugi argument, oznaczony argv, jest wskanikiem do tablicy wskanikw wskazujcych kolejne argumenty wywoania. Na przykad, jeeli program zosta wywoany w nastpujcy sposb:

84

Wgb jzyka C
program -u book a:\*.c b:\*.c

to argumenty funkcji main bd miay nastpujce wartoci:


argc=5
argv[0]="program"
argv[1]="-u"
argv[2]="book"
argv[3]="a:\*.c"
argv[4]="b:\*.c"

/* DOS 3.0 i wyzsze */

Ostatni argument funkcji main, oznaczony envp, jest wskanikiem do tablicy


wskanikw do zmiennych rodowiskowych. Ostatnim elementem tablicy jest
wskazanie puste NULL.
Poniszy program uywa trzeciego argumentu funkcji main, do wypisywania
wartoci 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 czynnoci, Startup wywouje program gwny, czyli funkcj main. Po powrocie z funkcji main, czyli po zakoczeniu dziaania programu, Startup odtwarza zapamitane przed wywoaniem programu
informacje (na przykad wektory przerwa) i wraca do DOS-u.

1. Zmieniamy Startup
Czsto pisze si proste programu, nie wymagajce zachowywania wektorw przerwa, instalowania procedur obsugi bdw i wykonywania wszystkich tych operacji, ktre robi Startup. Chcielibymy, eby takie programy byy krtkie a mog
one by krtkie, a nawet bardzo krtkie. eby tak si stao, trzeba pozby si
zbdnego kodu, a wic trzeba usun oryginalny Startup.
Sprbujmy stworzy wasn wersj Startup-u, ktra bdzie ograniczaa si jedynie do
wywoania funkcji main, umoliwiaa zrobienie z prostego programu maego
COM-a.
W tym miejscu trzeba kilka sw powici sposobowi w jaki kompilator
(i konsolidator) doczaj Startup do programu.
W przypadku kompilatorw firmy Borland jest to realizowane w sposb 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 kadego modelu pamici istnieje osobny kod Startup i osobny modu
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 moduy programu).


Dziki takiemu rozwizaniu zastpienie Startup-a sprowadza si do zastpienia
moduu c0s.obj z powyszego przykadu innym, np.:
tlink tsr16.obj program ... , program , , cs.lib

W rodowisku Microsoft C nie ma oddzielnych moduw Startup. Kod startowy


"zaszyty" jest w bibliotekach i automatycznie doczany do kadego programu
czonego z bibliotek. Aby standardowy kod Startup nie zosta doczony do
programu i mg by zastpiony innym, w programie naley zdefiniowa zmienn 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 przykady kodw startowych su do


otrzymywania programw typu COM.
Punkt wejcia programu typu COM musi mie przemieszczenie 100h. Nasz
pierwszy, najprostszy Startup od razu wywoa funkcj _main, a po powrocie
z tej funkcji wrci do DOS-u. Przypominam, e identyfikatory globalne w jzyku
C s automatycznie poprzedzane podkreleniem. Dlatego funkcja main nazywa
si w rzeczywistoci _main.
; plik c0.asm
.MODEL SMALL
extrn _main:near
.CODE
ORG 100h
start:
call _main
mov ah,4Ch
int 21h
end start

; wywolaj funkcje main


; wrc do DOS-a

Prosz zwrci uwag, e przed powrotem do DOS-u ustawiamy tylko zawarto


rejestru AH, natomiast rejestr AL bdzie zawiera warto zwrcon przez funkcj
main. Ta wanie bdzie zwrcona przez program.
Po asemblacji pliku c0.asm otrzymamy modu c0.obj.
Moemy teraz napisa pierwszy program, w ktrym oryginalny Startup zastpimy nasz minimaln wersj. Program bdzie zamienia ze sob porty drukarki,
a wic po jego wykonaniu port pierwszy stanie si drugim i vice versa. Progra-

86

Wgb jzyka C
mik taki jest przydatny np. gdy mamy uszkodzony port LPT1 i chcemy "podstawi" go portem LPT2.
/* plik lptport.c */
int _acrtused=0;
void main()

/* program wymienia adresy portw 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 portw drukarki umieszczone w obszarze danych BIOS-u. Zamy, e program znajduje si w pliku lptport.c. Po skompilowaniu w modelu Small1) otrzymamy modu lptport.obj.
Moemy 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 biecym katalogu zostanie utworzony program LPTPORT.COM . Jego dugo w zalenoci od kompilatora i ustawionych opcji wyniesie okoo 50 bajtw!
Wprawdzie nie robi on wiele, ale ten sam program skompilowany "standardowo"
zajmuje przecie prawie 4 tysice bajtw.
Nastpny programik bdzie suy do wyboru opcji w programach wsadowych
(typu .bat). Bdzie on pobiera z klawiatury odpowied na zadane pytanie
i zwraca odpowiedni warto. Warto zwracan przez program mona
w programach wsadowych testowa przy pomocy warunku:
if ERRORLEVEL liczba

Warunek ten jest speniony, gdy warto zwrcona przez ostatnio wywoany
program jest rwna lub wiksza od wartoci liczba.
Poniewa funkcje obsugi wejcia w standardowych bibliotekach C s do obszerne, bdziemy czyta klawiatur bezporednio przy pomocy funkcji BIOS-u.
Odpowiedni do naszego programu bdzie funkcja 0 przerwania 16h. Funkcj

Wszystkie programy naley kompilowa bez informacji dla debuggera, a w kompilatorach firmy Microsoft z opcj /Gs

V. Kod wynikowy

87

_getch czytajc znak z klawiatury zdefiniujemy w osobnym pliku getch.asm,

gdy bdziemy jej uywa take w innych programach2).


; plik getch.asm
.MODEL SMALL
.CODE
proc __getch
klawiatury
mov ah,0
int 16h
znak
ret
endp

; funkcja pobiera znak z bufora


; i zwraca jako rezultat
; jeeli bufor jest pusty czeka na

public __getch
end

Wykorzystana funkcja 0 przerwania 16h czeka na nacinicie klawisza i zwraca


w rejestrze AX kod nacinitego klawisza. Wartoci tej nie trzeba nigdzie przepisywa, poniewa program w jzyku C spodziewa si przekazania wartoci funkcji
typu intwanie w rejestrze AX.
Program ma pobiera odpowied na pytanie. Zamy, e po naciniciu klawisza T (odpowied twierdzca) program bdzie zwraca warto 116 (kod znaku 't')
a w przeciwnym wypadku warto 0.
/* plik tak_nie.c */
int _acrtused=0;
main()
{
char c=_getch();
klawiatury */
if(c=='t'||c=='T')
return 't';
*/
else
return 0;
*/
}

/* pobierz znak z

/* jezeli 't' lub 'T'

/* w przeciwnym razie

Teraz kompilujemy nasz program w modelu Small i tworzymy program wykonywalny.


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 bajtw dugoci. Mona go wykorzysta w programach


wsadowych w nastpujcy sposb:
2

Moduy asemblerowe, w ktrych definiowane s symbole uywane w programie w C


naley kompilowa programem TASM z opcj /ml

88

Wgb jzyka C
@echo "Zainstalowa cache-a (t/n) ? "
@tak_nie
@if ERRORLEVEL 116 SUPERPCK /EP /T+

Po uruchomieniu powyszego programu na ekranie zostanie wywietlone pytanie:


Zainstalowa cache-a (t/n) ?

Jeeli uytkownik nacinie klawisz T, zostanie uruchomiony program


SUPERPCK, w przeciwnym wypadku uruchomienie programu nie nastpi.

2. Programy rezydentne
W rozdziale tym zaprezentuj "zastpczy" Startup, ktry nie bdzie, jak poprzednio, uproszczeniem Startup-a oryginalnego, lecz bdzie peni cakowicie
odmienn funkcj. Zamiast uruchamia program gwny (funkcj main), bdzie
on instalowa go jako program rezydentny, przechwytujcy wybrane przerwanie.
W ten sposb zamienienie zwykego programu na program rezydentny bdzie bardzo proste i bdzie sprowadza si do jego powtrnej konsolidacji.
Zanim przejd do meritum, chciabym przypomnie po krtce czym s programy
rezydentne, do czego su i jak si je tworzy.
Programy rezydentne to programy, ktre po zakoczeniu dziaania pozostawiaj
cz swojego kodu w pamici i ustawiaj wektor wybranego przerwania tak,
eby wskazywa na ten kod. Po wygenerowaniu tego przerwania nastpuje uruchomienie rezydujcej w pamici czci programu.
2.1. Jakie s zastosowania programw rezydentnych ?
Program rezydentny moe by rozszerzeniem systemu operacyjnego, dostarczajcym
innym programom usug, ktrych nie zapewnia system. Przykadem takiego programu jest sterownik myszki. Programy, ktre chc skorzysta z myszki (np. odczyta
pooenie kursora myszki, stan klawiszy) wywouj przy pomocy odpowiedniego
przerwania rezydentny program sterownika myszki i przekazuj mu w rejestrach
procesora informacj o jak usug prosz.
Innym zastosowaniem programw rezydentnych jest zmiana obsugi standardowego przerwania. Program taki moe na przykad przej przerwanie klawiatury
w celu niestandardowej interpretacji niektrych klawiszy (np. ALT+A jako ).
Przy pomocy programw rezydentnych realizuje si take w systemie DOS namiastk procesw dziaajcych w tle. Program przejmuje w tym celu jakie czsto wywoywane przez system przerwanie (np. przerwanie zegara 1Ch lub
przerwanie obsugi klawiatury 16h), dziki czemu jest odpowiednio czsto wywoywany. Takie "procesy" mog albo wykonywa w tle jak prost prac albo
tylko czuwa i ujawnia si dopiero po zaistnieniu okrelonego warunku (upywie okrelonego czasu, naciniciu jakiego klawisza itp.).

V. Kod wynikowy

89

2.2. Tworzenie programw rezydentnych


Cz rezydujca programu jest wywoywana jako procedura obsugi przerwania. Przerwanie, a w szczeglnoci przerwanie sprztowe, moe pojawi si
praktycznie w dowolnym momencie, a wic w dowolnym momencie moe przerwa dziaanie innego programu. Aby po powrocie z procedury obsugi przerwania (programu rezydentnego) przerwany program mg wznowi poprawnie
dziaanie, stan procesora musi pozosta niezmieniony. Dlatego te kady program rezydentny powinien zachowywa zawarto wszystkich rejestrw procesora.
Ponadto, jeeli program rezydentny korzysta z usug systemu DOS (za pomoc
przerwanie 21h), musi sprawdza, czy w momencie jego wywoania nie bya
wykonywana jak funkcja DOS-u. Wynika to z faktu, e do funkcji DOS-u nie
mona wchodzi dwukrotnie (DOS nie jest wielobieny ( ang. reentrant)). Kopotu tego mona unikn albo przez nieuywanie usug systemowych
w programie rezydentnym, albo wykorzystujc przerwanie, ktre jest wywoywane tylko wtedy, gdy aktywacja programu rezydentnego jest "bezpieczna" (idealne jest tu przerwanie BIOS-u 16h).
Przerwania, przejmowane przez programy rezydentne bardzo czsto peni wan funkcj w systemie. Jeeli tak jest, program rezydentny powinien rwnie
wywoywa oryginaln (tzn. zastan w momencie instalacji) procedur obsugi
przerwania.
2.3. Funkcja main jako program rezydentny
Funkcja main nie zachowuje oczywicie zawartoci rejestrw procesora. Dlatego
wektor przerwania nie moe wskazywa bezporednio na ni, lecz na fragment kodu, ktry zachowa rejestry na stosie, wywoa funkcj _main, a po jej zakoczeniu
odtworzy zawarto rejestrw ze stosu. Kod ten powinien take zapewnia wykonanie oryginalnej procedury obsugi przerwania.
eby zabezpieczy si przed zaptleniem wywoa programu rezydentnego
(wywoaniem programu w trakcie jego dziaania), powinien on posiada jaki
wskanik aktywnoci. Przed aktywacj program sprawdzaby czy wskanik jest
wyzerowany i tylko w takim przypadku uaktywniaby si ustawiajc jednoczenie wskanik na 1.
Naley jeszcze zastanowi si nad zawartoci rejestrw segmentowych
w chwili wywoania programu rezydujcego w pamici. Poniewa obsuga przerwania przez procesor polega na wykonaniu dalekiego wywoania procedury,
rejestr segmentowy kodu CS bdzie wskazywa segment, w ktrym znajduje si
program rezydentny. Rejestry segmentowe danych DS i ES, bd oczywicie
wskazywa na segment(-y) danych programu przerwanego. Zakadamy, e program w C bdziemy kompilowa w modelu Small (tworzenie rezydentw wikszych ni 64K nie ma sensu). W takim przypadku dane znajduj si w tym

90

Wgb jzyka C
samym segmencie co kod a wic do rejestrw DS oraz ES naley przed wywoaniem funkcji _main przepisa zawarto rejestru CS.
Poniej przedstawiam fragment kodu, realizujcy wszystkie opisane powyej dziaania.
Interrupt16

proc
cli

far

cmp
je
mov
sti
push
push
push
push
push
push
push
push
push
push

cs:TSR, 1
end
cs:TSR, 1
ax
bx
cx
dx
di
si
ds
es
bp
cs

pop
push
pop

ds
cs
es

; zablokuj przerwania dopki

wskanik
; aktywnoci nie jest ustawiony
; czy program aktywny ?
; ustaw wskanik aktywnoci
; mona odblokowa przerwania
; zachowaj rejestry na stosie

; inicjuj rejestry segmentowe

danych

aktywnoci
end:

call _main

; wywoaj program gwny

pop
pop
pop
pop
pop
pop
pop
pop
pop

bp
es
ds
si
di
dx
cx
bx
ax

; odtwrz zawarto rejestrw

mov

cs:TSR, 0

; wyzeruj wskanik

sti
jmp

cs:Original16

; skocz do oryginalnej

procedury
endp

; obsugi przerwania 16h

Odwoania do zmiennych (TSR i Original16) na pocztku i kocu programu s


prefiksowane rejestrem CS, gdy w tych miejscach rejestr DS, uywany domylnie
do adresowania pamici, wskazuje na segment danych programu przerwanego.
Powyszy kod bdzie jedn z czci nowego Startup-a.
Druga cz Startup-a musi zapamita zastany wektor przerwania 16h
w zmiennej Original16, ustawi wektor tego przerwania tak, eby wskazywa na
zdefiniowan powyej procedur Interrupt16, i powrci do DOS-u pozostawiajc
program w pamici.

V. Kod wynikowy

91

Do pozostawienia programu rezydujcego w pamici, uyjemy funkcji DOS-u


31h (Keep). Funkcji tej naley poda w rejestrze DX liczb paragrafw 16bajtowych ktre bdzie zajmowa program rezydujcy. Warto ta jest definiowana przez programist w zmiennej globalnej size w programie w C.
extrn _main:near
extrn _size:word
extrn _autor:near
.model TINY
.CODE
org 100h
gdy
Start:

; offset kodu 100h


; program typu COM

;--- zachowaj oryginalny wektor przerwania 16h


mov
int
mov
mov

ax, 3516h
;pobierz wektor 16h
21h
word ptr Original16, bx
;zachowaj offset wektora
word ptr Original16+2, es ;zachowaj segment wektora

;--- zaaduj nowy wektor


mov
mov

ax, 2516h
dx, offset Interrupt16
int
21h

;funkcja 25h i wektor 16h


;offset w dx, segment w ds

;--- terminate and stay resident


mov
mov

ax, 3100h
dx,_size

int

21h

;funkcja 31h kod powrotu 0


;ile paragrafw zostawi

Moglibymy ju poczy obie czci i utworzy nowy Startup. Zanim jednak to


zrobimy dodajmy jeszcze dwie proste i poyteczne funkcje.
Przed uruchomieniem kadego programu system operacyjny tworzy kopi rodowiska. Kopia taka zostanie utworzona take dla programu rezydentnego; co
gorsza, pozostanie ona, zupenie niepotrzebnie, w pamici. Dopiszmy wic do
naszego Startup-a danie zwolnienia pamici zajtej przez kopi rodowiska.
Adres zawierajcego j segmentu umieszczony jest w sowie przesunitym
o 2Ch bajtw wzgldem pocztku prefiksu segmentu programu (PSP).
mov
mov
mov
mov
int

bx,2Ch
ax,[word ptr bx]
es,ax
ah,49h
21h

;zwolnij pami zajmowan przez


;kopi rodowiska
;adres segmentu rodowiska do ES
;zwolnij segment pamici
;wskazywany przez ES

Drugim zadaniem kodu startowego bdzie wypisanie wizytwki programu rezydentnego w czasie jego instalacji. Zamy, e bdzie to acuch wskazywany
przez globaln zmienn programu w C o nazwie autor. Do wypisania tego acucha uyjemy funkcji 09h DOS-u (funkcja ta oczekuje, e acuch bdzie zakoczony znakiem $).
mov

ah,09h

92

Wgb jzyka C
mov
int

dx,[word PTR _autor]


21h

;wypisz acuch

Oto pena 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
int
mov
mov

ax, 3516h
;funkcja 35h i wektor 16h
21h
word ptr Original16, bx
;zachowaj offset wektora
word ptr Original16+2, es ;zachowaj segment wektora

;--- zaaduj nowy wektor


mov
mov

ax, 2516h
dx, offset Interrupt16
int
21h

;funkcja 25h i wektor 16h


;offset w dx, segment w ds

;--- wypisz wizytwk


mov
mov
int

ah,09h
dx,[word PTR _autor]
21h

;wypisz acuch

;--- zwolnij pami rodowiska


mov
mov
mov
mov
int

bx,2Ch
ax,[word ptr bx]
es,ax
ah,49h
21h

;zwolnij pami zajmowan


;przez kopi rodowiska
;funkcja zwolnij pami

;--- terminate and stay resident


mov
mov
int

ax, 3100h
dx,_size
21h

Interrupt16 proc
cli
cmp
je
mov
sti
push
push
push
push
push
push
push

;funkcja 31h kod powrotu 0


;ile paragrafw zostawi

far
; zablokuj przerwania dopki wskanik
; aktywnoci nie jest ustawiony
cs:TSR, 1 ; czy aktywny ?
end
cs:TSR, 1 ; ustaw wskanik aktywnoci
; mona odblokowa przerwania
ax
; rejestry na stos
bx
cx
dx
di
si
ds

V. Kod wynikowy

93
push
push

es
bp

push

cs

pop
push
pop

ds
cs
es

call

_main

; wywoaj program gwny

pop
pop
pop
pop
pop
pop
pop
pop
pop

bp
es
ds
si
di
dx
cx
bx
ax

; odtwrz zawarto rejestrw

mov

cs:TSR, 0

; zaznacz, e nieaktywny

sti
jmp

cs:Original16

; inicjuj rejestry segmentowe

danych

end:

; skocz do oryginalnej

procedury
endp
TSR
Original16

; obsugi przerwania 16h


label BYTE
db 0
label Dword
dw ?
dw ?

;offset
;segment

END Start

W powyszej wersji funkcja main zostaje "podpita" do przerwania 16h. Na


dyskietce doczonej do ksiki znajduj si take wersje wykorzystujca przerwanie zegarowe 1Ch i przerwanie sprztowe klawiatury 09h. Wersja dla przerwania
zegarowego jest dokadnym odpowiednikiem wersji dla przerwania 16h.
W przypadku przerwania klawiatury 09h, przed wywoaniem funkcji main wywoywana jest oryginalna procedura obsugi tego przerwania. Inaczej funkcja
main nie mogaby odczyta kodu klawisza, ktrego nacinicie spowodowao jej
uaktywnienie.
Dodatkowo dyskietka zawiera wszystkie wyej wspominane programy rozbudowane o moliwo deinstalacji programu rezydentnego. Deinstalacja nastpuj
po wywoaniu programu z opcj /u lub /U i jest moliwa tylko wtedy, gdy po
zainstalowaniu programu jegoprzerwanie nie zostao przejte (np. przez inny program rezydentny).
eby uatwi korzystanie ze zdefiniowanych wyej moduw przedstawiam poniej dwa programiki wsadowe tworzce automatycznie program rezydentny korzystajcy z wybranego przerwania.
REM
@if
@if
@if

plik TSRMS.BAT dla rodowiska Microsoft C


%1==1c goto 1c
%1==16 goto 16
%1==9 goto 9

94

Wgb 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 plikw naley uzupeni ewentualnymi ciekami dostpu. Przykadowe


wywoanie powyszych programw moe wyglda nastpujco (zakadam
ogln nazw TSR zamiast TSRMS czy TSRTC):
tsr 16 program

3. Przykadowe programy rezydentne


Uywajc zdefiniowanego powyej kodu Startup, programista nie musi martwi
si o adne kwestie wynikajce z faktu, e program ma dziaa jako rezydent.
Program napisany w normalny sposb i uruchomiony przy pomocy debuggera,
np. w rodowisku zintegrowanym kompilatora, wystarczy jedynie ponownie
skonsolidowa, np.:
tlink /t tsr16.obj program , program

W ten sposb powstanie program typu COM (uyto 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 wywoywany przerwaniem 16h. Od tego momentu kade wygenerowaniu przerwania
16h spowoduje wywoanie funkcji main programu.

V. Kod wynikowy

95

Tak wic teoretycznie pisanie programw rezydentnych nie rni si niczym od


pisania innych programw. W praktyce, programom takim stawia si troch inne
wymagania.
Przede wszystkim zajmuj one stale miejsce w pamici komputera, a wic powinny by mae. Z tego powodu niedopuszczalne jest uycie wielu funkcji
z bibliotek standardowych, ktre nie byy projektowane do zastosowania
w programach rezydentnych i daj duy kod wynikowy. Dotyczy to szczeglnie
funkcji wejcia/wyjcia, ktre trzeba zastpi wasnymi. Prost funkcj wejciowa zdefiniowalimy ju we wczeniejszej czci tego rozdziau; bya to funkcja
_getch zwracajca kod znaku wprowadzonego z klawiatury. Funkcja ta w razie
potrzeby czeka na nacinicie klawisza. Poniej przedstawiam drug przydatn
funkcj wejcia.
; plik checkch.asm
.model SMALL
.CODE
proc _checkch
mov ah,1
int 16h
jnz koniec
mov ax,-1
koniec: ret
endp

; funkcja zwraca znak z bufora


klawiatury
; lub -1 jezeli bufor jest pusty
; nie usuwa znaku z bufora

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 wyjcia najlepiej uywa w programach rezydentach bezporednich odwoa do pamici ekranu. Aby program by uniwersalny, powinien
on samodzielnie ustala adres pamici ekranu w zalenoci od karty graficznej.
Mona 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);
/* ... */
}

Wartoci makrodefinicji MK_FP jest daleki wskanik do miejsca w pamici


o adresie podanym w postaci segment:offset. W warunku if sprawdzana jest warto zmiennej z obszaru danych BIOS-u okrelajcej biecy tryb graficzny.

96

Wgb jzyka C
W trybie o numerze 7, odpowiadajcym kartom monochromatycznym, pami
ekranu zaczyna si od adresu 0xb000:0000; w pozostaych trybach - od adresu
0xb800:0000
Jeeli program rezydentny ma peni funkcj procesu dziaajcego w tle, naley
take zadba by by on wystarczajco szybki i nie spowalnia pracy komputera.

Przykad pierwszy
Z takimi zaoeniami moemy napisa pierwszy, prosty program rezydentny. Jak
przystao na ksik powicon jzykowi C, program bdzie pomocny przy
programowaniu w tym jzyku. Bdzie to nakadka na edytor, podwietlajca
sowa kluczowe jzyka C w tekcie wywietlanym na ekranie. Program, wykorzystujcy przerwanie zegarowe 1Ch bdzie regularnie przeglda ekran
w poszukiwaniu sw kluczowych C, a w przypadku znalezienia takiego sowa
bdzie odpowiednio zmienia atrybuty ekranu.
Zacznijmy od definicji globalnych. Dla potrzeb kodu startowego musimy zdefiniowa zmienn size, okrelajc rozmiar programu w paragrafach i zmienn autor, wskazujc na acuch wywietlany podczas instalacji.
int size=0x50;
char *autor="Adam Sapek

C Key Words$";

Naley pamita, by acuch wskazywany przez zmienn autor koczy si znakiem $.


Warto zmiennej size dobiera si w nastpujcy sposb. Gotowy program naley
skompilowa i doczy do niego odpowiedni kod startowy. Do dugoci otrzymanego programu *.COM naley doda 256, do tego doda sumaryczn dugo (w
bajtach) zmiennych globalnych, ktrym nie jest jawnie nadawana w definicji
warto (wygodniej i bezpiecznij jest jawnie nada warto wszystkim zmiennym).
Tak otrzyman warto naley podzieli przez 16 (zaokrglajc w gr).
Jeeli po zainstalowaniu programu nastpuje zawieszenie systemu, prawdopodobne jest, e warto zmiennej size jest za maa. Jeeli program rezydentny po
zainstalowaniu dziaa niepoprawnie, ale nie powoduje zaamania systemu, naley
raczej poszuka bdu w programie, ni zwiksza warto zmiennej size. Czstym
powodem niepoprawnego dziaania programu rezydentnego, podczas gdy wersja
nierezydentna dziaa 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 rezydentnym zmienne te mog zawiera wartoci przypadkowe pozostae po poprzednim wywoaniu.
Sowa kluczowe jzyka C s zgromadzone w tablicy wskanikw 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 sw w tablicy jest taka, by sowa czciej wystpujce znajdoway si bliej


pocztku tablicy. Dziki temu zmniejsza si redni czas przeszukiwania tablicy.
eby odnale na ekranie sowa kluczowe jzyka C, program bdzie szuka maych liter, sprawdza czy s one pocztkiem sowa (czy nie s poprzedzone liter lub podkreleniem) i porwnywa kolejne sowa kluczowe z tablicy ze znalezionym sowem.
Jeeli jedno ze znajdujcych si w tablicy sw bdzie odpowiadao sowu znalezionemu na ekranie, program sprawdzi jeszcze, czy sowo na ekranie koczy
si w tym miejscu. Jest to konieczne aby nie wyrni na przykad pocztku
nazwy zmiennej:
char

inteligencja;

jako sowa kluczowego int.


Jak wida z powyszego skrconego opisu algorytmu, program ma do wykonania dosy duo pracy. Poniewa bdzie on wykorzystywa przerwanie zegarowe,
ca t prac bdzie wykonywa okoo 18 razy na sekund. Jeeli kto uwaa, e
to za duo, aby program zosta niezauwaony, to ma racj: na wolnym komputerze zainstalowanie go spowoduje wyrane spowolnienie pracy. Pojawia si tutaj
zasygnalizowany wczeniej problem szybkoci programw rezydentnych dziaajcych w tle. W tym przypadku mona prbowa zmienia algorytm przeszukiwania tablicy sw kluczowych , np. zamiast przeszukiwania liniowego
zastosowa przeszukiwanie rozproszone. Moim zdaniem istnieje jednak prostsze
rozwizanie: skoro program wywoywany jest za czsto eby wykona tak du
prac, to naley podzieli j na fragmenty, ktre bd wykonywane w kolejnych
wywoaniach. W naszym przypadku program moe jednorazowo przeglda tylko jedn czwart ekranu. W ten sposb cay ekran bdzie aktualizowany mniej
wicej cztery razy na sekund, co w zupenoci wystarczy.
Aby w kolejnych wywoaniach program przeglda kolejne wiartki ekranu,
zmienna suca do adresowania pamici ekranu nie bdzie nigdy inicjowana,
a jedynie cigle zwikszana i dzielona modulo 4000. W ten sposb kolejne wywoanie programu zacznie przeszukiwanie ekranu w tym miejscu, w ktrym
skoczyo poprzednie, a zmienna adresujca ekran nie wyjdzie nigdy poza dozwolony zakres.
Kompletny tekst programu przytoczono poniej.
/* plik cwords.c */
#define MK_FP(seg,ofs) ((void far*)\
(((unsigned long)(seg) << 16 ) |
(unsigned)(ofs)))
char *autor="Adam Sapek
int size=65,
paragrafach */
x,i,off=0,

C Key Words (93)$";


/* rozmiar TSR-a w

98

Wgb jzyka C
_acrtused=0;
Microsoft C */

/* potrzebne przy

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')
/* jeeli maa
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])
/* porwnaj napis
na ekranie
*/
i++;
/* z kolejnym
sowem kluczowym */
if(!(*list)[i])
/* jeeli zgodne
*/
{
c=screen[2*i];
/* nastpny
znak na ekranie */
if((unsigned char)(c-'a')>'z'-'a' &&
/* jeeli nie
jest liter, */
(unsigned char)(c-'A')>'Z'-'A' &&
/* cyfr ani
podkreleniem */
(unsigned char)(c-'0')>'9'-'0' && c!='_' )
{
screen++;
while(i)
screen[2*(--i)]|=0xF;
/* to rozjanij
znalezione sowo */
break;
}
}

V. Kod wynikowy

99

list++;
sowo z listy */
}
}
}
}
}

/* sprawdz nastpne

Program naley skompilowa w modelu Small a nastpnie utworzy program


COM poleceniem:
tsr 1c cwords

Po uruchomieniu uzyskanego w ten sposb programu, na ekranie pojawi si napis:


Adam Sapek

C Key Words (93)

i od tego momentu kade sowo kluczowe jzyka C bdzie wypisywane si na


ekranie rozjanionym atrybutem.

Przykad drugi
Poprzedni program naley do klasy "rezydentw" wykonujcych w tle jak prac. Inn kategori s programy "czuwajce", ktre uaktywniaj si tylko
w konkretnej sytuacji. Bardzo czsto powodem uaktywnienia programu rezydentnego jest nacinicie jakiego klawisza. W takim przypadku istnieje dwie
moliwoci: program moe przechwyci przerwanie sprztowe klawiatury 09h
lub przerwanie BIOS-u 16h.
Przerwanie 09h jest przerwaniem sprztowym, generowanym zawsze przy naciniciu (i puszczeniu) klawisza. Standardowa procedura obsugi tego przerwania
sprawdza, ktry klawisz zosta nacinity i wpisuje kod odpowiedniego znaku do
bufora klawiatury.
Z kolei przerwanie 16h jest przerwaniem programowym. Procedura obsugi tego
przerwania realizuje midzy innymi funkcje pobierania danych wprowadzanych
z klawiatury. W przeciwiestwie do obsugi wyprowadzenia danych na ekran,
gdzie BIOS jest czsto omijany a programy odwouj si bezporednio do pamici video, zdecydowana wikszo (praktycznie wszystkie) programy realizuj odczytywanie klawiatury przy uyciu funkcji przerwania 16h. Funkcje
przerwania 16h umoliwiaj sprawdzenie, czy w buforze klawiatury znajduje si
jaki znak i pobranie znaku z bufora. Jeeli funkcja pobierajca znak zostanie
wywoana gdy bufor jest pusty, to bdzie ona czekaa na wprowadzenie znaku
z klawiatury. atwo sobie wyobrazi, e gdyby programy, chcc pobra znak
z klawiatury, wywoyway od razu funkcj pobrania znaku, to instalowanie programu rezydentnego wykorzystujcego przerwanie 16h byoby pozbawione sensu. Na szczcie, powszechnie przestrzegana jest zasada, e najpierw w ptli
wywouje si funkcj sprawdzajc, czy w buforze klawiatury jest znak do pobrania, a dopiero po pojawieniu si znaku pobiera si go funkcj 00h. Dziki te-

100

Wgb jzyka C
mu moemy by prawie pewni, e przerwanie 16h bdzie wywoywane dosy
regularnie.
Wrmy do wyboru przerwania dla programu uaktywnianego naciniciem klawisza. Uwaam, e zawsze, gdy to jest moliwe, naley unika przejmowania
przerwa sprztowych. Moliwo uruchomienia w kadej chwili programu
"podwieszonego" pod takie przerwanie w wikszoci przypadkw jest wad,
a nie zalet. W praktyce, program wykorzystujcy przerwanie 16h nie zostanie
wywoany tylko w czasie trwania operacji dyskowych. Uaktywnienie programu
w takiej chwili jest raczej niepodane, gdy niepotrzebnie zwiksza ryzyko
utraty danych (zwiksza si prawdopodobiestwo wystpienia awarii, gdy plik
nie jest cakowicie zapisany). Istniej oczywicie sytuacje, w ktrych trzeba wykorzysta przerwanie sprztowe. Miaem okazj kiedy pisa program rezydentny, bdcy nakadk na pewien nieprofesjonalny program. "Rezydent" mia
umoliwia wpisywanie czsto powtarzajcych si danych przy pomocy nacinicia jednego klawisza. Po napisaniu programu okazao si, e wsppracuje
on ze wszystkim z wyjtkiem programu, dla ktrego by przeznaczony. Powodem by fakt, e program ten nie wywoywa funkcji sprawdzajcej w buforze
obecno znaku, lecz od razu pobiera ten znak. W ten sposb znaki wpisywane
z klawiatury byy od razu odczytywane z bufora przez funkcj czytajc, ktra
na nie cay czas czekaa, za program rezydentny nie mia szans na "zauwaenie"
adnego znaku. Po zamianie przerwania 16h na przerwanie sprztowe klawiatury
09h programy wsppracoway bez zarzutu.
Instalujc program korzystajcy z przerwania klawiatury trzeba pamita, e
moe on zosta wywoany w dowolnym momencie, a wic take z wntrza
DOS-u. Wywoanie programu wykorzystujcego funkcje usugowe DOS-u
(przerwanie 21h) w trakcie wykonywania ktrej z nich spowoduje najprawdopodobniej zaamanie systemu. Jednym ze sposobw zabezpieczenia si przed tak sytuacj, jest sprawdzanie przed uruchomieniem programu rezydujcego, czy
wykonywana jest jaka funkcja DOS-u. Wykorzystuje si do tego wewntrzny
znacznik DOS-u InDOS, ktrego adres zwraca funkcja 34h w rejestrach ES:BX.
Warto tego znacznika rwna zero oznacza, e nie jest wykonywana adna funkcja DOS-u. Znacznik InDOS jest ustawiony take wtedy, gdy DOS, nic nie robic,
czeka na nacinicie klawisza. Poniewa system znajduje si wwczas w jaowej
ptli, uruchomienie programu rezydentnego nie moe mu zaszkodzi. Aby umoliwi wywoanie programu w takiej sytuacji, wykorzystuje si przerwanie 28h, generowane przez system w trakcie oczekiwania na wprowadzenie danych
z klawiatury.
Zasygnalizowane powyej problemy zwizane z uruchamianiem programw rezydentnych, szczeglnie wykorzystujcych przerwanie 09h, s bardzo rozlegym
zagadnieniem. Po wyczerpujce informacje radz sign do specjalistycznej literatury dotyczcej systemu DOS.
Program, ktry poniej przedstawi, nie bdzie uaktywnia si po naciniciu
klawisza, a mimo to bdzie stale czyta klawiatur i bdzie wywoywany prze-

V. Kod wynikowy

101

rwaniem 16h. Jego zadaniem jest wygaszenie ekranu jeeli przez trzy minuty nie
zostanie nacinity aden klawisz. Program ten bdzie korzysta z kilku funkcji
napisanych w asemblerze (w tym ze zdefiniowanych wczeniej w tym rozdziale
funkcji _getch i checkch), a take ze standardowych funkcji C.
Wygaszenie ekranu moe polega na wpisaniu odpowiednich wartoci do obszaru pamici video, natomiast do wygaszenia kursora konieczne jest skorzystanie
z funkcji BIOS-u.
Poniej przedstawiam definicje dwch funkcji sucych do wygaszenia
i wywietlania kursora.
; plik kursor.asm
.MODEL SMALL
.CODE
proc _hide_cursor
mov ah,03h
mov bh,0h
int 10h
or
ch,20h
"niewidoczny"
mov ah,1h
int 10h
ret
endp

; funkcja chowa kursor

; pobierz opis kursora


; ustaw atrybuty na

; zapisz opis kursora

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 sowo opisujce ksztat i atrybuty kursora


i ustawia atrybuty tak by kursor by niewidoczny, natomiast funkcja
show_kursor przywraca widoczno kursora. W ten sposb para wywoa
funkcji:
hide_cursor();
/* ... */
show_cursor();

nie powoduje zmiany ksztatu kursora.


Jak ju wspomniaem, w programie zostan uyte take funkcje z bibliotek standardowych jzyka C. W tym przypadku bdzie to generator liczb pseudolosowych.

102

Wgb jzyka C
Wzmiankowaem wczeniej, e wiele funkcji standardowych daje zbyt dugi kod
jak na programy rezydentne. Nie dotyczy to jednak wszystkich funkcji. Jeeli
istnieje potrzeba zastosowania funkcji bibliotecznych naley wybiera funkcj
jak najniszego poziomu. Na przykad do obsugi plikw lepiej uy "bliskich
systemowi" funkcji zdefiniowanych w pliku io.h ni funkcji zdefiniowanych
w stdio.h.
Przy prbie doczania niektrych funkcji z bibliotek standardowych konsolidator moe zgosi komunikat o braku definicji pewnych symboli. Prawdopodobnie bd to zmienne definiowane w oryginalnym Startup-ie. Mona w takim
przypadku sprbowa zdefiniowania zmiennych o takich nazwach w programie
gwnym i zobaczy, czy program bdzie dziaa poprawnie (zwykle bdzie).
Wspominaem ju wczeniej, e wyjcie na ekran najlepiej realizowa
w programach rezydentnych przez bezporednie odwoania do pamici video.
Pami ekranu mona reprezentowa na wiele sposobw.
W poniszym programie zmienna screen jest zadeklarowana nastpujco:
char far *screen;

a wic pami ekranu jest reprezentowana przez daleki wskanik znakowy, co


umoliwia atwe przepisanie jego zawartoci. W razie potrzeby mona definiowa bardziej wyrafinowane struktury odwzorowujce ekran, np.
int (far *screen)[80];

W zasigu tej definicji mona odwoywa si do konkretnego znaku na ekranie


przy pomocy jego wsprzdnych, np.
screen[10][40]=0x0F41;

Naley przy tym pamita, e pierwszy indeks odpowiada wsprzdnej Y,


a starszy bajt wpisywanego sowa okrela atrybuty wywietlanego znaku.
eby jeszcze bardziej zbliy si do "normalnego spojrzenia" na ekran, mona
zdefiniowa go jako tablic struktur. Struktura bdzie opisywa kady znak na
ekranie jako kod znaku i atrybut:
typedef struct {
char code,attr;
}character;
character (far *screen)[80];

W zasigu takiej definicji mona odwoywa si zarwno do atrybutw, jak


i kodw konkretnych znakw przez ich wsprzdne, np.:
for(x=0;x<80;x++)
if(screen[10][x].code=='A')screen[10][x].attr=0x0f;
/* rozjanij litery 'A' w jedenastym wierszu od gry */

V. Kod wynikowy

103

Oto peny tekst programu saver wygaszajcego ekran, po okoo 3 minutach od


ostatniego nacinicia klawisza.
/* plik saver.c */
#include <stdlib.h>
#define MK_FP(seg,ofs) ((void far*)\
(((unsigned long)(seg) << 16 ) |
(unsigned)(ofs)))
int size=80,
paragrafach */
_acrtused=0;
Microsoft C
*/
char *autor="Adam
unsigned char far
/* zegar */
far
far

/* rozmiar TSR-a w
/* potrzebne w

Sapek
Screen Saver (C) 92$";
*timer_ticks=MK_FP(0x0040,0x006c),
*screen,
*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
wsprzdne (x,y) */
{
off=160*y+2*x;
}
void print(char *s)
/* pisz na ekranie cig
dwch znakw */
{
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
nacinito klawisz */
{ time=timer_ticks[1]; srand(*timer_ticks); } /* zapamitaj
czas posiej
*/
/* generator
liczb losowych */
if((unsigned char)(timer_ticks[1]-time)>=13)
/* czy miny
3 min. ?
*/
{

104

Wgb jzyka C
if(*(char far *)0x449==7)
pami video ?
*/
screen=MK_FP(0xb000,0x0000);
B000:0
*/
else
screen=MK_FP(0xb800,0x0000);
B000:0
*/
screen2=screen+4000;
druga strona
*/

/* gdzie
/* Hercules ->

/* VGA itp. ->


/* screen2 -

move(screen2,screen);
zawarto ekranu */

/* przepisz

hide_cursor();
kursor
*/

/* schowaj

while(checkch()==-1)
zosatnie nacinity*/
{
klawisz
*/
do{ if(rand()%11==0)direct=rand()%8;
11 krokw nowy */
x=snake[0].x+dx[direct];
*/
y=snake[0].y+dy[direct];
}while(x<0||x>=79||y<0||y>24);
tym kierunku */

/* a nie

wa

at(x,y);

print("");

*/
at(snake[0].x,snake[0].y);
at(snake[1].x,snake[1].y);
at(snake[2].x,snake[2].y);
at(snake[3].x,snake[3].y);

print("");
print("");
print("");
print(" ");

for(a=3;a>=1;a--)

/* jakis
/* rednio co
/* kierunek

/* czy mona w
/* rysuj

/* przesu wa

*/
{
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);
/* odtwrz ekran
*/
time=timer_ticks[1];
show_cursor();
/* poka kursor
*/
}
}

W celu uzyskania programu SAVER.COM, instalowanego jako program rezydentny przechwytujcy przerwanie 16h, naley skompilowa plik SAVER.C
w modelu Small, po czym poczy otrzymany plik SAVER.OBJ z odpowiednim
kodem startowym i bibliotecznymi:
tsr 16 saver getch checkch kursor



    

'?3,.21?GRF



 

You might also like