'=========================================================================== ' Subject: ASSEMBLY IN QBASIC 5: PROCS Date: 12-01-96 (05:37) ' Author: Rick Elbers Code: QB, QBasic, PDS ' Origin: t030611@tip.nl Packet: ASMCODE.ABC '=========================================================================== 'ASSEMBLY IN QBASIC PART 5: USING A PROCEDURE SEGMENT '------------------------------------------------------- 'Rick Elbers november 1996 DECLARE SUB pokestring (SEGJE%, OFFJE%, MAIN$) DECLARE SUB PokeW (pokeseg%, pokeoff%, word%) DECLARE FUNCTION int2str$ (sword%) '------------ 'INTRODUCTION '------------ 'There are more ways to handle procedures in your assembler subs. This short 'article will discuss one of them. This one is especially done because we 'need him in order to make an interrupthandler, which will be my next 'contribution. When we are discussing procedures we can separate the 'called program( that one is named newint% here) and the caller(that one is 'simply named callsub%). '----------------- 'CALLED PROCEDURE: '----------------- DIM newint%(6) newint%(0) = &H5052 'PUSH AX,DX newint%(1) = &H41B2 'MOV DL,41 newint%(2) = &H2B4 'MOV AH,02 newint%(3) = &H21CD 'INT 21 newint%(4) = &H5A58 'POP DX,AX newint%(5) = &HCB 'RETF (returns to the caller) 'CONTROL IF IT WORKS: segm% = VARSEG(newint%(0)): offs% = VARPTR(newint%(0)) CLS : DEF SEG = segm%: CALL ABSOLUTE(offs%) DEF SEG : SLEEP 'For me it worked... '---------------- 'CALLER PROCEDURE '----------------- DIM callsub%(3): cdeseg% = VARSEG(callsub%(0)): i% = VARPTR(callsub%(0)) DEF SEG = cdeseg% POKE i%, &H9A 'the opcode for CALL PokeW cdeseg%, i% + 1, offs% PokeW cdeseg%, i% + 3, segm% DEF SEG = cdeseg% 'has to be restored since POKEW returned to def seg POKE i% + 5, &HCB 'retf (return to QBASIC) 'This program does nothing but calling the newint% and then 'returning to QBASIC. Let us controle it: LOCATE 2, 1: CALL ABSOLUTE(i%): DEF SEG 'It does seems to work allright..... SLEEP '------- 'NEWINT$ '------- 'Some off you might have noticed i used integers for storage of the assembler subs 'here. That is for a few good reasons and in some follow up article on this 'i will resolve all mentioned problems with using integers for assembler code 'storage..But for this moment i will add, so to speak for backward 'compatibility, a way to handle procedures when you use strings for assembler 'subs storage. '---------------- 'CALLED PROCEDURE '---------------- newint$ = "" newint$ = newint$ + CHR$(&H52) + CHR$(&H50) 'PUSH AX,DX newint$ = newint$ + CHR$(&HB2) + CHR$(&H41) 'MOV DL,41 newint$ = newint$ + CHR$(&HB4) + CHR$(&H2) 'MOV AH,02 newint$ = newint$ + CHR$(&HCD) + CHR$(&H21) 'INT 21 newint$ = newint$ + CHR$(&H58) + CHR$(&H5A) 'POP DX,AX newint$ = newint$ + CHR$(&HCB) 'RETF (returns to the caller) 'CONTROL IF IT WORKS: segm% = VARSEG(newint$): offs% = SADD(newint$) DEF SEG = segm%: LOCATE 3, 1: CALL ABSOLUTE(offs%): DEF SEG : SLEEP 'seems oke '----------------- 'CALLING PROCEDURE '----------------- 'Okay now for the big surprise. We are going to use a PROC segment likewise 'we used a DATAS segment in some other article. In that PROC segment we will 'poke every string_stored_assembler_routine that we want to call. 'After that is done we can call the routines from the calling program the same 'way we readed out data in other instances. Let us see how it works: 'First let us set up the PROCS segment: DIM PROCS%(LEN(newint$) \ 2 + 1) procseg% = VARSEG(PROCS%(0)): procoff% = VARPTR(PROCS%(0)) procseg$ = int2str$(procseg%) 'Now let us poke newint$ into it and make newint$ adressable pokestring procseg%, procoff%, newint$: newintoff$ = int2str$(procoff%) '----------------------------------- 'CODE '----- 'call procseg$[newintoff$] 'retf '----------------------------------- asm$ = "" asm$ = asm$ + CHR$(&H9A) + newintoff$ + procseg$ 'retf asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$): LOCATE 4, 1: CALL ABSOLUTE(Codeoff%): DEF SEG : SLEEP '________________________________________ 'This was our first procedure....!!! '-------------- 'FINISHING.... '-------------- 'I hope you will be realizing the tremendous opportunities of this approach 'since you just can poke ANY routine inside the PROC segment and call him 'in your main program. For the sake of demonstration i will just add one 'more program to it. Actually it is the printstring that i used in the 'second part of ASSEMBLY IN QBASIC. Of course the actual procedures i used 'as calling procedures are very simple and does not seem to add any 'functionality whatsoever. However that is why they serve a great purpose in 'outlining the CONCEPT of a PROCEDURE segment. Actual possibilities should 'be very simple to fantasize and in fact to make when you have understood 'it all.... '-------------------------------------------------------------------------- 'DATA: '------ b$ = "Hello i have been printed indirectly$" 'This is the way DOS processes stringprint DIM datas%(LEN(b$) \ 2 + 1) dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): dataoff$ = int2str$(dataoff%) pokestring dataseg%, dataoff%, b$ 'CODE: '----- prnstr$ = "" prnstr$ = prnstr$ + CHR$(&H50) 'pusha prnstr$ = prnstr$ + CHR$(&H52) prnstr$ = prnstr$ + CHR$(&H1E) prnstr$ = prnstr$ + CHR$(&HB8) + dataseg$ 'MOV AX,DATASEG$ prnstr$ = prnstr$ + CHR$(&H8E) + CHR$(&HD8) 'MOV DS,AX prnstr$ = prnstr$ + CHR$(&HBA) + dataoff$ 'MOV DX,DATAOFF$ prnstr$ = prnstr$ + CHR$(&HB4) + CHR$(&H9) 'MOV AH,9 prnstr$ = prnstr$ + CHR$(&HCD) + CHR$(&H21) 'INT 21 prnstr$ = prnstr$ + CHR$(&H1F) 'popa prnstr$ = prnstr$ + CHR$(&H5A) prnstr$ = prnstr$ + CHR$(&H58) prnstr$ = prnstr$ + CHR$(&HCB) 'retf 'PROC '----- 'We have to adjust our PROC segment to hold both routines now: REDIM PROCS%(LEN(newint$) \ 2 + 1 + LEN(prnstr$) + 1) procseg% = VARSEG(PROCS%(0)): procoff% = VARPTR(PROCS%(0)) procseg$ = int2str$(procseg%): 'Let us poke both routines in the PROC segment now and make the routines 'adressable: pokestring procseg%, procoff%, newint$: newintoff$ = int2str$(procoff%) prnoff% = procoff% + LEN(newint$) pokestring procseg%, prnoff%, prnstr$: prnstroff$ = int2str$(prnoff%) 'Okay we have a PROC segment now with: 'PROC:0 newint$ 'PROC:LEN(newint$) prnstr$ 'When we want to call both routines we have to adjust the callers CODE: '---------------------------------------------------------------------- 'CODE '----- 'call procseg$[newintoff$] 'call procseg$[prnstroff$] 'retf '----------------------------------- asm$ = "" asm$ = asm$ + CHR$(&H9A) + newintoff$ + procseg$ asm$ = asm$ + CHR$(&H9A) + prnstroff$ + procseg$ 'retf asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$): LOCATE 5, 1 CALL ABSOLUTE(Codeoff%): DEF SEG '________________________________________ 'There does not seem to be a limit to the possible chaining that you can 'do. You can call procedures from procedures from procedures enz... 'But you might consider to store the heavy used procedures in one PROC 'segment of course.....I hope that this article helps a bit in assembly- 'programming in QBASIC (still the most used BASIC). I hope i pointed out 'in this article(s) that assembly programming in QBASIC is not necessaraly 'very difficult, when you pay attention to the main principles. 'So do not go for quick solutions at an easy level, but instead try to 'make clear choices at levels that still allow you to. I am sure that will 'later on help you * a lot! Seeing the difficulties in simple things often 'makes difficult things simple ...... 'Bye. 'Rick END SUB hlpje '----------------------------------- 'push ax 'push ds 'mov ax,0000 'mov ds,ax 'call 1234:0000 'retf '----------------------------------- prnstr$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H1E) asm$ = asm$ + CHR$(&HB8) + CHR$(&H0) + CHR$(&H0) asm$ = asm$ + CHR$(&H8E) + CHR$(&HD8) asm$ = asm$ + CHR$(&H9A) + CHR$(&H0) + CHR$(&H0) + CHR$(&H34) + CHR$(&H12) asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$) CALL ABSOLUTE(Codeoff%) '________________________________________ DEF SEG END SUB FUNCTION int2str$ (sword%) 'This function is translating SWORD Integers into a string. Its only use 'is when you still use asm$ for assembler functions( like i do). In that 'case you can make your integer values usable .. 'THis function simply translates the hexa bytes 'into stringbytes as is. '---------------------------------------------------- DEF SEG = VARSEG(sword%) ptr% = VARPTR(sword%) int2str$ = CHR$(PEEK(ptr%)) + CHR$(PEEK(ptr% + 1)) DEF SEG END FUNCTION SUB pokestring (SEGJE%, OFFJE%, MAIN$) '------------------------------------------------------ 'This function pokes a string (might be ASCIIZ)into 'memory at a given location, making it possible to 'access strings in byte form '------------------------------------------------------ DEF SEG = SEGJE% FOR i% = 0 TO LEN(MAIN$) - 1 POKE OFFJE% + i%, ASC(MID$(MAIN$, i% + 1, 1)) NEXT DEF SEG END SUB SUB PokeW (pokeseg%, pokeoff%, word%) 'This function will just poke a word into memory, just like 'the standard function Poke does it, with one enhancement. 'While poke needs a def seg before it we will transfer that to 'the function also! So : ' DW segment to poke word to ' DW offset to poke word to ' DW wordvalue to poke 'Of course you should use this only for reasons of putting all your 'variables in one DATAsegment, since otherwise just defining a integer 'is enough. '--------------------------------------------------------------- DEF SEG = VARSEG(word%) ptr% = VARPTR(word%) highbyte% = PEEK(ptr% + 1): lowbyte% = PEEK(ptr%) DEF SEG = pokeseg% POKE pokeoff%, lowbyte% POKE pokeoff% + 1, highbyte% DEF SEG END SUB