Using Assembly in QuickBasic ============================ PART I By Raze, 28th April 2000 Updated 1st May 2000 email:andrew_raze@telstra.easymail.com.au 'Quickbasic And More' site:- http://www.geocities.com/qbandmore ______________________________________________________________________________ Welcome to the new 'Using Assembly in QuickBasic' tutorial. This tutorial shows you one way of calling some assembly routines in QB 1.1 and better. Remember, for QB versions that support libaries you must use the /L option at the DOS prompt to load the needed library! NOTE: This tutorial assumes you already have knowledge of the assembly language itself, so if you're a beginner, this ain't the place for you! I accept no responsibilities for any damage use of information aquired from this tutorial may cause. Remember assembly is a powerful language and if you don't know what you're doing, things can happen... So use at your own risk!! ______________________________________________________________________________ HAVE YOU GOT WHAT IT TAKES?? WHAT YOU NEED TO HAVE BEFORE YOU START LEARNING HOW TO PUT ASSEMBLY CALLS IN QUICKBASIC: ========================================================== - A good enough knowledge of Assembly to write the routines you need. - DEBUG.EXE (You will find this in the WINDOWS\COMMAND folder for Win95 and later Machines, or the DOS folder for pre-Windows 95.) - Understanding of the hexadecimal numerical system If you pass the test, read on... ______________________________________________________________________________ WHY USE ASSEMBLY IN QB? ======================= For anyone who knows what assembly is, how it works and how low-level it is, the reason behind this should already be apparent...SPEED!!! Unless you haven't yet noticed (?!) Raw QB code isn't very speedy where intensive processes like graphics are concerned, even compiled. That's simply because the interpreting process of QB syntax to machine code is quite complex and therefore slows the whole damn thing down. Luckily we can call strips of assembly code to operate on the machine's level and speed up those core parts of our graphics algorithms for example, and with dramatic results! The reason why assembly and QB are such a great mix is because QB on its own is easy to code, and assembly although VERY hard to code but VERY fast! There is no point coding an entire program in assembly (good luck for those who choose to) as not the whole program needs the speed. This way we can program the stuff in QB that's fast anyway, and when things start to slow down, only then does assembly really need to come in. Once you've mastered the art of Assembly language and applying it in QuickBasic miracles can happen... An excellent example is the 'RPG Engine Demo Build 4' by Ryan Ross and (the late and great) Milo Sedlacek, the use of a pure assembly engine is assembly and QuickBasic at it's best... ______________________________________________________________________________ PUTTING ASSEMBLY IN QUICKBASIC ============================== 'Assembling' in DEBUG: ====================== You must first be aware that DEBUG is not capable of instruction sets for 386+ processors so some assembly codes you may already know such as 32-bit registers cannot be used. However this shouldn't be a big problem in the case of QB calls. First write your assembly code out on paper or some text editor and just save it as text. Here is a useless little prog I'll use as an example: push bp mov bp,sp pop bp retf Next, enter the MS-DOS prompt and type DEBUG. You should be greeted by a little '-' prompt. Type 'a' and enter to begin assembly mode. You should see a new prompt with the memory segment/offset address before it similar this: 1A6E:0100 Of course, the actual address code will probably be something different in different cases, this is not important. The above is only an example! Now copy in your assembly code line-by-line from your piece of paper (or a multi-tasked window if you typed it in a text-editor). When you are done, just enter a blank line and the '-' prompt should return. Press 'u' and ENTER to 'un-assemble' it. A list similar to below should appear: 1A6E:0100 55 PUSH BP 1A6E:0101 89E5 MOV BP,SP 1A6E:0103 5D POP BP 1A6E:0104 CB RETF Again this is only an example, the actual details will be based on whatever the currently set memory address is and whatever code you typed in! When you do small assembly programs you will see more lines of other code appear below but ignore these... Donot attempt to use them! DEBUG displays these lines but they are just the following addresses you didn't use that have code the computer had previously ran instructions in... The first column is the memory address for each assembly line, the second is the hexadecimal symbol of each machine-code command, while the last two columns are the assembly code you typed in. NOTE: Remember to keep track or where your own code actually ends and donot copy the rest below it! Save the Information You Need: ============================== Anyway, copy this list of your code up to where it ends by either copying to paper, or if you're in Windows ALT+ENTER to put it into a window and use the MARK/COPY tools on the toolbar to copy the information you need. Then simply open up edit, and paste it, and save it under any filename you choose with a .BAS extension, as we will later re-open this in the QuickBasic IDE to apply the code. You can ALT+ENTER back to a full screen now you've finished copying. The information is not ready yet, because there's only one column of the info we need, and this is the second column which holds the hexadecimal machine- code instructions for each line. So delete the memory address column before it , as well as the original assembly in the last two columns. In the above example, we would be left with: 55 89E5 5D CB Each line has the machine-code instructions in Hexadecimal format. These instructions range from 0-255 therefore each hexadecimal value is two characters long. You know the ASCII characters, all those smiley faces and wierd symbols and blocks? These 256 characters are another representation of machine language instructions but don't worry about ASCII for now, we only need to deal with their Hex versions. So why are some lines longer? This is because some single assembly lines represent single machine-code instructions while others that are a bit more complex represent more than one. In the example, mov bp,sp requires two machine-code instructions, hence 2 hex characters on one line, 89 and E5. Organising the Hex instructions: ================================ Well the assembly still isn't ready for the QB format yet... We need to put all this into a DATA statement. Therefore we will break each hex character up with commas, put into one line and put the 'DATA' statement in front of it all. Our little example will now look like this: DATA 55,89,E5,5D,CB Remember to break up even instructions that share the same line, each hex instruction is two characters long only... No more, no less! Now let's save this and exit the editor, and enter QuickBasic. Using 'CALL ABSOLUTE': ====================== Now we're in QuickBasic itself, let's open up the file we've made. All we have is the row of DATA, so now it's time to get this and put it into a string variable, and in the more suitable ASCII format. First we need an algorithm to read the data, code it BEFORE the data... DIM asmdat$ 'Is the string we read the data directly into in steps. DIM asm$ 'The string we'll put it all into in ASCII, and call it. RESTORE FOR x% = 1 to 5 'We use 5 because our assembly uses 5 instructions. Modify this to suit your own code's size. READ asmdat$ asm$ = asm$ + CHR$(VAL("&H" + asmdat$)) NEXT x% The line asm$ = asm$ + CHR$(VAL("&H" + asmdat$)) is important. First the text "&H" is added before it so QuickBasic can recognise this as hexadecimal. Next, the VAL statement returns the numerical value of the current Hex code. In the case of the first instruction, 55, the normal decimal value is 85. Then, we have to convert this to the actual ASCII character. ASCII character 85 is the letter 'U', we use CHR$ to convert to this. Finally, we add the ASCII character to the asm$ string until the whole stream of instructions are in there. If we PRINTed the example it would appear similar to this: U…å]Ë Now our instructions are in a suitable format to be called as assembly by QuickBasic, and we do this with a statement called 'CALL ABSOLUTE'. In it's simplest form,the syntax to run our example code would be: DEF SEG = VARSEG(asm$) offset% = SADD(asm$) CALL ABSOLUTE (offset%) DEF SEG What this does is firstly get QB to point to the start of asm$'s location in the memory and make this the default segment. Then we use the SADD statement to get the offset location of the start of asm$. Finally, 'CALL ABSOLUTE' comes in to business, and runs the assembly code by going to the location of the string in the memory by it's offset and processing it. Now, here's the whole program in action... CLS DIM asmdat$ DIM asm$ RESTORE hexdata: FOR x% = 1 to 5 READ asmdat$ asm$ = asm$ + CHR$(VAL("&H" + asmdat$)) NEXT x% DEF SEG = VARSEG(asm$) offset% = SADD(asm$) CALL ABSOLUTE(offset%) DEF SEG END hexdata: DATA 55,89,E5,5D,CB If you copy and run this example it will appear as though nothing happens, because the assembly routine is very very simple and performs nothing practical. But If you see the 'Press Any Key to Continue' message you know it worked because it didn't crash! Exchanging Variable Data between QB and the Assembly: ===================================================== Of course calling assembly isn't much use to us if we can't feed the assembly routine values and return values from it. Within the brackets we can include other values, but they must be in INTEGER format, and the offset for the assembly instructions themself must always come last in the brackets. Here is an example of using CALL ABSOLUTE to find the sum of two numbers... CALL ABSOLUTE(x%, y%, z%, offset%) For a simple operation like this we'd need 3 variables, two for the two numbers to add and a third to put the result into. The values of the integers will be found at 2-byte intervals (integers are 2-bytes in size) and in order in the stack, but again I'll assume you already have the knowledge to retrieve the addresses and values you're dealing with. In the assembly code, if you donot use square brackets when MOVing the value of the stack at where the variable address info exists, you will need to refer to your variables like this... CALL ABSOLUTE(BYVAL x%, BYVAL y%, BYVAL z%, offset%) One important thing to remember though, we must appropriately use RETF at the end of the routine to clear the 'mess' you've made by putting values into it. In this case of three values, we must follow RETF by the amount of data in bytes we are dealing with. Because with 3 variables, the amount of data will be 3 * 2 bytes = 6 bytes, therefore we must use: RETF 6 This should clear any risk of crashing if the rest of the routine itself works correctly as well. The reason why I've not added any number after RETF in my other example is simply because the routine didn't use or require any data exchange with QB, so no data in the stack was used. ______________________________________________________________________________ CONCLUSION ========== Well, that should be enough to get you started, I'm actually still a beginner at assembly myself and using these techniques alone I can already do some simple calls between QB and assembly routines that work! Of course there's no point in going to the effort to write a routine that only adds numbers, unless you're a REAL speed-freak who needs the fastest and most optimized code possible! And remember, this is only one of many ways to put assembly in QuickBasic. You don't *have* to have your assembly in hex-form within DATA statements... Try BSAVing and BLOADing it as ASCII in resource files! There's also other methods of putting your assembly into your .BAS files... There are a couple of programs written out there that allow an easy way to transfer your assembly from code you've written in a text-file straight into HEX and in your .BAS file, with comments! Well that's it for now on assembly, when I learn some more on assembly myself perhaps I'll write another tutorial about it... C-ya round! ______________________________________________________________________________