Ahhoz, hogy valaki ezt a részt megértse, feltétlenül szükséges az Assembly nyelv ismerete. Előző két számunk mondhatnám gyerekjáték volt. Így talán térjünk inkább néhány komolyabb alkalmazásra. Említettem, hogy néhány parancsot kihagytak az alap QB-ből. Pont a legfontosabbakat. Ha ezeket használni akarjuk "QB.exe /L" kell indítani. És ha ezt megtettük elénk tárul néhány igazán hatékony utasítás.

CALL ABSOLUTE (par1 AS AKARMI, par2 AS AKARMI, ofsz AS INTEGER)

Gépi kódú programot futtathatunk vele. Akármennyi paramétert adhatunk át, az utolsó és kötelező paraméternek a szegmensen belüli offszetnek kell lennie, a szegmenset a DEF SEG-gel állíthatunk be és a VARSEG-gel kérdezhetjük le a sztringet vagy tömböt. Attól függően, hogy sztringet vagy tömböt használunk, SADD(sztring AS STRING)-ot vagy VARPTR(tomb(1) )-rel kapjuk meg az offszetjét. QB távoli hívással hívja meg, ez tehát azt jeleni, hogy RET FAR-ral térünk a rutinból vissza. A paraméterátadás kétféleképpen történhet. Vagy közeli (2 bájtos) mutatót nyom le a stackre, vagy pedig a változó érékét, de ehhez ezt jelezni kell a BYVAL kulcsszóval. Egy hátránya van, ha BYVAL-lal adunk át paramétereket: csak odafelé működik, vissza nem. Vagyis a gépi rutinból nem adhatunk így át azon a változón értéket. A paramétereket balról jobbra nyomja a veremre, tehát a legbaloldalibb lesz a legelső, legfölső elem. Íme egy egérkezelő rutin.

RESTORE MouseData
Mouse$ = SPACE$(57)
FOR i = 1 TO 57
  READ A$
  H$ = CHR$(VAL("&H" + A$))
  MID$(Mouse$, i, 1) = H$
NEXT i
DEF SEG = VARSEG(gepikod$)
CALL ABSOLUTE (szam%, valos#, szoveg$, SADD(gepikod$))

MouseData:
DATA 55,89,E5,8B,5E,0C,8B,07,50,8B,5E,0A,8B,07,50,8B
DATA 5E,08,8B,0F,8B,5E,06,8B,17,5B,58,1E,07,CD,33
DATA 53,8B,5E,0C,89,07,58,8B,5E,0A,89,07
DATA 8B,5E,08,89,0F,8B,5E,06,89,17,5D,CA,08,00

CALL INTERRUPT(intnum AS INTEGER, inreg AS USR, outreg AS USR)
CALL INTERRUPTX(intnum, inreg, outreg)

A kettő között csak annyi különbség van, hogy az INTERRUPTX használhatja a DS és ES szegmensregisztereket, az INTERRUPT nem. Az intnum a megszakítás száma. A USR egy a felhasználó által definiált típus. Ez tárolja a processzor regisztereit.

TYPE RegtypeX
  ax AS INTEGER
  bx AS INTEGER
  cx AS INTEGER
  dx AS INTEGER
  bp AS INTEGER
  si AS INTEGER
  di AS INTEGER
  flags AS INTEGER
  ds AS INTEGER ' ezt a kettot csak az INTERRUPTX
  es AS INTEGER
END TYPE

Figyelem! A regisztereknek olyan nevet adhatunk, amilyent csak kívánunk (például a cx-nek counterreg vagy akármi), de a a regiszterek sorrendjének meg kell egyeznie, különben könnyen megeshet, hogy nem annak a regiszternek adtuk azt az értéket és elszáll a program, s újraindíthatjuk a gépet.

Ha lusták lennénk beírni az előző részt, helyette inkább csak ezt az egy sort:

'$INCLUDE : 'QB.BI' vagy REM $INCLUDE : 'QB.BI'
Ekkor viszont csak úgy használhatjuk a regisztereket, ahogy az a fájlban van, ahogy azt én is tettem az elején.
 

Néhány példa:

'$INCLUDE: 'QB.BI'
DIM inreg AS RegTypeX, outreg AS RegTypeX
szoveg$= "Ezt latjuk a kepernyon, a megszakitas hivasa utan." + "$"
inreg.ax=&H900
inreg.ds=VARSEG(szoveg$)
inreg.dx=SADD(szoveg$)
CALL INTERRUPTX(&H21, inreg, outreg)
END

Másik:

'$INCLUDE: 'qb.bi'
DIM inreg AS regtypex, outreg AS regtypex
puffer$=CHR$(255)+SPACE$(256) ' Az elso bájt a beolvasott karakterek szamat jelenti.
inreg.ax= &HA00 ' Billentyuzet pufferelt beolvasasa.
inreg.ds= VARSEG(puffer$)
inreg.dx= SADD(puffer$)
CALL INTERRUPTX(&H21, inreg, outreg)
LOCATE 20, 1
PRINT puffer$ ' Az elso ket karaktert
END

Végre használhatjuk az egeret is:

'$INCLUDE : 'qb.bi'
DIM inreg AS RegtypeX, outreg AS RegtypeX
inreg.ax=&H3
CALL INTERRUPTX(&H33, inreg, outreg)
xloc%= outreg.cx
yloc%= outreg.dx
gombok%= outreg.bx

még egy érdekes felhasználása:

FOR i=0 TO 10*16
  READ bájt%
  s$=s$+bájt%
NEXT i
inreg.ax= &H1100
inreg.bx= &H1000
inreg.cx= &HA
inreg.dx= &H30
inreg.es= VARSEG(s$)
inreg.bp= SADD(s$)
CALL INTERRUPTX(&H10, inreg, outreg)
' A szamok kepei 0-9
DATA 0,124,130,130,130,130,130,130,0,130,130,130,130,130,130,124 : ' Nulla
DATA 0,0,2,2,2,2,2,2,0,2,2,2,2,2,2,0 : ' Egy
DATA 0,124,2,2,2,2,2,2,124,128,128,128,128,128,128,124 : ' Ketto
DATA 0,124,2,2,2,2,2,2,124,2,2,2,2,2,2,124 : ' Harom
DATA 0,0,130,130,130,130,130,130,124,2,2,2,2,2,2,0 : ' Negy
DATA 0,124,128,128,128,128,128,128,124,2,2,2,2,2,2,124 : ' Ot
DATA 0,124,128,128,128,128,128,128,124,130,130,130,130,130,130,124 : ' Hat
DATA 0,124,2,2,2,2,2,2,0,2,2,2,2,2,2,0 : ' Het
DATA 0,124,130,130,130,130,130,130,124,130,130,130,130,130,130,124 : ' Nyolc
DATA 0,124,130,130,130,130,130,130,124,2,2,2,2,2,2,124 : ' Kilenc

WAIT ioport, maszk-and1, maszk-xor2

Addig feltartja a program futását, amíg a portról beolvasott érték és a maszkértékek között végrehajtott művelet eredménye hamis, vagyis 0. Ha igaz, bármilyen érték, akkor a következő utasításra ugrik. Ezzel is óvatosan bánjunk, mert ha az a bit nem billen be és így végtelen ciklusba kerülünk, akkor nem segít a ctrl-break sem. Az első operandusa a port címe, a második operandus értéke a portról kiolvasottal and műveletet hajt végre, és ha van harmadik is még xor műveletet is csinál. Ha az utolsó operandust (xor) elhagyjuk, 0 értékkel xor-ol. Felhasználási területe sokféle. A függőleges visszatérés figyelésére használják, mivel ott biztos bebillennek, nem kell félnünk a lefagyástól.

WAIT &H3DA, 8 [ ,8]

Bárhol használhatjuk, ahol a DO-INP-LOOP lassú a port figyelésére.

Saját könyvtár (library) létrehozása

QuickLibraryt a LINK.EXE-vel hozhatunk létre. Ez a fájl az interpreternek kell. Ha a programunkat *.EXE-re lefordítjuk, akkor szükségünk lesz a *.LIB kiterjesztésű könyvtárra. Azért is jobb így könyvtárral dolgozni, mert Assemblyben megírjuk vagy megírja valaki a rutint, lefordítja, és már használhatjuk is. A CALL ABSOLUTE-nak az a hátránya, hogy az opkódokat kell kézileg beirkálni a DATA sorba. Megoldhatnánk ezt mondjuk ha .com fájlként elmentjük és ezt a fájt megnyitva töltjük be a memóriába és ott végrehajtjuk, de ez talán még bonyolultabbnak tűnik. A megoldást a könyvtár jelenti.

Nem kell elmentenünk az ált. regisztereket (ax, bx, cx, dx) ez a tapasztalatom, azonban a szegmensregisztereket (cs, ds, es, ss) és a (sp, bp) mentsük el, ha megváltoztattuk.

Ha úgy gondoljuk kész vagyunk a forrásszöveggel, azt TASM-al fordítsuk obj-ra, azután pedig

LINK /q *.obj
.386
.code
PUBLIC RDTSC
RDTSC proc
  PUSH BP
  MOV BP, SP
  db 0fh ; RDTSC
  db 31h
  MOV BX, [BP+6]
  MOV [BX], EAX
  MOV BX, [BP+8]
  MOV [BX], EDX
  POP BP
  RET 4
end proc

DECLARE SUB RDTSC(clk1&, clk2&)
RDTSC clk1&, clk2&
PRINT HEX$(clk1&);HEX$(clk2&)

Ezt a példaprogramot csak PENTIUM-tól fölfele futtassuk. FUNCTION-nél maga a függvény is ad vissza értéket, ezt az ax regiszterben kapjuk vissza, ha INTEGER a típusa. LONG típusú számoknál a felső érték a dx regiszterben van. De ez csak egész számoknál (INTEGER, LONG) működik. Mi van a lebegőpontosokkal? Hát igen erről én sem találtam semmit, de aztán egy kis kísérletezgetéssel megfejtettem. Lehet SINGLE illetve DOUBLE típusú függvényeket is használni. Egyszerű: Mielőtt a Basic meghívná alprogramunkat egy távoli hívással, előtte lenyom egy 2 bájt hosszúságú mutatót, ami a lebegő pontos érték offszetjét veszi fel. Visszatérésnél nem elég a RET FAR, hiszen akkor ott maradna az a két bájtnyi "szemét" és a programunk megáll, elszáll pontosabban. Ezt el kell távolítanunk.

SINGLE típusnál 4 bájtos, DOUBLE-nél 8 bájtos a szám.

Először kudarcba fulladt az egész próbálkozás. Lehetetlenek tűnt sztringek átadására, de aztán mégiscsak rájöttem. Azt tudtam, hogy valahogy regiszterekkel működik az egész, nem veremátadással, ahogy az előbb. A megoldás végül az ax regiszter. Ez tartalmazza az offsetet. De ez csak a sztringleíró offszet. Az első két bájt (word) a karakterlánc hossza, a második word az az offset ahol a sztring elhelyezkedik.

Optimalizáció

Hogy gyorsan fusson programunk optimalizálnunk kell, javítani az algoritmuson. Az egyik megoldás, hogy gépi kódban hajtjuk végre, amit akarunk, illetve könyvtár formájában egy-két új hatékony utasítást gyártunk. Igen, ez valóban mind gyors, de aki nem akar belemélyedni ilyen mélységig a számítógép világába, de mégis kicsit fel szeretné gyorsítani programját, annak más a megoldás. Most jöjjön néhány QBASIC jó tanács, amit érdemes követni:

  • Ne használjunk lebegőpontos számításokat. Ha tudjuk, akkor INTEGER számokat alkalmazzunk. Ezért is szokták a program/szubrutin elején:

  • DEFINT A-Z.
  • A PSET pixelrajzoló utasítással nem érdemes pontokat rajzolni, inkább írjunk közvetlenül a videomemóriába (&HA000). Azt mondják ezzel ugyanannyi idő alatt két pixelt is ki lehet gyújtani.

  • SCREEN 13
    PSET (x,y),szin

    helyett

    DEF SEG =&HA000
    POKE x*320+y, szin

  • A főciklust alprogramba tegyük. Ha a főprogramban futtatjuk lassabb lesz, állítólag. Ne ágyazzunk egymásba több IF THEN sorozatot, használjunk inkább SELECT CASE-t. Azt hiszem ez egyértelmű.
  • Ha tehetjük IF THEN nélkül oldjuk meg a feladatot.

  • IF a<>0 THEN
    helyett
    IF a THEN
  • MOD helyett használjuk az AND operátort.

  • szam=64 MODmaszk
    helyett
    szam 64 ANDmaszk
  • Ne használjunk nagyszámú ciklust.
  • Ciklusban feleslegesen ne számoljunk.
  • Szubrutinok hívásánál, az (alignment) miatt, használjunk (dummy) paramétert.

  • SUB (as%, sd%, par%, dummy%)
  • Integer osztásjelet alkalmazzuk.

  • c=a/b helyett c=a\b
  • Ciklusszámlálónak is próbáljunk INTEGER értéket adni.
  • Ugrási táblázatot (look-up table) használjunk.
  • Jó példa erre a Szinusz-koszinusztáblázat.
  • Rövid ciklusokat inkább írjuk le.
  • Tömbök törlésére REDIM-et használjuk, FOR NEXT helyett.
  • Kerüljük a többdimenziós tömböket.

  • DIM array(0 TO 63, 0 TO 63)
    helyett
    DIM array(4096)
  • Ne osszunk, reciprokot használjunk szorzóként.
További linkek: Ezzel véget ért rövid QBasic-es sorozatunk, várom kérdéseiket!