To je vodič in zbirka nasvetov za uporabo zbirniške (assembly language) kode v QuickBASIC programih z uporabo knjižnice (library). Osredotočil se bom na NASM, toda programerske tehnike delujejo z vsakim zbirnikom (assemblerjem). Pozor! To ni vodič za učenje zbirniškega jezika. Če imate kakšna vprašanja, me vprašajte.
Glavni vir za ta vodič je bila objava uporabnika po imenu "Artelius".
Ustvarite datoteko z besedilom, dajte ji končnico ASM, in jo odprite. Vanjo skopirajte naslednjo predlogo:
GLOBAL MojaProcedura
GROUP DGROUP
SECTION CODE
MojaProcedura:
; vstavite kodo tukaj
Analizirajmo jo. Na začetku se s smernico GLOBAL
določijo procedure (oznake, oz. labeli), ki bodo dostopne QuickBASIC programom. Vsaka procedura mora biti v svoji vrstici s svojo smernico GLOBAL
. Vrstica GROUP DGROUP
povezovalniku (linkerju) pove, da bo koda dostopala do spremenljivk v privzetem segmentu, ki ga QuickBASIC imenuje DGROUP (toda še vedno lahko dostopate do podatkov v drugih segmentih; glejte spodaj). SECTION CODE
označuje začetek dejanske kode. Potem lahko začnete pisati procedure.
Vse procedure (tako SUB procedure kot funkcije - FUNCTION) morajo slediti naslednji obliki (takoj za oznako):
push bp
mov bp, sp
; sem vstavite telo procedure
pop bp
retf x
Vsi ukazi razen RETF
pravzaprav niso potrebni, toda če želite dostopati do parametrov, ki vam jih posreduje QuickBASIC (oz. jih spreminjati, če so podani kot kazalci), jih boste potrebovali. Zamenjajte x za RETF
s številom parametrov, ki so vam posredovani, množeno z dva (ker so 16-bitni). Za več informacij glede funkcij in parametrov glejte Napredne tehnike spodaj.
Vaša koda je sedaj pripravljena na prevajanje in povezovanje. Ustvarite batch file z naslednjo vsebino:
@DEL %1.LIB
N:\NASM -o %1.OBJ -f obj %1.ASM
T:\LINK /Q %1.OBJ, %1.QLB,,T:\BQLB45.LIB;
T:\LIB %1.LIB +%1.OBJ
Zamenjajte N:\ in T:\ z lokacijama, kjer sta NASM in QuickBASIC, ali pa uporabite ukaz SUBST (DOS/Windows) ali MOUNT (DOSBox), da ustvarite navidezne pogone, ki kažejo nanju. Zdaj, ko ste ustvarili to datoteko, lahko vsakič, ko želite narediti NASM knjižnico, vtipkate ASMLINK imeKnjižnice
in dobili boste knjižnico, nared za uporabo.
Preden lahko QuickBASIC uporablja katerokoli izmed procedur v vaši knjižnici, morajo biti te deklarirane. Priporočam vam, da si ustvarite datoteko BI, ki bo vsebovala te deklaracije. Te uporabljajo enako sintakso, kot če bi deklarirali BASICovske procedure (z ukazom DECLARE), toda nekatere parametre boste želeli posredovati po vrednosti (BYVAL
), če jih ne boste spreminjali. Ko ste napisali in vključili ($INCLUDE) svojo datoteko BI, lahko svoje procedure začnete uporabljati.
To je zbirka nasvetov, ki razložijo reči, ki že niso bile razložene zgoraj.
V splošnem lahko uporabljate vse registre.
Če je vaša procedura funkcija, se AX in DX uporabljata za vračanje vrednosti (glej spodaj).
QuickBASIC pričakuje, da bodo SI, DI, SP, BP, DS (se mi zdi), SS, in CS (razumljivo) na koncu procedure vsebovali enake vrednosti kot pred vstopom vanjo. Te registre lahko shranite na sklad (stack; PUSH) in jih nato prikličete (POP), toda včasih boste morali ohraniti SP, ker bo uničen. Lahko uporabite MOV ES, SP
namesto PUSH in MOV SP, ES
namesto POP (pravzaprav bo deloval vsak 16-bitni register, toda jaz uporabljam ES najmanj od vseh). Mislim, da QuickBASIC ne pričakuje, da bo ES ohranjen; ta tehnika je doslej zame delovala.
Začetne vrednosti registrov niso določene.
Da lahko vaša procedura vrne vrednost, jo mora datoteka BI deklarirati kot funkcijo (FUNCTION). Postopek vračanja pa je odvisen od podatkovnega tipa:
Sledi primer funkcije, ki vrne INTEGER:
GLOBAL VrniPrivzetiPogon
GROUP DGROUP
SECTION CODE
VrniPrivzetiPogon:
; Primer vračanja trenutnega privzetega pogona. Deklaracija:
; DECLARE FUNCTION VrniPrivzetiPogon% ()
mov ah, 19h ; DOS funkcija
int 21h
mov ah, 0 ; Zavrzi višji bajt
add al, 65 ; Prevori v ASCII
retf ; Vrni se v QB (rezultat je v AX)
QuickBASIC parametre posreduje na skladu. Dobite jih lahko s premiki (displacements), relativnimi na BP. Zadnji parameter ima najmanjši premik, ki je vedno enak 6 (besedi pod njo sta CS in IP pred vstopom v vašo proceduro). Na primer: če posredujete a%, b%, in c%, bo imel a% premik +6, b% +8, in c% +10. Premiki so vedno sodi, toda velikost parametrov je odvisna od njihovih tipov in načina posredovanja (po vrednosti ali po kazalcu). Lahko tudi mešate parametre, ki so posredovani po vrednosti in kazalcu; ni treba, da so vsi enaki. Ko se vaša procedura zaključi, mora imeti RETF ustrezen operand (glej Procedure zgoraj).
Posredovanje po vrednosti je, kar se branja tiče, najlažje. To velja še posebej za vrednosti tipa INTEGER. To storite tako, da vstavite BYVAL pred njihovim imenom v ukazu DECLARE v vaši datoteki BI. Tako posredovanih parametrov ne morete spreminjati, lahko pa jih berete neposredno, če jih premaknete v registre. Po vrednosti lahko posredujete le tipe INTEGER, LONG, SINGLE, in DOUBLE. Sledi primer branja spremenljivke tipa INTEGER po vrednosti:
GLOBAL IzpisiZnak
GROUP DGROUP
SECTION CODE
IzpisiZnak:
; Primer branja in izpisovanja znaka, posredovanega po vrednosti. Deklaracija:
; DECLARE SUB IzpisiZnak (BYVAL Koda%)
; Postavi sklad
push bp
mov bp, sp
mov ah, 2 ; DOS funkcija (izpis znaka)
mov dx, [bp+6] ; Znak, ki naj bo izpisan (višji bajt se ignorira)
int 21h ; Izpiši ga
; Nazaj v QB
pop bp
retf 2
Če mu ne ukažemo drugače (z BYVAL), QuickBASIC ne posreduje dejanskih vrednosti parametrov, temveč le kazalce nanje. Tako jih lahko tudi spreminjate. To ima tudi lep stranski učinek; ker dobite njihove odmike, vsak od njih zasede dva bajta na skladu. Branje vrednosti pa zahteva malo več dela: najprej morate premakniti odmik s sklada v register, šele potem lahko ta register uporabite kot kazalec. Sledi primer dostopanja do spremenljivke tipa INTEGER po kazalcu:
GLOBAL Povecaj
GROUP DGROUP
SECTION CODE
Povecaj:
; Primer povečevanja INTEGER spremenljivke po kazalcu. Deklaracija:
; DECLARE SUB Povecaj (Kaj%)
; Postavi sklad
push bp
mov bp, sp
mov bx, [bp+6] ; Preberi kazalec
inc word [bx] ; Povečaj spremenljivko
; Nazaj v QB
pop bp
retf 2
Da dobite oddaljen parameter, najprej po vrednosti posredujte njegov segment (dobite ga s funkcijo VARSEG), nato pa ga posredujte s kazalcem kot ponavadi. Obstaja še en način, in sicer z uporabo SEG (namesto BYVAL; tako vam ni treba ročno posredovati segmenta), toda tega še nisem preizkusil.
Opomba: sledeče informacije veljajo za QuickBASIC 4.5 in morda še nekatere druge verzije, toda tega nisem preizkusil. Ne veljajo za QuickBASIC 7.1 (najbrž tudi ne za Visual Basic za DOS), ker ta shranjuje nize na drugačen način.
Branje nizov (spreminjanja ne bom obravnaval; glej Klicanje notranjih procedur QB spodaj), ne glede, ali so dinamični ali fiksne velikosti (fixed-length; ti se začasno pretvorijo v dinamične nize), poteka na sledeč način: QuickBASIC vam posreduje odmik opisa niza (string descriptor). To je 4-bajtna struktura, ki vsebuje (v tem vrstnem redu) dolžino niza in kazalec (odmik) na njegovo vsebino. Oba sta 16 bitov široka. Lahko pa tudi ročno posredujete dolžino niza (dobite jo s funkcijo LEN) in odmik (dobite ga s funkcijo SADD). Zdaj, ko to veste, lahko uporabite indeksni register, da pokažete na niz in CX (ali katerikoli drug register) kot števec, in se v zanki premikate čez niz. Sledi primer branja "bližnjega" niza:
GLOBAL IzpisiNiz
GROUP DGROUP
SECTION CODE
IzpisiNiz:
; Primer izpisovanja QuickBASIC niza. Deklaracija:
; DECLARE SUB IzpisiNiz (Kaj$)
; Postavi sklad
push bp
mov bp, sp
push si ; SI moramo ohraniti
mov si, [bp+6] ; Preberi kazalec na opis niza
mov cx, [si] ; Preberi dolžino niza
mov bx, [si+2] ; Preberi kazalec na vsebino niza
mov ah, 2 ; DOS funkcija (izpis znaka)
.naslednji:
mov dl, [bx] ; Preberi znak
int 21h ; Izpiši ga
inc bx ; Pojdi na naslednjega
dec cx ; Zmanjšaj števec
jnz .naslednji ; Če še ni konec niza, ponovi
; Nazaj v QB
pop si
pop bp
retf 2
Uporabniški tipi (user-defined types) so le seznam navadnih spremenljivk, ki so shranjene ena za drugo. Izjema so nizi, ki so vedno fiksni in ne uporabljajo opisov; so le surovi bajti, toda brez kakršnegakoli načina, da bi dobili njihovo dolžino (vedeti jo morate vnaprej). Takšne strukture so vedno "oddaljene"; glejte Dostopanje do "oddaljenih" parametrov (zunaj DGROUP) zgoraj.
Polja (arrays) so očitno vedno shranjena "daleč" in najprej s stolpcem (column-major; toda mislim, da se prevajalnik da prisiliti, da najprej shrani vrstice). So le surovi seznami podatkov. Polja dinamičnih nizov so seznami opisov nizov in polja fiksnih nizov so le en fiksen niz za drugim brez podatka o dolžini (vedeti jo morate vnaprej). Da dobite začetek polja, uporabite funkcijo VARPTR s prvim elementom v polju.
Iz svojih procedur lahko tudi kličete procedure, ki jih QuickBASIC uporablja za svoje ukaze in funkcije. Tega še nisem preizkusil. Da jih uporabite, jih deklarirajte s smernico EXTERN med vrsticama GROUP DGROUP
in SECTION CODE
. Na primer: če želite deklarirati B$SCAT, ki jo QuickBASIC uporablja za združevanje nizov (concatenation), vstavite EXTERN B$SCAT
. Če želite raziskati, kako te procedure delujejo in kakšne parametre zahtevajo, vam priporočam, da prevedete nekaj programov s parametrom /A
(na ukazni vrstici; prevajalnik se pokliče z ukazom BC), da dobite zbirniško kodo za ta program.