'=========================================================================== ' Subject: ASSEMBLY IN QBASIC 4: DATA Date: 02-07-97 (22:15) ' Author: Rick Elbers Code: QB, QBasic, PDS ' Origin: rick@tip.nl Packet: ASMCODE.ABC '=========================================================================== 'ASSEMBLY IN QBASIC PART 4: USING DATASEGMENTS '---------------------------------------------- 'Rick Elbers november 1996, revised in februari 1997 DEFINT A-Z DECLARE SUB appenddatademo () DECLARE SUB sectorread (dgroup()) DECLARE FUNCTION shell86qb$ () DECLARE FUNCTION getfile$ () DECLARE FUNCTION readboot3$ () DECLARE FUNCTION readsec4$ () DECLARE FUNCTION getdir$ () DECLARE FUNCTION datetime$ () DECLARE SUB stackdemo () DECLARE SUB nodatastring () DECLARE SUB dosprstr (string2write$) 'This short article is ment as a follow up on STEPHAN van Loendersloot's 'INTERRUPTS IN QBASIC in this directory. He described the very first step 'in calling CALL ABSOLUTE and this article will introduce the logical 'second step. It will very basically describe the way in which you can 'use DATAS in your assembler subs. '*************** 'INTRODUCTION '*************** 'Every program ( well at least most) consist not only of code, but operates 'on some datas. Same goes for assembly functions that you call from QBASIC. 'That is why it is very important to thoroughly consider how you could do 'that, and what method you should use in which instance. 'In general this article will distinguish the following use of DATAS in 'incrementing usefullness, complexity: 'A) Use an array or string to store a bunch of DATA and: 'A1)Pass it to your asm function through the stack : ' Initialization possible, hardcoding impossible, ' Uses segment overrides for array's and strings, ' Base/indexed adressing is not very simple. ' [!For variables this is also the IAB method!] 'B) Store DATA somewhere in your code "segment" and: 'B1)Use asm$ storage : ' Initialization possible, hardcoding possible ' Uses segment overrides for array's but not for strings ' Base/indexed adressing is not very simple ' [!This method is best in cases of stringprocessing!] 'B2)Use asm() storage: ' Initialization possible, hardcoding possible ' Uses segment overrides for strings and array's ' Base/indexed adressing simple ' [!This method is best in cases of Base/indexed adressing!] 'Shortly I will elaborate on each one, and a little bit longer on the com- 'bination of force/ problems in B1). Very rudimentair demonstrations will 'be provided. My choice is for very easy examples, since this is not about 'mastering advanced programs, but about mastering advanced technigues. At 'the risk that nobody will see the advantages of course. CLS : COLOR 0, 7: PRINT "DEMONSTRATING STACKPASSINGS..": COLOR 7, 0: VIEW PRINT 3 TO 25 CALL stackdemo COLOR 0, 7: PRINT "DEMONSTRATING DGROUP USES..": COLOR 7, 0 VIEW PRINT 3 TO 25 CALL appenddatademo LOCATE 23, 1: PRINT "Good bye" END SUB appenddatademo 'This will demonstrate the use of DATA by the way of appending them to 'your code. Its main use will be in the area of PARAMETER blocks, although 'you could use this method for the passing of STORAGE blocks too. 'In the approach in this section DATA SPACE is hardcoded so to speak. 'The actual parameters could be: '1) HARDCODED (if they are not changing, or at least not in your program) '2) PASSED and STORED with BASE/INDEXED adressing( if they change). 'The demonstration will be again increasing in complexity. Starting off 'with a simple string hardcoding, we will move on to a special case of 'DATA BLOCKS: the passing of a block of PARAMETERS. 'First we will look into parameter blocks with mainly fixed values. Secondly 'we will discuss DATA passing with parameterblocks with mainly variable 'elements. 'We will need some BASE/ INDEXED adressing of the parameter block 'inside our assembly procedure in the second case. 'In the process we will adress the choice of storage of your assembly code 'in integers or strings, since it turns out that the main DATA TYPE could 'be the decisive element right there. 'At that point we have gone through all basic DATA techniques and you are 'ready to understand even the most difficult of the ASSEMBLY routines in 'QBASIC like XMS/FLATMODE etc. 'A)A HARDCODED PRINTSTRING: COLOR 0, 7: PRINT "PRINTING A DGROUP STRING": COLOR 7, 0: CALL nodatastring 'This one will have you given a feeling for what hardcoding is all about. 'IF this string was the only one that you had to print in your program 'then hardcoding it, like in the above routine, is MUCH faster and CLEANER 'then passing it through the stack. In general: IF some parameter is not 'going to change in your program you must try to hardcode that parameter. 'Even better: if you have the chance to make a parameter static then you'll 'be advised to do so. Apart from that you do not have to pass the parameter, 'and that you save clocks inside your asm routine, you also keep a clean 'QBASIC program, and prevent passing errors[ like when you pass a wrong 'handle .... ]. Oke thats that about hardcoding. Lets move on to parameters. 'B)A HARDCODED STATIC PARAMETERBLOCK 'The next step to be taken is hardcoding a parameterblock. We discussed 'earlier why hardcoding your parameterblock is favored over passing it time 'and again, certainly when it is almost a static paramblock. 'Of course the distinction between a static and a dynamic parameterblock 'is only gradually. Most parameterblocks require some sort of change in 'parameters, and most blocks have some possibilities for hardcoding too. 'We will start with a sectorread procedure for DOS 4+. This function works 'with an almost static parameterblock. There are a lot of things remarkable 'about this procedure so stay tuned. It's is getting difficult but rewarding 'right now... 'Initialize: PRINT : PRINT : PRINT "Press a key for next demonstration": SLEEP: CLS COLOR 0, 7: PRINT "READING A DGROUP SECTORBUFFER ON DOS 4 +": COLOR 7, 0: '****IMPORTANT: YOU NEED DOS 4+ FOR THIS DEMONSTRATION*********** DIM dgroup(280): CALL sectorread(dgroup()) DEF SEG = VARSEG(dgroup(0)): 'Read a sector: sector& = 1 'Translate sector to unsigned long. helplowbyte& = sector& AND &HFFFF& lowbyte = helplowbyte& AND &H7FFF lowbyte = lowbyte OR -(helplowbyte& AND &H8000): helphighbyte& = ((sector& AND &HFFFF0000) \ 65535) AND &HFFFF& highbyte = helphighbyte& AND &H7FFF highbyte = highbyte OR -(helphighbyte& AND &H8000) sector& = (lowbyte * 65536) OR highbyte CALL absolute(sector&, 512) FOR I = 0 TO 511 PRINT HEX$(PEEK(I)); NEXT DEF SEG 'Of course the translation of the sector to unsigned should ideally also 'be done inside your asm routine( much much faster too) and you should 'have done some errorchecking , but all that would have made the asm routine 'even more difficult. 'As is ,the routine provides an excellent example of almost everything 'that is important for using DATA inside asm routines: '1) Choice for integerstorage '2) Choice for hardcoding AND the parameterblock AND the storagearray '3) Choice and the way to use .386 instructions inside asm$ routine ' [this one thanks to Gunther Ilzig and Erika Shulz!] '4) Passing the dynamic parameter to the asm routine '5) Putting the dynamic parameter by based adressing to the parameterblock 'I will go shortly into every point now.. '1) CHOICE FOR INTEGERSTORAGE 'This routine reads out sectors. That is to say that it will read a mixture 'of stringdata and integer data. So whatever youre choice will be for storing 'the sector you have to translate integers to strings with CHR$ OR strings 'to integers with ASC. 'When you look shortly into that you will notice that ASC is much slower then 'CHR$ and in addition requires some byte additions. Added together that will 'make integerstorage the better choice for the storage of the sectors. 'You could make the comment here that having an INTEGER DATABLOCK has 'has nothing to do with the way you will store your assembly CODE. I hope 'to falsify that statement in the explanations below...My statement will be 'that you have to modulate your type of assembly CODE storage to the type of 'the DATA storage. '2)CHOICE FOR HARDCODING AND THE PARAMETERBLOCK AND THE STORAGEARRAY 'Since we are not going to write directly to the STORAGE DATA block, It 'is very well justified to hardcode that DATA block too. For easyness of 'reading it out I made the choice to put the DATAstore block BEFORE the 'asm routine( CODE) so that DATA STORAGE starts at offset 0. 'When you want to CHANGE some DATAstore block directly( making it more like 'a parameter or variable block anyway) then I will NOT advise you to hardcode 'that the way I did it for sectorread. For instance when you have some 'sectorWRITE function and you had your DATA appended to the code like 'in the sector READ function then there is the risk that when you are filling 'the DATA BLOCK you in fact OVERWRITE your ASM CODE. And that will cause 'unpredictable effects when you execute the CALL ABSOLUTE. The same error in 'READING or PASSIVE functions will only give you the wrong output... 'The general rule that seems to be emerging is that you pass variable DATA 'to the stack and that you hardcode AND the parameterblock itself AND an 'eventually passive DATAstore block itself AND all static parameters. 'Hardcoding the parameter block is of course much better then passing it 'through the stack. You only need one segment/segment override, and less 'stackpassing between QBASIC and CALL ABSOLUTE. Furthermore you can change 'an eventually variable parameter inside your assembly routine in a simple 'way, where you should have to do that inside your QBASIC program otherwise. '3) CHOICE AND THE WAY TO USE .386 INSTRUCTIONS INSIDE ASSEMBLY ROUTINE ' [this one thanks to Gunther Ilzig and Erika Shulz!] 'Since we are passing a long integer(sectornr&) to the asm routine, it pays 'really off to use 32 bit instructions here. In the two simple cases the 32 'bit equivalent of the 8086 instructions is just putting &h66 before the 8086 'instruction and that is that. 'So: 'mov ax,[bx] =&H8B &H7 'mov eax,[bx]=&H66 &H8B &H7 'By using the extended registers you can fill the long integer at once! 'and store it to the parameterblock at once too. For comparison: in 8086 code 'you should have done something like: 'mov ax,[bx] 'mov dx,[bx+2] '.....segment overrides 'mov [param:0],ax 'mov [param:2],dx 'This is adding a few(g) cycles as you can see....Since the .386 codes are 'often applicable, I highly recommend that you read the other contributions 'by Gunther and Erika, since they will explain .386 coding in QBASIC in more 'detail. '4) PASSING THE DYNAMIC PARAMETER TO THE ASM ROUTINE 'When you understood all that was above you will not be surprised that 'we passed the changing parameter sector& through the stack. THat way 'we can maintain a routine that can be initialized by only moving the 'changed parameter to the parameterblock. 'C)A HARDCODED DYNAMIC PARAMETERBLOCK 'There is not a separate example routine available for DYNAMIC parameter 'blocks, but the principles of them will be fully explained with reference 'to the dynamic part of the sectorread parameter block. 'While this example routine has an only a very little DYNAMIC part, the 'principles are the same for a more DYNAMIC parameterblock like in XMS/ EMS 'move functions. If you still wanna look at a more DYNAMIC parameterblock 'handling you should have a look at the RAM in QBASIC series. '5) STORING THE DYNAMIC PARAMETER BY BASED /INDEXED ADRESSING 'As in other routines with more dynamic parameters then this sectorread, we 'move the sector& with a simple base displacement to its proper place in the 'parameterblock. This base ( or indexed) displacement is a thing that 'you always need when you have to fill a parameterblock. In a way it is 'the counterpart of hardcoding and the price you have to pay for all 'advantages of an hardcoded parameterblock as I pointed out above. 'You should know that the segment override we did in this routine with 'push cs:pop ds is specific for integerstorage. When you are going to 'fixate a stringstorage like we did with the first printstring example 'in this part then you do not need a segment override since ds is pointing 'to the stringsegment by default. 'When you understood the principles and choices from above you should not 'have a big trouble to understand more elaborate parameterblocks like in 'XMS move[ See for that: RAM IN QBASIC 3:XMS] and you have a few tools to 'use DATA inside your assembly routines and in addition some rules against 'which you can evaluate which way of DATA you could/ should use or is optimal. END SUB DEFSNG A-Z FUNCTION datetime$ '------------------------------------------------- 'This procedure gets the date and time of a file 'STACKPASSING : DX, CX 'IN : CX= HANDLE 'OUT : DX=FFFF ERROR AND CX= ERRORCODE ' ELSE DX= DATE ' CX = TIME '-------------------------------------------------- asm$ = "" asm$ = asm$ + CHR$(&H55) 'push bp asm$ = asm$ + CHR$(&H89) + CHR$(&HE5) 'mov bp,sp asm$ = asm$ + CHR$(&H8B) + CHR$(&H7E) + CHR$(&H6) 'mov di,[bp+06] asm$ = asm$ + CHR$(&H8B) + CHR$(&H1D) 'mov bx,[di] handle asm$ = asm$ + CHR$(&HB8) + CHR$(&H0) + CHR$(&H57) 'mov ax,5700 asm$ = asm$ + CHR$(&HBA) + CHR$(&HFF) + CHR$(&HFF) 'mov dx,ffff set to error asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 asm$ = asm$ + CHR$(&H73) + CHR$(&H2) 'jnc +2 No error asm$ = asm$ + CHR$(&H89) + CHR$(&HC1) 'mov cx,ax > errorcode asm$ = asm$ + CHR$(&H89) + CHR$(&HD) 'mov [di],cx time or error asm$ = asm$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H8) 'mov bx,[bp+08] asm$ = asm$ + CHR$(&H89) + CHR$(&H17) 'mov [bx],dx date asm$ = asm$ + CHR$(&H5D) 'pop bp asm$ = asm$ + CHR$(&HCA) + CHR$(&H4) + CHR$(&H0) 'retf 4 datetime$ = asm$ END FUNCTION FUNCTION getdir$ '------------------------------------------------------------------ 'This function returns the current directory from C:\ 'STACKPASSING: buffer$ 'IN : DS[SI] 65 byte buffer 'OUT : ERROR :LEFT$(buffer$,1)="G" ' :CVI(MID$(buffer$,2,1))= errorcode ' ELSE :buffer$=path offset C:\ '------------------------------------------------------------------ asm$ = "" asm$ = asm$ + CHR$(&H55) 'push bp 4 asm$ = asm$ + CHR$(&H89) + CHR$(&HE5) 'mov bp,sp 1 asm$ = asm$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(6) 'mov bx,[bP+06] 1+9 asm$ = asm$ + CHR$(&H8B) + CHR$(&H77) + CHR$(&H2) 'mov si,[bx+2] 1+9 asm$ = asm$ + CHR$(&HB4) + CHR$(&H47) 'mov ah,47 1 asm$ = asm$ + CHR$(&H30) + CHR$(&HD2) 'mov dl,0 default 1 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 26 asm$ = asm$ + CHR$(&H72) + CHR$(&H4) 'JC +3 error! 1/3 asm$ = asm$ + CHR$(&H5D) 'pop bp 4 asm$ = asm$ + CHR$(&HCA) + MKI$(2) 'RETF 2 14 asm$ = asm$ + CHR$(&H89) + CHR$(&H4) 'mov [si],ax errorreturn!1+9 asm$ = asm$ + CHR$(&HEB) + CHR$(&HF8) 'jmp -9: finish 3 getdir$ = asm$ END FUNCTION FUNCTION getfile$ '----------------------------------------------------------------- 'This function returns the directory in an array, without 'BASIC interference that is........ ' 'STACKPASSING: DIR() 'IN : seg( DIR()):46 search$ 'OUT : seg( DIR()):44 errorcode ' seg( DIR()):126 files, zero terminated '------------------------------------------------------------------ asm$ = "" asm$ = asm$ + CHR$(&H55) 'push bp 4 asm$ = asm$ + CHR$(&H89) + CHR$(&HE5) 'mov bp,sp 1 asm$ = asm$ + CHR$(&H6) 'push es 3 asm$ = asm$ + CHR$(&H1E) 'push ds 3 'SET DTA asm$ = asm$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(6)'mov bx,[bp+06]1+9 asm$ = asm$ + CHR$(&HC5) + CHR$(&H17) 'lds dx,[bx] 6+5 asm$ = asm$ + CHR$(&H1E) 'push ds 3 asm$ = asm$ + CHR$(&H7) 'pop es 3 asm$ = asm$ + CHR$(&HB4) + CHR$(&H1A) 'mov AH,1A 1 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 26 'FINDFIRST asm$ = asm$ + CHR$(&H31) + CHR$(&HC9) 'xor cx,cx attrib=0 1 asm$ = asm$ + CHR$(&HBA) + MKI$(46) 'mov DX,offset(searchstring$)1 asm$ = asm$ + CHR$(&HB4) + CHR$(&H4E) 'mov AH,4E 1 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 26 asm$ = asm$ + CHR$(&H72) + CHR$(&H15) 'JB +24 op: 1/3 asm$ = asm$ + CHR$(&HBF) + MKI$(126) 'mov di,offset(result$) 1 'DIRLOOP: ONLY FILENAMES asm$ = asm$ + CHR$(&HBE) + MKI$(30) 'mov si,offset(DTAstringoff$) 1 'PRINTNAME: asm$ = asm$ + CHR$(&HFC) 'cld 2 asm$ = asm$ + CHR$(&HAC) 'lodsb 5 asm$ = asm$ + CHR$(&HAA) 'stosb 5 asm$ = asm$ + CHR$(&H84) + CHR$(&HC0) 'test AL,AL 1 asm$ = asm$ + CHR$(&H74) + CHR$(&H2) 'jz next entrance 1/3 asm$ = asm$ + CHR$(&HEB) + CHR$(&HF8) 'jmp printtname 3/1 'FIND NEXT ENTRANCE: asm$ = asm$ + CHR$(&HB4) + CHR$(&H4F) 'mov ah, 4f 1 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 26 asm$ = asm$ + CHR$(&H73) + CHR$(&HEE) 'Jnc dirloop 3/1 'OP: asm$ = asm$ + CHR$(&H1F) 'pop ds 3 asm$ = asm$ + CHR$(&HA3) + MKI$(44) 'mov [errorcode],ax 6+1 asm$ = asm$ + CHR$(&H7) 'pop es 3 asm$ = asm$ + CHR$(&H5D) 'pop bp 4 asm$ = asm$ + CHR$(&HCA) + MKI$(2) 'retf 2 14 getfile$ = asm$ END FUNCTION DEFINT A-Z SUB nodatastring '----------------------------------- 'Writing a simple com program 'CS=DS=ES 'IP=100 for comprograms '----------------------------------- 'CS:100 asm$ = asm$ + CHR$(&HBA) + CHR$(&HB) + CHR$(&H1)'mov dx,10B offset(string) asm$ = asm$ + CHR$(&HB4) + CHR$(&H9) 'write $ delimited string asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'call dos asm$ = asm$ + CHR$(&HB4) + CHR$(&H4C) 'exit shell asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'call dos 'CS:10B :STRING2WRITE asm$ = asm$ + "Hello, world this is stand alone 'BASIC' Ahum...$" '1)EXECUTE this one as a com file OPEN "standbas.com" FOR BINARY AS #1 PUT #1, , asm$ CLOSE SHELL "standbas.com": PRINT : '2) EXECUTE this one inside QBASIC. 'The problems are not very easy , when you want to use some DATA inside 'your code "segment", since there is no easy way of finding out what 'the offset of the DATA is. And when you use a variable length string 'then the adress will be prone to garbage collection too. 'So what we have to do: '1) DECLARE a fixed length string (length=CODE+DATA) to fixate the memadress. '2) HARDCODE the offset of your data inside the CODE 'And then it works! and can be initialized too. Be sure not to use an array 'of fixed length strings, since they do not default to the stringsegment. DIM staticasm AS STRING * 50: Codeoff = VARPTR(staticasm$) asm$ = "" asm$ = asm$ + CHR$(&HBA) + MKI$(Codeoff + 8)'mov dx,offset(string2write) asm$ = asm$ + CHR$(&HB4) + CHR$(&H9) 'mov ah,9 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 asm$ = asm$ + CHR$(&HCB) 'retf 'STRING2WRITE: asm$ = asm$ + "Hello, world this is intrepreted QBASIC!$" staticasm$ = asm$ DEF SEG = VARSEG(staticasm$): CALL absolute(Codeoff) DEF SEG END SUB SUB sectorread (dgroup()) '------------------------------------------------------------------------ 'This routine reads one sector from the C:\ drive. 'STACKPASSING : LONGSECTOR by reference 'IN : LONGSECTOR 'OUT : ERROR =FFFF+ errorcode 'The sector is read into the parameterblock and the routine executed. '--------------------------********--------------------------------------- ' *DGROUP* ' ******** '0.... 512 = sector buffer offset 0 for easy read out! '512... 546 = assembly routine '546.... = parameterblock as: ' 0:DW = sector2start ' 4:W = nrsector2read ' 6:DW = offset:segment van buffer '------------------------------------------------------------------------ '------------------------------------------------------------------------ 'DGROUP:256*2 CODE asm$ = "" asm$ = asm$ + CHR$(&H55) 'push bp asm$ = asm$ + CHR$(&H89) + CHR$(&HE5) 'mov bp,sp asm$ = asm$ + CHR$(&H1E) 'push ds asm$ = asm$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H6) 'mov bx,[bp+06] asm$ = asm$ + CHR$(&H66) + CHR$(&H8B) + CHR$(&H7) 'mov eax,[bx] get the long asm$ = asm$ + CHR$(&HBB) + MKI$(546) 'mov bx,offset(params) asm$ = asm$ + CHR$(&HE) + CHR$(&H1F) 'push cs:pop ds set DS asm$ = asm$ + CHR$(&H66) + CHR$(&H89) + CHR$(&H7) 'mov dwordptr [bx],eax asm$ = asm$ + CHR$(&HB8) + CHR$(&H2) + CHR$(&H0) 'mov ax,2= C:\ as drive asm$ = asm$ + CHR$(&HB9) + CHR$(&HFF) + CHR$(&HFF)'mov cx,-1 asm$ = asm$ + CHR$(&HCD) + CHR$(&H25) 'int 25 asm$ = asm$ + CHR$(&H83) + CHR$(&HC4) + CHR$(&H2) 'add sp,2 asm$ = asm$ + CHR$(&H1F) 'pop ds asm$ = asm$ + CHR$(&H5D) 'pop bp asm$ = asm$ + CHR$(&HCA) + MKI$(2) 'retf 2 'Poke asm$ into the array! DEF SEG = VARSEG(dgroup(0)) FOR I = 0 TO LEN(asm$) - 1: POKE I + 256 * 2, ASC(MID$(asm$, I + 1, 1)): NEXT 'DGROUP:273*2 PARAMS:! dgroup(273) = &H0: dgroup(274) = 0 '0:sectornr& dgroup(275) = 1 '4:1 sector to read dgroup(276) = VARPTR(dgroup(0)) '6: offset buffer dgroup(277) = VARSEG(dgroup(0)) '8: segment buffer END SUB DEFSNG A-Z FUNCTION shell86qb$ asm$ = "" asm$ = asm$ + CHR$(&H55) 'push bp asm$ = asm$ + CHR$(&H89) + CHR$(&HE5) 'mov bp,sp asm$ = asm$ + CHR$(&H1E) 'push ds asm$ = asm$ + CHR$(&H6) 'push es asm$ = asm$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H6) 'mov bx,[bp+06] asm$ = asm$ + CHR$(&HC4) + CHR$(&H1F) 'les bx,[bx] ES[BX] points to params asm$ = asm$ + CHR$(&H8B) + CHR$(&H7E) + CHR$(&H8) 'mov di,[bp+08] asm$ = asm$ + CHR$(&HC5) + CHR$(&H15) 'lds dx,[di] DS[DX] points to program asm$ = asm$ + CHR$(&HB8) + CHR$(&H0) + CHR$(&H4B) 'mov ax,4B00 function asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 'DOS asm$ = asm$ + CHR$(&H7) 'pop es asm$ = asm$ + CHR$(&H1F) 'pop ds asm$ = asm$ + CHR$(&H5D) 'pop bp 'We are done? asm$ = asm$ + CHR$(&HCA) + MKI$(4) 'retf 4 shell86qb$ = asm$ END FUNCTION DEFINT A-Z SUB stackdemo 'This sub demonstrate the use of DATA by passing them through the stack. 'The demonstration will be increasing in complexity. Starting off with 'a simple variable passing, we will move on to the passing of string DATA 'and an array of DATA. If you are experiencing troubles with the way 'that different data are passed through the stack then you have to look 'at ASM IN QBASIC3:PASSING again. In this article the knowledge 'from that article is assumed. 'Initializing asm$ = datetime$: getcurdir$ = getdir$: buffer$ = SPACE$(65) '1) Passing DATA as a variable through the stack[Initialized function] '********************************************************************** filename$ = "c:\dos\qbasic.exe" OPEN filename$ FOR BINARY AS #1 cx = FILEATTR(1, 2) '________________________________________ Codeoff = SADD(asm$) DEF SEG = VARSEG(asm$) CALL absolute(dx, cx, Codeoff) '________________________________________ CLOSE : IF dx = &HFFFF THEN PRINT "Error nr: "; HEX$(cx); " encountered": EXIT SUB PRINT "DATE of "; : COLOR 0, 7: PRINT UCASE$(filename$): COLOR 7, 0 PRINT "YEAR "; : COLOR 0, 7: PRINT 1980 + (dx AND &HFE00) \ &H1FF: COLOR 7, 0 PRINT "MONTH "; : COLOR 0, 7: PRINT (dx AND &H1E0) \ &H1F: COLOR 7, 0 PRINT "DAY "; : COLOR 0, 7: PRINT (dx AND &H1F): COLOR 7, 0 PRINT "TIME of "; : COLOR 0, 7: PRINT UCASE$(filename$): COLOR 7, 0 PRINT "HOURS "; : COLOR 0, 7: PRINT (cx AND &HF800) \ &H7FF: COLOR 7, 0 PRINT "MINUTES "; : COLOR 0, 7: PRINT (cx AND &H7E0) \ &H1F: COLOR 7, 0 PRINT "SECONDS "; : COLOR 0, 7: PRINT 2 * (cx AND &H1F): COLOR 7, 0 PRINT : PRINT "Press a key for next demonstration": SLEEP: CLS '2) Passing DATA as a string through the stack[function initialized] '________________________________________ Codeoff = SADD(getcurdir$) CALL absolute(buffer$, Codeoff) '________________________________________ IF ASC(MID$(buffer$, 2, 1)) THEN PRINT "Current directory : "; : COLOR 0, 7: PRINT "C:\" + UCASE$(RTRIM$(buffer$)); COLOR 7, 0 ELSE PRINT "Error getting directory nr: "; HEX$(CVI(LEFT$(buffer$, 2))) END IF PRINT : PRINT : PRINT "Press a key for next demonstration": SLEEP: CLS 'Note that in this routine DS is used as default to stack/stringsegment and 'that slowness in errorretrieving with stringfunctions is offered for a 'faster OKE return. '*************************************************************************** 'Very important for this discussion is that you notice that you should 'start to consider to hardcode the buffer into the routine, since you ALWAYS 'need to use a 65 byte buffer by default AND since stackpassing of an extra 'variable requires: '-PUSHING an extra variable onto the stack by QBASIC '-Extra CYCLES in your routine: ' mov bx,[bp+06];mov si,[bx] is taking up 1+9+1+5 ticks( 9 for base+disp calc) ' mov si,value requires 1 cycle! '-POPPING an extra variable off the stack by QBASIC '-In addition if the buffer is the ONLY variable to pass you can spare ' one extra cycle by RETF instead of RETF 2. 'This extra cycles things are slightly less worse for segment passing like 'you will see in the next section. For the hardcoding you have to look for 'the DATAappend demonstrations. '*************************************************************************** '3) Passing DATA as an array through the stack[function initialized] 'It is important that you understand that there are basically two reasons 'why some array is passed to an assembly routine: '1) To pass some parameters '2) To allow some storage arrea. 'The difference's are big. For one thing the second array passing is easy, 'and often the passing of parameters is not. For another thing often the 'parameters have to be filled , be it in QBASIC or in the assembly routine, 'and the storage arrea is just passed as a pointer. Examples of the first 'array passings are EXEC, All MOVE memory functions for low memory, 'EMS, XMS, READSECTOR/ WRITESECTOR for DOS 4++, etc, etc. In general: the 'more difficult functions. Examples of the second array passings are much 'more easy, and MarkK covers a lot of them too. You should think of SETDTA, 'READFILE, READSECTOR/WRITESECTOR for DOS 4--, GET DPB, GET DISPLAYSTATE, etc. '1)PASSING an array for data storage[initialized] 'Init and get big enough storage array( < 800 files): Tasm$ = getfile$: DIM dirs(0) AS STRING * 10000 'input: COLOR 0, 7: PRINT "REDIRECTABLE DRIVE-/DIRECTORY-/FILE- SCANNER" COLOR 7, 0: PRINT "(redirection example c:\>tmp.tmp)" DEF SEG = &H40: POKE &H1C, PEEK(&H1A): DEF SEG INPUT "Drive/directory/file to scan : ", fdir$ 'Valid strings to search ? posi = INSTR(fdir$, ">"): IF posi > 0 THEN dir$ = LEFT$(fdir$, posi - 1) ELSE dir$ = fdir$ IF INSTR(dir$, ".") <> 0 THEN MID$(dirs$(0), 47) = dir$ + "0" ELSEIF RIGHT$(dir$, 1) <> "\" THEN MID$(dirs$(0), 47) = dir$ + "\*.*0" ELSE MID$(dirs$(0), 47) = dir$ + "*.*0" END IF DEF SEG = VARSEG(Tasm$): CALL absolute(dirs(), SADD(Tasm$)) VIEW PRINT 5 TO 24 DEF SEG = VARSEG(dirs(0)): j = dataoff + 127: k = INSTR(j, dirs(0), CHR$(0) + CHR$(0)) IF posi <> 0 THEN 'redirection OPEN MID$(fdir$, posi + 1) FOR OUTPUT AS #1 IF PEEK(44) = 3 THEN PRINT #1, "ongeldig pad" ELSEIF PEEK(&H16) = 0 AND PEEK(&H17) = 0 THEN PRINT #1, "bestand niet gevonden" ELSE PRINT #1, MID$(dirs(0), j, k - j) END IF CLOSE 'no redirection ELSE IF PEEK(44) = 3 THEN PRINT "ongeldig pad" ELSEIF PEEK(&H16) = 0 AND PEEK(&H17) = 0 THEN PRINT "bestand niet gevonden" ELSE LOCATE 5, 32: COLOR 0, 7: PRINT UCASE$(fdir$): VIEW PRINT 6 TO 24: COLOR 7, 0: PRINT MID$(dirs(0), j, k - j); END IF DEF SEG END IF PRINT CHR$(27) PRINT : PRINT : PRINT "Press a key for next demonstration": SLEEP: VIEW PRINT 3 TO 24: CLS 2 'Note that in this routine there was already some parameter APPENDED to the 'code: the search string. It will be very instructive to look at what changes 'you should make just to PASS the search$ THROUGH the STACK too. You have to 'save DS, set DS to the stringsegment again. Execute a findfirst and RESET DS 'again. Apart from the actual stackpassing that is. '************************************************************************ 'But the topic of interest was STACKPASSING of an DATAarray for storage. 'We passed the DATAS() and it was filled by the assembly routine. With 'our way of passing the array we have modificated MarkK's method: '-Instead of passing segment AND offset we passed array() 'That way we have spared: '-PUSHING an extra variable onto the stack by QBASIC '-Extra CYCLES in the routine: ' 2*(mov bx,[bp+06];mov si,[bx] is taking up 2*(1+9+1+5) ticks ' mov bx,[bp+06];lds dx,[bx] is taking up 1+9+7+5 ticks '-POPPING an extra variable off the stack by QBASIC 'This routine points to hardcoding the DATAS too, since it could be an 'static space in memory. For the savings you can get by that you have to 'look back to the printstring example before. '************************************************************************ '2)PASSING an array for parameters passing[initialized] 'Until now this article has been pretty straightforward, but now it becomes 'difficult, when we are going to adress the topic of parameterpassing. Not 'only are there different methods to be considered there, also the particular 'functions that need parameterpassing are among the most difficult functions 'existent. 'It is important to realize that when you pass another (sub)procedure to an 'assembly procedure, that is nothing more then a special case of parameter 'passing. In a later article ASM IN QBASIC 5: PROCS I will get into 'Procedures and there passing into more detail. At this moment we will look 'into a more plain example of parameterpassing: LOAD and EXECUTE. As an 'example of parameter passing the LOAD and EXECUTE function is extremely 'usefull, since it eats no less then TWO parameterblocks. 'Initializing: asm$ = shell86qb$: DEF SEG = &H40: POKE &H1C, PEEK(&H1A): DEF SEG PRINT "Give me please the programname with extension ? "; DO UNTIL progname$ <> "": COLOR 0, 7: LINE INPUT ; progname$: LOOP COLOR 7, 0: PRINT "Give the parameters please ? "; COLOR 0, 7: LINE INPUT ; parameters$: COLOR 7, 0 CLS : PRINT "We shelled from QBasic to: "; : COLOR 0, 7: PRINT progname$: COLOR 7, 0: PRINT "With the parameters passed :"; : COLOR 0, 7: PRINT parameters$ COLOR 7, 0: PRINT STRING$(80, "_") VIEW PRINT 5 TO 22 '-------------------------------- '1) PROGRAMNAME BLOCK TO DS[DX]: '-------------------------------- exec$ = progname$ + "0": l = LEN(exec$): DIM exename((l / 2) + 1) DEF SEG = VARSEG(exename(0)): FOR I = 0 TO l - 1: POKE I, ASC(MID$(exec$, I + 1, 1)): NEXT: '--------------------------------------------------------------------------- '2) PARAMETERBLOCK TO ES[BX]: '---------------------------- 'a)Parameterblock overview: '---------------------------- '0 W pointer to environment (0= default dos environment) '2 DW pointer to commandtail '6 DW FCB1 pointer '10 DW FCB2 pointer '14 FCB=20*nul '34 Commandtail '-------------- 'Commandtail: '-------------- commandtail$ = " " + parameters$: param$ = CHR$(LEN(commandtail$)) + commandtail$ + CHR$(13) DIM parameter(7 + LEN(param$) + 10): es = VARSEG(parameter(0)): bx = VARPTR(parameter(0)) '----------------------------------------------------------- 'Filling the parameterblock '---------------------------- DEF SEG = es: parameter(0) = 0 'default environ parameter(1) = bx + 34: parameter(2) = es 'ptr > commandtail parameter(3) = bx + 14: parameter(4) = es 'ptr >FCB 1 parameter(5) = bx + 14: parameter(6) = es 'ptr >FCB2 FOR I = bx + 14 TO bx + 33: POKE I, 0: NEXT 'FCB's FOR I = 1 TO LEN(param$): POKE bx + 33 + I, ASC(MID$(param$, I, 1)): 'Commandtail NEXT DEF SEG = VARSEG(asm$) offcode = SADD(asm$): CALL absolute(exename(), parameter(), offcode): DEF SEG VIEW PRINT: LOCATE 23, 1 PRINT : PRINT "Press a key for DGROUP demonstrations": SLEEP: CLS 'This routine provided us with a nice example for parameterpassing, but you 'should have realized it is far from ideal. '1) Since a lot of entrances in the parameters() will never change ,at least ' these entrances could be hardcoded. '2) Furthermore you will hate the way you have to handle the commandline in ' QBASIC, and when you add to that the knowledge that the maximum length of ' the commandline$ is fixed at 128 bytes[PSP won't allow more], you are ' even more driven towards hardcoding space for the commandline inside ' youre assembly routine too. 'At this moment the contours of a brand new approach are beginning to take 'form.. 'In the end ,the only thing that your optimized routine will do is pass the 'commandtail and params as a string to the assembly routine and the assembly 'routine will handle the rest. In addition to that, both parameterblocks will 'be placed inside your stringsegment, so you only need to override ES. 'How all of this is possible and why it is the optimized way to go will be 'explained in the next section about appending DATA. For now it is enough 'when you know what the trigger was to develop that approach.. END SUB