   #Start Contents

                Pisanie wlasnych bibliotek w jezyku asembler

   Pewnie zdarzylo sie juz wam uslyszec o kims innym:
   "Alez on(a) jest swietnym(a) programista(ka)! Nawet pisze wlasne
   biblioteki!"
   Pokaze teraz, ze nie jest to trudne, nie wazne jak przerazajacym sie
   to moze wydawac. Osoby, ktore przeczytaja ten artykul i zdobeda
   troszke wprawy beda mogly mowic:
   "Phi, a co to za filozofia pisac wlasne biblioteki!"

   Zacznijmy wiec od pytania: co powinno sie znalezc w takiej bibliotece?
   Moga to byc:
     * Funkcje wejscia i wyjscia, podobnie jak na przyklad w jezyku C
     * Funkcje, ktore juz przepisywalismy ze 20 razy w roznych programach
     * Sprawdzone funkcje, napisane przez kogos innego, ktorych nie
       umielibysmy sami napisac, lub ktore "po prostu moga sie przydac"

   Co to zas jest to owa "biblioteka"?
   Jest to plik (najczesciej z rozszerzeniem .lib), na ktory sklada sie
   skompilowany kod, a wiec na przyklad pliki .obj. Biblioteka eksportuje
   na zewnatrz nazwy procedur w niej zawartych, aby linker wiedzial, jaki
   adres podac programowi, ktory chce skorzystac z takiej procedury.

   Bede w tym artykule uzywal skladni i linii polecen 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 wiec jakis prosty kod zrodlowy. Oto on:
   (przeskocz przykladowy modul biblioteki)
; wersja TASM

public _graj_dzwiek

biblioteka_dzwiek       segment byte    public "bibl"
assume cs:biblioteka_dzwiek

_graj_dzwiek            proc    far



; wejscie:      BX = zadana czestotliwosc dzwieku w Hz, co najmniej 19
;               CX:DX = czas trwania dzwieku w mikrosekundach
;
; wyjscie:      CF = 0 - wykonane bez bledow
;               CF = 1 - blad: BX za maly



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               ;najnizsza mozliwa czestotliwosc to ok. 18Hz
        jb _graj_blad


        in   al,klawiatura+1    ; port B kontrolera klawiatury
        or   al,3               ; ustawiamy bity: 0 i 1 - wlaczamy glosnik i
                                ; bramke od licznika nr. 2 czasomierza
                                ; do glosnika
        out  klawiatura+1,al



        mov si,dx               ;zachowujemy DX

        mov dx,12h
        mov ax,34ddh
        div bx                  ;AX = 1193181 / czestotliwosc, DX=reszta

        mov dl,al               ;zachowujemy mlodszy bajt dzielnika
                                ; czestotliwosci



        mov al,0b6h

        out czasomierz+3,al     ;wysylamy komende:
                                ; (bity 7-6) wybieramy licznik nr. 2,
                                ; (bity 5-4) bedziemy pisac 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 mlodszy bajt
        out czasomierz+2,al     ; port licznika nr. 2 i bity 0-7 dzielnika
                                ; czestotliwosci
        mov al,ah
        out czasomierz+2,al     ; bity 8-15

        mov dx,si               ;odzyskujemy DX


_graj_pauza:
        mov ah,86h
        int 15h                 ; pauza o dlugosci CX:DX mikrosekund

        jnc _graj_juz
        dec dx
        sbb cx,0                ; w razie bledu zmniejszamy CX:DX
        jmp short _graj_pauza

_graj_juz:

        in   al,klawiatura+1
        and  al,not 3           ; zerujemy bity: 0 i 1 - wylaczamy glosnik
                                ; i bramke
        out  klawiatura+1,al

        pop si                  ; przywracamy rejestry
        pop dx
        pop ax
        popf
        clc                     ; brak bledu

        retf


_graj_blad:
        pop si                  ; przywracamy rejestry
        pop dx
        pop ax
        popf
        stc                     ; blad

        retf

_graj_dzwiek            endp

biblioteka_dzwiek       ends
end
     _________________________________________________________________

   Teraz ten sam kod w skladni NASMa/FASMa:
   (przeskocz modul w skladni NASMa/FASMa)
; wersja NASM

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

segment biblioteka_dzwiek       ; FASM: section ".text" code

_graj_dzwiek:



; wejscie:      BX = zadana czestotliwosc dzwieku w Hz, co najmniej 19
;               CX:DX = czas trwania dzwieku w mikrosekundach
;
; wyjscie:      CF = 0 - wykonane bez bledow
;               CF = 1 - blad: BX za maly



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

        pushf
        push ax
        push dx
        push si


        cmp bx,19               ;najnizsza mozliwa czestotliwosc to ok. 18Hz
        jb _graj_blad


        in   al,klawiatura+1    ; port B kontrolera klawiatury
        or   al,3               ; ustawiamy bity: 0 i 1 - wlaczamy glosnik i
                                ; bramke od licznika nr. 2 czasomierza
                                ; do glosnika
        out  klawiatura+1,al



        mov si,dx               ;zachowujemy DX

        mov dx,12h
        mov ax,34ddh
        div bx                  ;AX = 1193181 / czestotliwosc, DX=reszta

        mov dl,al               ;zachowujemy mlodszy bajt dzielnika
                                ; czestotliwosci



        mov al,0b6h

        out czasomierz+3,al     ;wysylamy komende:
                                ; (bity 7-6) wybieramy licznik nr. 2,
                                ; (bity 5-4) bedziemy pisac 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 mlodszy bajt
        out czasomierz+2,al     ; port licznika nr. 2 i bity 0-7 dzielnika
                                ; czestotliwosci
        mov al,ah
        out czasomierz+2,al     ; bity 8-15

        mov dx,si               ;odzyskujemy DX


_graj_pauza:
        mov ah,86h
        int 15h                 ; pauza o dlugosci CX:DX mikrosekund

        jnc _graj_juz
        dec dx
        sbb cx,0                ; w razie bledu zmniejszamy CX:DX
        jmp short _graj_pauza

_graj_juz:

        in   al,klawiatura+1
        and  al,~3              ; zerujemy bity: 0 i 1 - wylaczamy glosnik
                                ; i bramke
                                ; 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 wytwarzajaca dzwiek w glosniczku (patrz artykul
   o programowaniu glosniczka). Troche tego jest, co? Ale jest tu duzo
   spraw, ktore mozna omowic.

   Zacznijmy wiec po kolei:
    1. public... / global...
       Funkcje, ktore maja byc "widoczne na zewnatrz" tego pliku, a wiec
       mozliwe do uzycia przez inne programy, musza byc zadeklarowane
       jako "public" (TASM/FASM) (w NASMie: "global"). To jest "na
       wszelki wypadek". Niektore kompilatory domyslnie traktuja
       wszystkie symbole jako publiczne, inne nie. Jesli w programie
       bedziemy chcieli korzystac z takiej funkcji, nalezy ja
       zadeklarowac jako "extrn" (TASM/FASM) lub "extern" (NASM).
    2. Deklaracja segmentu
       Zaden przyzwoity kompilator nie pozwoli na pisanie kodu poza
       jakimkolwiek segmentem (no chyba, ze domyslnie zaklada segment
       kodu, jak NASM). Normalnie, w "zwyklych" programach, np typu .com,
       role te pelni dyrektywa ".code".
    3. assume
       Mowimy kompilatorowi, ze rejestr CS bedzie wskazywal na ten
       segment
    4. Gwiazdki lub inne elementy oddzielajace (tu usuniete)
       Moga sie wydawac smieszne lub niepotrzebne, ale gdy liczba
       procedur w pliku zaczyna siegac 10-20, to NAPRAWDE zwiekszaja
       czytelnosc kodu, oddzielajac procedury, dane itd.
    5. Deklaracja procedury (wczesniej zadeklarowanej jako publiczna)
       Znak podkreslenia z przodu jest tylko po to, by w razie czego nie
       byl identyczny z jakas etykieta w programie korzystajacym z
       biblioteki. Deklaracja jest typu "far", zeby zmienic CS na biezacy
       segment i uniknac klopotow z 64kB limitem dlugosci skoku
       (konkretnie to sa to +/- 32kB).
    6. To, czego procedura oczekuje i to, co zwraca.
       Jedna procedure latwo zapamietac. Ale co zrobic, gdy jest ich juz
       100? Analizowac kod kazdej, aby sprawdzic, co robi, bo akurat
       szukamy takiej jednej....? No przeciez nie.
    7. Dobra technika programowania jest deklaracja stalych w stylu "equ"
       (lub #define w C).
       Zamiast nic nie znaczacej liczby mozna uzyc wiele znaczacego
       zwrotu, co przyda sie dalej w kodzie. I nie kosztuje to ani
       jednego bajtu. Oczywiscie, ukrywa to czesc kodu (tutaj: numery
       portow), ale w razie potrzeby zmienia sie te wielkosc tylko w 1
       miejscu, a nie w 20.
    8. push...
       Poza wartosciami zwracanymi nic nie moze byc zmienione!
       Nieprzyjemnym uczuciem byloby spedzenie kilku godzin przy
       odpluskwianiu (debugowaniu) programu tylko dlatego, ze ktos
       zapomnial zachowac jakiegos rejestru, prawda?
    9. Sprawdzanie warunkow wejscia, czy sa prawidlowe. Zawsze nalezy
       wszystko przewidziec.
   10. Kod procedury. Z punktu widzenia tego artykulu jego tresc jest dla
       nas nieistotna.
   11. Punkt(y) wyjscia
       Procedura moze miec dowolnie wiele punktow wyjscia. Tutaj
       zastosowano dwa, dla dwoch roznych sytuacji:
         1. parametr byl dobry, procedura zakonczyla sie bez bledow
         2. parametr byl zly, zwroc informacje o bledzie
   12. Koniec procedury, segmentu i pliku zrodlowego. Slowo "end" nie
       zawsze jest konieczne, ale nie zaszkodzi. Wskazuje, gdzie nalezy
       skonczyc przetwarzanie pliku.

   Mamy wiec juz plik zrodlowy. Co z nim zrobic? Skompilowac, oczywiscie!
        tasm naszplik.asm /z /m

   (/z - wypisz linie, w ktorej wystapil blad
   /m - pozwol na wielokrotne przejscia przez plik)
   lub, dla NASMa:
        nasm -f obj -o naszplik.obj naszplik.asm

   (-f - okresl format pliku wyjsciowego
   -o - okresl nazwe pliku wyjsciowego)
   lub, dla FASMa:
        fasm naszplik.asm naszplik.obj

   Mamy juz plik "naszplik.obj". W pewnym sensie on juz jest biblioteka!
   I mozna go uzywac w innych programach, na przyklad 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 mozemy teraz zrobic:
        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 sie wszystkim za nas - utworzy plik "program2.exe",
   zawierajacy takze "naszplik.obj". Jaka z tego korzysc? Plik
   "program2.asm" moze bedzie zmieniany w przyszlosci wiele razy, ale
   "naszplik.asm/.obj" bedzie ciagle taki sam. A w razie checi zmiany
   procedury _graj_dzwiek wystarczy ja zmienic w 1 pliku i tylko jego
   ponownie skompilowac, bez potrzeby wprowadzania tej samej zmiany w
   kilkunastu innych programach. Te programy wystarczy tylko ponownie
   skompilowac z nowa "biblioteka", bez jakichkolwiek zmian kodu.

   No dobra, ale co z plikami .lib?
   Otoz sa one odpowiednio polaczonymi plikami .obj. I wszystko dziala
   tak samo.
   Ale jak to zrobic?
   Sluza do tego specjalne programy, nazywane "librarian" (bibliotekarz).
   W pakiecie TASMa znajduje sie program "tlib.exe". Jego wlasnie uzyjemy
   (dziala jak LLIB i wszystko robimy tak samo). Pliki .obj, ktore chcemy
   polaczyc w biblioteke mozna podawac na linii polecen, ale jest to
   meczace, nawet jesli napisze sie plik wsadowy "tlib.bat" uruchamiajacy
   tlib. My skorzystamy z innego rozwiazania.
   Programowi mozna na linii polecen podac, aby komendy czytal z jakiegos
   pliku. I to wlasnie zrobimy. Piszemy plik "tlib.bat":
        tlib.exe naszabibl.lib @lib.txt

   i plik "lib.txt" (zwyklym edytorem tekstu):
        +- ..\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

   (uzylem tutaj nazw modulow, ktore skladaja sie na moja biblioteke).
   "+-" oznacza "zamien w pliku dany modul"
   "&" oznacza "sprawdzaj jeszcze w kolejnej linijce"
   Przy pierwszym tworzeniu mozna uzyc "+" zamiast "+-", aby uniknac
   ostrzezen o uprzedniej nieobecnosci danego modulu w bibliotece.
   Teraz uruchamiamy juz tylko "tlib.bat" a w razie potrzeby zmieniamy
   tylko "lib.txt".

   Gdzie zdobyc narzedzia:
    1. NASM
    2. Alink
    3. 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
       Jesli tam go nie ma, to poszukajcie na stronach FreeDOS-a

   Kopia mojej biblioteki powinna znajdowac sie na stronach, gdzie
   znalezliscie ten kurs.

   Milej zabawy.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
