BIBL TUT




Pisanie własnych bibliotek w assemblerze











Pisanie własnych bibliotek w języku assembler

Pewnie zdarzyło się już wam usłyszeć o kimś innym:
"Ależ on(a) jest świetnym(ą) programistą(ką)! Nawet pisze własne biblioteki!"
Pokażę teraz, że nie jest to trudne, nie ważne jak przerażającym się to może wydawać.
Osoby, które przeczytają ten artykuł i zdobędą troszkę wprawy będą mogły mówić:
"Phi, a co to za filozofia pisać własne biblioteki!"

Zacznijmy więc od pytania: co powinno się znaleźć w takiej bibliotece?
Mogą to być:

Funkcje wejścia i wyjścia, podobnie jak np. w języku C
Funkcje, które już przepisywaliśmy ze 20 razy w różnych programach
Sprawdzone funkcje, napisane przez kogoś innego, których nie umielibyśmy sami
napisać, lub które "po prostu mogą się przydać"


Co to zaś jest to owa "biblioteka"?
Jest to plik (najczęściej z rozszerzeniem .lib, teraz już też .dll), na który składa
się skompilowany kod, a więc np. pliki .obj. Biblioteka eksportuje na zewnątrz nazwy
procedur w niej zawartych, aby linker wiedział, jaki adres podać programowi, który chce
skorzystać z takiej procedury.

Będę w tym artykule używał składni i linii poleceń Turbo Assemblera (TASMa) firmy Borland
z linkerem TLink i bibliotekarzem TLib oraz NASMa (Netwide Assembler) z linkerem ALink
i darmowym bibliotekarzem znalezionym w Internecie (patrz linki na dole strony).

Napiszmy więc jakiś prosty kod źródłowy. Oto on:

; wersja TASM

public _graj_dzwiek

biblioteka_dzwiek segment byte public 'bibl'
assume cs:biblioteka_dzwiek

;**************************************

_graj_dzwiek proc far



; wejście: BX = żądana częstotliwość dźwięku w Hz, co najmniej 19
; CX:DX = czas trwania dźwięku w mikrosekundach
;
; wyjście: CF = 0 - wykonane bez błędów
; CF = 1 - błąd: BX za mały



czasomierz equ 40h ;numer portu programowalnego czasomierza
klawiatura equ 60h ;numer portu kontrolera klawiatury

pushf ; zachowujemy modyfikowane rejestry
push ax
push dx
push si


cmp bx,19 ;najniższa możliwa częstotliwość to ok. 18,2 Hz
jb _graj_blad


in al,klawiatura+1 ; port B kontrolera klawiatury
or al,3 ; ustawiamy bity: 0 i 1 - włączamy głośnik i
; bramkę od licznika nr. 2 czasomierza
; do głośnika
out klawiatura+1,al



mov si,dx ;zachowujemy DX

mov dx,12h
mov ax,34ddh
div bx ;AX = 1193181 / częstotliwość, DX=reszta

mov dl,al ;zachowujemy młodszy bajt dzielnika częstotliwości



mov al,0b6h

out czasomierz+3,al ;wysyłamy komendę:
; (bity 7-6) wybieramy licznik nr. 2,
; (bity 5-4) będziemy pisać najpierw bity 0-7, potem bity 8-15
; (bity 3-1) tryb 3: generator fali kwadratowej
; (bit 0) licznik binarny 16-bitowy

mov al,dl ; odzyskujemy młodszy bajt
out czasomierz+2,al ; port licznika nr. 2 i bity 0-7 dzielnika częstotliwości
mov al,ah
out czasomierz+2,al ; bity 8-15

mov dx,si ;odzyskujemy DX


_graj_pauza:
mov ah,86h
int 15h ; pauza o długości CX:DX mikrosekund

jnc _graj_juz
dec dx
sbb cx,0 ; w razie błędu zmniejszamy CX:DX
jmp short _graj_pauza

_graj_juz:

in al,klawiatura+1
and al,not 3 ; zerujemy bity: 0 i 1 - wyłączamy głośnik i bramkę
out klawiatura+1,al

pop si ; przywracamy rejestry
pop dx
pop ax
popf
clc ; brak błędu

retf


_graj_blad:
pop si ; przywracamy rejestry
pop dx
pop ax
popf
stc ; błąd

retf

_graj_dzwiek endp

;**************************************

biblioteka_dzwiek ends
end


Teraz ten sam kod w składni NASMa:

; wersja NASM

global _graj_dzwiek

segment biblioteka_dzwiek

;**************************************

_graj_dzwiek:



; wejście: BX = żądana częstotliwość dźwięku w Hz, co najmniej 19
; CX:DX = czas trwania dźwięku w mikrosekundach
;
; wyjście: CF = 0 - wykonane bez błędów
; CF = 1 - błąd: BX za mały



czasomierz equ 40h ;numer portu programowalnego czasomierza
klawiatura equ 60h ;numer portu kontrolera klawiatury

pushf
push ax
push dx
push si


cmp bx,19 ;najniższa możliwa częstotliwość to ok. 18,2 Hz
jb _graj_blad


in al,klawiatura+1 ; port B kontrolera klawiatury
or al,3 ; ustawiamy bity: 0 i 1 - włączamy głośnik i bramkę od
; licznika nr. 2 czasomierza do głośnika
out klawiatura+1,al



mov si,dx ;zachowujemy DX

mov dx,12h
mov ax,34ddh
div bx ;AX = 1193181 / częstotliwość, DX=reszta

mov dl,al ;zachowujemy młodszy bajt dzielnika częstotliwości



mov al,0b6h

out czasomierz+3,al ;wysyłamy komendę:
; (bity 7-6) wybieramy licznik nr. 2,
; (bity 5-4) będziemy pisać najpierw bity 0-7, potem bity 8-15
; (bity 3-1) tryb 3: generator fali kwadratowej
; (bit 0) licznik binarny 16-bitowy

mov al,dl ; odzyskujemy młodszy bajt
out czasomierz+2,al ; port licznika nr. 2 i bity 0-7 dzielnika częstotliwości
mov al,ah
out czasomierz+2,al ; bity 8-15

mov dx,si ;odzyskujemy DX


_graj_pauza:
mov ah,86h
int 15h ; pauza o długości CX:DX mikrosekund

jnc _graj_juz
dec dx
sbb cx,0 ; w razie błędu zmniejszamy CX:DX
jmp short _graj_pauza

_graj_juz:

in al,klawiatura+1
and al,~3 ; zerujemy bity: 0 i 1 - wyłączamy głośnik i bramkę
out klawiatura+1,al

pop si
pop dx
pop ax
popf
clc

retf


_graj_blad:
pop si
pop dx
pop ax
popf
stc

retf

;**************************************



Jest to moja procedura wytwarzająca dźwięk w głośniczku (patrz mój inny artykuł).
Trochę tego jest, co? Ale jest tu dużo spraw, które można omówić.

Zacznijmy więc po kolei:

public... / global...
Funkcje, które mają być "widoczne na zewnątrz" tego pliku, a więc możliwe do użycia przez
inne programy, muszą być zadeklarowane jako "public" (w NASMie: global). To jest "na wszelki wypadek".
Niektóre kompilatory domyślnie traktują wszystkie symbole jako "public", inne nie.
Jeśli w programie będziemy chcieli korzystać z takiej funkcji, należy ją zadeklarować
jako "extrn" (TASM) lub "extern" (NASM).

Deklaracja segmentu
Żaden przyzwoity kompilator nie pozwoli na pisanie kodu poza jakimkolwiek segmentem
(no chyba, że domyślnie zakłada segment kodu, jak NASM).
Normalnie, w "zwykłych" programach, np typu .com, rolę tę pełni dyrektywa ".code".

assume
Mówimy kompilatorowi, że CS będzie wskazywał na ten segment
Gwiazdki
Mogą się wydawać śmieszne lub niepotrzebne, ale gdy liczba procedur w pliku zaczyna
sięgać 10-20, to NAPRAWDĘ zwiększają czytelność kodu, oddzielając procedury, dane itd.

Deklaracja procedury (wcześniej zadeklarowanej jako public)
Znak podkreślenia z przodu jest tylko po to, by w razie czego nie był identyczny z jakąś
etykietą w programie korzystającym z biblioteki. Deklaracja jest typu "far", żeby
zmienić CS na bieżący segment i uniknąć kłopotów z 64kB limitem długości skoku
(konkretnie to są to +/- 32kB).
To, czego procedura oczekuje i to, co zwraca.
Jedną procedurę łatwo zapamiętać. Ale co zrobić, gdy jest ich już 100? Analizować kod każdej,
aby sprawdzić, co robi, bo akurat szukamy takiej jednej....? No przecież nie.
Dobrą techniką programowania jest deklaracja stałych w stylu "equ" (lub #define w C).
Zamiast nic nie znaczącej liczby można użyć wiele znaczącego zwrotu, co przyda się dalej
w kodzie. I nie kosztuje to ani jednego bajtu. Oczywiście, ukrywa to część kodu (tutaj:
numery portów), ale w razie potrzeby zmienia się tą wielkość tylko w 1 miejscu, a nie
w 20.
push...
Poza wartościami zwracanymi nic nie może być zmienione! Nieprzyjemnym uczuciem byłoby
spędzenie kilku godzin przy odpluskwianiu (debugging) programu tylko dlatego, że ktoś
zapomniał zachować jakiegoś rejestru, prawda?
Sprawdzanie warunków wejścia, czy są prawidłowe. Zawsze należy wszystko przewidzieć.

Kod procedury. Z punktu widzenia tego artykułu jego treść jest dla nas nieistotna.

Punkt(y) wyjścia
Procedura może mieć dowolnie wiele punktów wyjścia. Tutaj zastosowano
dwa, dla dwóch różnych sytuacji:
parametr był dobry, procedura zakończyła się bez błędów
parametr był zły, zwróć informację o błędzie

Koniec procedury, segmentu i pliku źródłowego. Słowo "end" nie zawsze jest konieczne, ale
nie zaszkodzi. Wskazuje, gdzie należy skończyć przetwarzanie pliku.


Mamy więc już plik źródłowy. Co z nim zrobić? Skompilować, oczywiście!

tasm naszplik.asm /z /m

(/z - wypisz linię, w której wystąpił błąd
/m - pozwól na wielokrotne przejścia przez plik)
lub

nasm -f obj -o naszplik.obj naszplik.asm

(-f - określ format pilku wyjściowego
-o - określ nazwę pliku wyjściowego)

Mamy już plik "naszplik.obj". W pewnym sensie on już jest biblioteką! I można go używać w innych
programach, np. w pliku "program2.asm" mamy:

...
extrn _graj_dzwiek:far ; NASM: "extern _graj_dzwiek"

...
...
mov bx,440
mov cx,0fh
mov dx,4240h
call far ptr _graj_dzwiek ; NASM: call far _graj_dzwiek
...

I możemy teraz zrobić:

tasm program2.asm /z /m
tlink program2.obj naszplik.obj

lub

nasm -f obj -o program2.obj program2.asm
alink program2.obj naszplik.obj -c- -oEXE -m-

a linker zajmie się wszystkim za nas - utworzy plik "program2.exe", zawierający także
"naszplik.obj". Jaka z tego korzyść? Plik "program2.asm" może będzie zmieniany w przyszłości
wiele razy, ale "naszplik.asm/.obj" będzie ciągle taki sam. A w razie chęci zmiany procedury
_graj_dzwiek wystarczy ją zmienić w 1 pliku i tylko jego ponownie skompilować, bez potrzeby
wprowadzania tej samej zmiany w kilkunastu innych programach. Te programy wystarczy
tylko ponownie skompilować z nową "biblioteką", bez jakichkolwiek zmian kodu.


No dobra, ale co z plikami .lib?
Otóż są one odpowiednio połączonymi plikami .obj. I wszystko działa tak samo.
No ale jak to zrobić?
Służą do tego specjalne programy, nazywane "librarian" (bibliotekarz). W pakiecie TASMa
znajduje się program "tlib.exe". Jego właśnie użyjemy (działa jak LLIB i wszystko
robimy tak samo). Pliki .obj, które chcemy połączyć w
bibliotekę można podawać na linii poleceń, ale jest to męczące, nawet jeśli napisze się plik
".bat" uruchamiający tlib. My skorzystamy z innego rozwiązania.
Programowi można na linii poleceń podać, aby komendy czytał z jakiegoś pliku. I to właśnie
zrobimy. Piszemy plik "tlib.bat":

tlib.exe naszabibl.lib @lib.txt

i plik "lib.txt" (zwykłym notatnikiem):

+- ..\obj\pisz.obj &
+- ..\obj\wej.obj &
+- ..\obj\procesor.obj &
+- ..\obj\losowe.obj &
+- ..\obj\f_pisz.obj &

+- ..\obj\dzwiek.obj &
+- ..\obj\f_wej.obj &
+- ..\obj\fn_pisz.obj &
+- ..\obj\fn_wej.obj

(użyłem tutaj nazw modułów, które składają się na moją bibliotekę).
"+-" oznacza 'zamień w pliku dany moduł'
"&" oznacza 'sprawdzaj jeszcze w kolejnej linijce'
Przy pierwszym tworzeniu można użyć "+" zamiast "+-", aby uniknąć ostrzeżeń o uprzedniej
nieobecności danego modułu w bibliotece.
Teraz uruchamiamy już tylko "tlib.bat" a w razie potrzeby zmieniamy tylko "lib.txt".


Gdzie zbobyć narzędzia:

NASM, The Netwide Assembler: http://nasm.sourceforge.net
Alink, http://alink.sf.net/
Lib (LLIB, a nie ten z pakietu Borlanda czy MicroSoftu):

http://www.dunfield.com/downloads.htm (szukaj SKLIB31.ZIP)

http://www2.inf.fh-rhein-sieg.de/~skaise2a/ska/sources.html
Jeśli tam go nie ma, to poszukajcie na stronach FreeDOSa:
http://www.freedos.org



To chyba wszystko, o czym chciałem powiedzieć. Kopia mojej biblioteki powinna znajdować się
na stronach, gdzie znaleźliście ten kurs.


Miłej zabawy.




Wyszukiwarka

Podobne podstrony:
BIBL TUT
BIBL TUT
DOS BIBL TUT
ART121 tut 2
phys tut 08
wil pl inst bibl publprac
phys tut 12
DOS DIOD TUT
SYS TUT
ART121 tut 3
MYSZ TUT
PWR TUT
bibl zamowienia publiczne w prawie ue
GRAF TUT
tut?2 sdram vhdl

więcej podobnych podstron