BIBL TUT










Asembler: DOS: Własne biblioteki












Pisanie własnych bibliotek w języku asembler


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), 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) i FASMa
(Flat 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:
(przeskocz przykładowy moduł biblioteki)

; 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. 18Hz
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/FASMa:
(przeskocz moduł w składni NASMa/FASMa)

; wersja NASM

global _graj_dzwiek
; w FASMie:
; format COFF
; PUBLIC _graj_dzwiek

segment biblioteka_dzwiek ; FASM: section ".text" code

_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. 18Hz
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ę
; w FASMie: AND AL, not 3
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 (TASM/FASM)
(w NASMie: global). To jest na wszelki wypadek.
Niektóre kompilatory domyślnie traktują wszystkie symbole jako publiczne, inne nie.
Jeśli w programie będziemy chcieli korzystać z takiej funkcji, należy ją zadeklarować
jako extrn (TASM/FASM) 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 rejestr CS będzie wskazywał na ten segment
Gwiazdki lub inne elementy oddzielające (tu usunięte)
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 publiczna)
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 (debugowaniu) 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, dla NASMa:
nasm -f obj -o naszplik.obj naszplik.asm
(-f - określ format pliku wyjściowego
-o - określ nazwę pliku wyjściowego)
lub, dla FASMa:
fasm naszplik.asm naszplik.obj


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
; FASM: extrn _graj_dzwiek

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

I możemy teraz zrobić:

tasm program2.asm /z /m
tlink program2.obj naszplik.obj
lub, dla NASMa:

nasm -f obj -o program2.obj program2.asm
alink program2.obj naszplik.obj -c- -oEXE -m-
lub, dla FASMa:

fasm program2.asm program2.obj
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
wsadowy tlib.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 zdobyć narzędzia:

NASM

Alink
Lib (LLIB, a nie ten z pakietu Borlanda czy
Microsoft-u):
www.dunfield.com/downloads.htm (szukaj SKLIB31.ZIP)
www2.inf.fh-rhein-sieg.de/~skaise2a/ska/sources.html
Jeśli tam go nie ma, to poszukajcie na stronach
FreeDOS-a



Kopia mojej biblioteki powinna znajdować się
na stronach, gdzie znaleźliście ten kurs.


Miłej zabawy.



Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)






Wyszukiwarka

Podobne podstrony:
BIBL TUT
DOS BIBL TUT
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