home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 9 / 09.iso / l / l224 / 1.img / MANUAL.ZIP / BASIC.DOC next >
Encoding:
Text File  |  1990-10-29  |  38.6 KB  |  814 lines

  1.  
  2.             INTERFACING TURBO ASSEMBLER WITH TURBO BASIC
  3.             --------------------------------------------
  4.  
  5.  
  6. TABLE OF CONTENTS
  7. -----------------
  8.  
  9. 1. Introduction
  10. 2. Calling an Assembler Routine
  11. 3. Call Absolute
  12. 4. Call Interrupt
  13.  
  14.  
  15. 1. INTRODUCTION
  16. --------------
  17.  
  18.   Turbo Assembler's upward compatibility with Microsoft's Macro Assembler 
  19.   makes life easier for the Turbo Basic programmer. In this document, we'll
  20.   expand on some Turbo Basic examples currently in the Turbo Basic manual 
  21.   and supply others that illustrate how Turbo Assembler can extend the 
  22.   power of Turbo Basic.
  23.  
  24.   Note: When we refer to Turbo Basic, we mean versions 1.0 and greater.
  25.  
  26.  
  27. 2. CALLING AN ASSEMBLER ROUTINE
  28. -------------------------------
  29.  
  30.   Turbo Basic provides three general ways to call an assembler routine:
  31.  
  32.      1) You can use CALL to call a procedure containing inline code.
  33.      2) You can use CALL ABSOLUTE to a particular address in memory.
  34.      3) You can use CALL INTERRUPT and Turbo Basic's support for the
  35.         processor's interrupt-handling to branch to a routine.
  36.  
  37.   Whichever calling method you select, you must be sure to preserve the 
  38.   values of certain registers. CALL INTERRUPT is the least demanding in 
  39.   this respect: You need only be sure to save SS (stack segment) and SP 
  40.   (stack pointer). The other two methods require that you save the DS (data
  41.   segment) and BP (base pointer) registers, as well as SS and SP.
  42.  
  43.   "Preserving the registers" doesn't necessarily mean you must push all the 
  44.   registers on the stack, though that is the most typical way of ensuring 
  45.   their safety. Simple routines might not modify any of the registers, in 
  46.   which case, you might not need to take any precautions.
  47.  
  48.   We use the word "might" because it's generally a very good idea to avoid 
  49.   making assumptions, especially where assembler programming is concerned. 
  50.   Though your MS-DOS manual might specifically state that a particular 
  51.   interrupt will not alter the contents of the stack pointer or base pointer
  52.   (or any one of the other registers), that might not always be the case. 
  53.   MS-DOS does change, and some combinations of interrupts might contradict 
  54.   the information in the MS-DOS manual. It's better to be safe than sorry 
  55.   under such circumstances. Taking care to save the required registers will 
  56.   not adversely affect the performance of your routine, considering the
  57.   risks and added portability for new releases of MS-DOS.
  58.  
  59.  
  60.   Passing Parameters
  61.   ------------------
  62.   Turbo Basic passes parameters to assembler routines on the stack. All such 
  63.   calls are far; the last 4 bytes on the stack are the return address used 
  64.   by Turbo Basic when your routine is finished. The address of the first 
  65.   parameter you pass to your routine will be at [SP+4]; add two to that 
  66.   value for each register you push on the stack. Remember, stacks grow 
  67.   downward in memory to lower-numbered addresses.
  68.  
  69.   Each simple variable (other than arrays) passed on the stack will cause 
  70.   the stack to grow by 4 bytes; Turbo Basic passes both the segment (2 bytes)
  71.   and the offset (2 bytes) of such variables. This will be an important 
  72.   advantage later, as you will see.
  73.  
  74.   Parameters passed by value (as in constants and expressions) also take 
  75.   precisely 4 bytes on the stack. In this case, the value on the stack is 
  76.   not the value of the expression: It is the address of the temporary 
  77.   location in memory where the value is stored. This may seem roundabout, 
  78.   but it has two appreciable advantages. First, all assembler routines can 
  79.   handle passed values in precisely the same way. Second, routines that 
  80.   modify the value of a parameter mistakenly passed by value instead of by 
  81.   reference cannot alter an important area of memory.
  82.  
  83.   This way of using the stack can increase the standardization of your 
  84.   routines. The following example shows why this is advantageous. Suppose 
  85.   the value of the integer variable x% is precisely 4, and you have an 
  86.   assembler routine MYROUTINE that expects an integer to be passed. This 
  87.   routine will work exactly the same whether you invoke it with CALL 
  88.   MYROUTINE(x%) or CALL MYROUTINE(4%). If the routine were invoked with 
  89.   CALL MYROUTINE(4%) and tried to modify the value of the passed parameter,
  90.   it would modify an area of memory where the integer value 4 was temporarily
  91.   stored and no harm would be done.
  92.  
  93.   Note that the type was explicitly stated in the second case (4%). This is 
  94.   not absolutely necessary, though it is good practice. If Turbo Basic 
  95.   happened to assume that the value 4 is a single-precision number, your  
  96.   routine will use a bad value (2 bytes from a 4-byte, single-precision 
  97.   number) and run incorrectly. To make sure that your routines are passed 
  98.   the correct variable type, it is best to indicate the type of the variables
  99.   or values every time the routine is invoked.
  100.  
  101.   If you pass an array to a routine, the stack will grow by 60 bytes--most of 
  102.   the information that's passed to you is probably irrelevant. The Turbo Basic
  103.   manual recommends that you pass the relevant array parameters as integers 
  104.   instead of passing the entire array. Passing a few selected parameters 
  105.   rather than the whole array will save stack space, decrease the time needed
  106.   to call your routine, and make your routine more portable to later versions
  107.   of Turbo Basic.
  108.  
  109.   For example, suppose you have a simple routine that needs to push only the 
  110.   base pointer, BP. In that case, the value or address of the first parameter 
  111.   will be at [SP+6]. If you had pushed two registers, the address or value 
  112.   of the first parameter would be at [SP+8].
  113.  
  114.   Let's suppose the first parameter is an integer value and is passed by value
  115.   to the assembler routine. In that case, you can put the integer value into 
  116.   the CX register by simply writing
  117.  
  118.     push bp          ;save the base pointer
  119.     mov  bp,sp       ;make the base pointer equal the stack pointer
  120.     les  di,[bp+6]   ;ES contains the segment, DI the offset of value
  121.     mov  cx,es:[di]  ;now put the value into CX
  122.  
  123.   Note: The value will not be in the same segment as ordinary variables. You 
  124.   must take care to use the correct and complete address to access the value.
  125.   We'll say more about variables that are not in the current data segment 
  126.   later.
  127.  
  128.   On the other hand, if you knew the integer variable had been passed by 
  129.   reference instead of by value, [BP+6] would contain the offset address of 
  130.   the variable within its data segment. To put the value of that integer into
  131.   the CX register you could write
  132.  
  133.     push bp          ;save the base pointer
  134.     mov  bp,sp       ;make the base pointer equal the stack pointer
  135.     mov  bx,[bp+6]   ;put the address of the value in BX
  136.     mov  cx,[bx]     ;put the passed value into CX
  137.  
  138.   This routine assumes that the variable is located in the current data 
  139.   segment, and that only the variable's offset within that segment is needed
  140.   to update the variable's value.
  141.  
  142.   Passing variables is safer if you always assume the pass is done by value. 
  143.   If the variable is actually passed by reference, you will have lost nothing;
  144.   the complete variable address will include the current data segment. On the
  145.   other hand, if your routine assumes that the variable was passed by 
  146.   reference and it was not, the address you obtain will not be the correct
  147.   complete address because the segment will be wrong. Therefore, the routine
  148.   will either retrieve the wrong value or, if you attempt to alter the value
  149.   of the passed variable, will alter an incorrect area of memory with
  150.   unpredictable results.
  151.  
  152.   Passing variables by reference is much easier for variables such as strings,
  153.   arrays, and floating-point numbers. Those variables can be long enough to 
  154.   cause problems if they were actually passed on the stack. In addition, it 
  155.   takes nearly as much overhead to read a long variable from the stack as to 
  156.   obtain its address from the stack and manipulate the variable at a memory
  157.   location. For string variables (unless the string is very short, indeed)
  158.   it's unlikely there would be enough register space available to process 
  159.   the string without performing memory accesses anyway.
  160.  
  161.  
  162.   Variables Not in the Current Data Segment
  163.   -----------------------------------------
  164.   If the variable passed is not in the current data segment, then you'll 
  165.   need both the segment and the offset of the variable to access the value 
  166.   of the variable within your assembler program. Turbo Basic always passes 
  167.   both the segment and the offset of each variable on the stack; therefore, 
  168.   the complete address of each variable is always available to the programmer.
  169.  
  170.   The segment part of the address is in the 2 bytes immediately following 
  171.   the offset of the parameter. The most convenient way to use this 
  172.   information in your assembler programs is via the instruction LES.
  173.  
  174.   LES will load the indicated register with the offset value of the variable
  175.   and load the ES register with the segment part of the address. This 
  176.   guarantees you the full address of any variable, regardless of which 
  177.   data segment it is in.
  178.  
  179.   Again, suppose your routine needs to store the value of an integer variable
  180.   into the CX register. Since the ES register needn't be preserved, you can 
  181.   make use of LES:
  182.  
  183.     push bp           ;save the base pointer
  184.     mov  bp,sp        ;make the base pointer equal the stack pointer
  185.     les  di,[bp+6]    ;ES contains the segment, DI the offset
  186.     mov  cx,es:[di]   ;put the value of the variable in CX
  187.  
  188.   By passing the complete address of each variable, Turbo Basic makes it 
  189.   possible for the assembler programmer to write routines that are independent
  190.   of where the data is stored. If you rewrite your program and put variables 
  191.   or arrays into different data segments, you won't need to rewrite your 
  192.   assembler routines if you use the complete variable address and the 
  193.   LES instruction.
  194.  
  195.  
  196.   What Kind of CALL?
  197.   ------------------
  198.   There are two kinds of CALLs: far and near. Far CALLs leave the current 
  199.   code segment; near CALLs do not.
  200.  
  201.   In Turbo Basic, only CALL ABSOLUTE can cause any problems because it can be
  202.   located anywhere in memory. Therefore, Turbo Basic requires CALL ABSOLUTE
  203.   routines to terminate with a far return, and automatically generates a far
  204.   CALL when passing control to such routines.
  205.  
  206.   CALL INTERRUPT can implicitly generate a far call, but Turbo Basic handles
  207.   that internally. If you have written your own interrupt handlers, you only
  208.   need to use an IRET (return from interrupt) instruction to pass control
  209.   back to the Turbo Basic program.
  210.  
  211.   Inline assembler is inserted into your program when it is compiled. The 
  212.   code will generally be within the current code segment, but Turbo Basic
  213.   doesn't assume that; such routines also terminate with a far return. 
  214.   Turbo Basic will automatically generate the CALL and the return, so don't
  215.   use a RET instruction in your code. If you want to terminate the routine
  216.   somewhere before the end of the code, simply jump to a label at the end
  217.   of the code.
  218.  
  219.   Note: Since Turbo Basic doesn't use the DOS LINK program, you won't need
  220.   to concern yourself with declaring your routines PUBLIC, nor will you need
  221.   to declare them external within your program.
  222.  
  223.  
  224.   Popping the Stack
  225.   -----------------
  226.   Before the end of your routine, you should make sure that all the registers 
  227.   you have pushed onto the stack have been popped from the stack. It's easy 
  228.   to make a mistake in this area, especially if your routine conditionally 
  229.   PUSHes and POPs registers.
  230.  
  231.   If you pop too few registers, your routine will probably never return after
  232.   it is called, since Turbo Basic assumes that the last item on the stack is
  233.   the address to which it should return. If you pop too many, the same thing
  234.   will probably happen.
  235.  
  236.   Don't load or pop trash values into the segment registers because this can 
  237.   make your source code incompatible with future versions of DOS (protected
  238.   mode OS/2, for example).
  239.  
  240.  
  241.   Creating an Assembler Program for Turbo Basic
  242.   ---------------------------------------------
  243.   If you have created an assembler program and want to convert it into a .COM
  244.   file for use in a Turbo Basic program, you can still use the example batch 
  245.   file in the Turbo Basic manual:
  246.  
  247.     TASM %1;
  248.     Tlink /t %1;
  249.  
  250.   You should not include a stack segment because the assembler routine will 
  251.   use the stack furnished by Turbo Basic when running anyway.
  252.  
  253.   Turbo Assembler will default to a starting address of 100h if you do not 
  254.   provide an explicit ORG 100h statement at the beginning of your program. 
  255.   Still, it is better to explicitly state the ORG for later reference.
  256.  
  257.   If your routine is intended to run on an 80186, 80286, or 80386 processor, 
  258.   you can also use the .186, .286, and .386 directives at the beginning of 
  259.   your assembler code. Turbo Assembler then allows you to use opcodes 
  260.   applicable for those processors. This can be a very big advantage, as 
  261.   you will see.
  262.  
  263.  
  264.   CALLing an Inline Assembler Procedure
  265.   -------------------------------------
  266.   Suppose you have created an assembler routine and converted it into a .COM
  267.   file with Turbo Assembler. There are two ways you can use the result 
  268.   within your Turbo Basic program: with the $INLINE COM directive or with 
  269.   the $INCLUDE directive.
  270.  
  271.   The most direct way is to use the $INLINE COM filename method and have 
  272.   Turbo Basic insert the .COM file in the place you've indicated. This 
  273.   method is relatively simple to implement, but has several disadvantages:
  274.  
  275.   Turbo Basic has a limit of 16 $INLINE directives per procedure. This can 
  276.   cause problems if you're doing something rather complex (but it's rather 
  277.   unlikely).
  278.  
  279.   A more serious problem comes from the fact that the .COM files do not 
  280.   include documentation. You can include remarks in the calling program, 
  281.   of course, but it would be better if the .COM file included some 
  282.   documentation of its own.
  283.  
  284.   $INLINE .COM files can proliferate, too. Placing several of them in one 
  285.   file would be useful, especially if you often use a number of them 
  286.   together. (That's one of the reasons for using a library of assembler 
  287.   routines; unfortunately, it's not easy to create a library of .COM files.)
  288.  
  289.   Finally, $INLINE .COM files must be modified and reassembled if you alter 
  290.   them. This can be aggravating if the changes are relatively minor.
  291.  
  292.   Because of the way $INCLUDE COM works, you might want to convert the .COM 
  293.   files into a sequence of hexadecimal numbers that you can insert into 
  294.   programs via the $INCLUDE directive. Such routines can also be read from 
  295.   disk using the Turbo Basic editor's Read File command (Ctrl-K R); that 
  296.   way, your source file will explicitly show  hat is being included. This 
  297.   can be a big advantage for the Turbo Basic programmer.
  298.  
  299.   Since the hexadecimal codes are editable text, you can include or add 
  300.   comments. You can also use the Turbo Basic editor to make small changes 
  301.   in the inline code without having to re-assemble it, and you can put 
  302.   several routines into one file. By combining these techniques, you can 
  303.   effectively create a library of assembler routines for use with a family
  304.   of programs. You might be able to maintain such a library more easily than
  305.   if you had a formal library manager.
  306.  
  307.   If the routine is very long, the hexadecimal code file will be very large 
  308.   and might make the source file too large to edit comfortably. There is a 
  309.   limit of 64K on the largest file you can edit in one piece. If that 
  310.   becomes a problem, you can incorporate the hexadecimal file in your 
  311.   program as an $INCLUDE file. (Something that large wouldn't make your 
  312.   program more readable anyway.)
  313.  
  314.   Here is a small Turbo Basic program that will convert .COM files into 
  315.   hexadecimal files:
  316.  
  317.     'COM2INC.BAS
  318.     'This program converts COM files to $INCLUDE files with the Turbo
  319.     'Basic $INLINE meta-command for easy insertion in Basic programs.
  320.     DEFINT A-Z
  321.     'All variables will be integers
  322.     F$=COMMAND$
  323.     'Check to see if there's a command line
  324.     WHILE F$=""
  325.       PRINT"This program will convert COM files to $INCLUDE files"
  326.       PRINT"for use with Turbo Basic. The default file type of"
  327.       PRINT"the source file is COM. The default file type of the"
  328.       PRINT"output file is INC. You may override either default"
  329.       PRINT"by entering a specific file-type specification."
  330.       PRINT"If you enter no name for the output file, it will be"
  331.       PRINT"named the same as the input file, but will have a file"
  332.       PRINT"type specification of INC."
  333.       LINE INPUT"Enter the name of the file to convert: ";F$
  334.     WEND
  335.  
  336.     IF COMMAND$="" THEN
  337.       LINE INPUT"Enter the name of the desired output file: ";O$
  338.     END IF
  339.  
  340.     IF INSTR(F$,".")=0 THEN F$=F$+".COM"      'fix input spec
  341.     IF O$="" THEN
  342.       O$=LEFT$(F$,INSTR(F$,"."))+"INC"        'fix output spec,
  343.       ELSE
  344.         IF INSTR(O$,".")=0 THEN O$=O$+".INC"  'both ways
  345.     END IF
  346.  
  347.     OPEN"R",#1,F$,1          'input file will be read one byte
  348.     FIELD #1,1 AS A$         'at a time into A$
  349.  
  350.     LASTBYTE&=LOF(1)         'end of file position
  351.     OPEN"O",2, O$            'output file is opened
  352.     FOR I&=1 TO LASTBYTE&-1
  353.       GET 1,I&
  354.       X%=ASC(A$)
  355.       IF ((I&-1) MOD 5=0) THEN PRINT #2,"":PRINT #2,"$INLINE ";
  356.       PRINT #2,"&H";HEX$(X%);
  357.       IF ((I&-1) MOD 5<>4) THEN PRINT #2,",";
  358.     NEXT I&
  359.     GET 1,LASTBYTE&
  360.     PRINT #2,"&H";HEX$(ASC(A$))
  361.     PRINT"Conversion complete. ";LASTBYTE&;" bytes read."
  362.     PRINT 0$;" contains ";LOF(2);" bytes."
  363.     CLOSE
  364.     END
  365.  
  366.   This program will output a file with up to five hex codes per line. Each 
  367.   line will begin with the $INLINE directive, and the resulting file should 
  368.   have enough room in it for comments you might want to add. If you want to 
  369.   put more or fewer hex codes on a single line, you need only change the 
  370.   references to MOD 5 to MOD N, where N is greater or less than 5.
  371.  
  372.   If you want to make small changes to the routines you have written and 
  373.   converted to hex codes, you should be able to do so without having to 
  374.   recreate the whole routine from scratch, provided the changes are small 
  375.   enough and your documentation is complete.
  376.  
  377.  
  378.   Locating a Turbo Basic Routine in Memory
  379.   -----------------------------------------
  380.   There are three general ways of finding the location of a routine in 
  381.   memory:
  382.  
  383.      1) You can have the routine itself return its address.
  384.      2) You can group a series of routines together and have a single
  385.         routine return an address applicable for all of them.
  386.      3) You can look for a special sequence of bytes within the
  387.         computer's memory.
  388.  
  389.   To create a routine that will return its address, you could use code 
  390.   similar to the following:
  391.  
  392.     xy:  mov  ax,cs          ;move the code segment register to AX
  393.          push bp             ;save the base pointer
  394.          mov  bp,sp          ;and copy the stack pointer into BP
  395.          les  di,[bp+6]      ;ES contains the segment, DI the offset
  396.          mov  es:[di],ax     ;store the CS value to first parameter
  397.          mov  dx, offset xy  ;get the current offset
  398.          les  di,[bp+0ah]    ;address of second parameter
  399.          mov  es:[di],dx     ;store offset value to second parameter
  400.          jmp  fin            ;jump around "real" code
  401.                              ; real code would be here
  402.     fin: pop  bp             ;restore BP and return
  403.  
  404.   You will need to pass two integers to this routine's variables; it will 
  405.   return the code segment in the first and the offset in the second. The 
  406.   problem: All that code is useless after it has been used once. In fact, 
  407.   it's worse than useless because the code must be removed before the 
  408.   routine can be run normally.
  409.  
  410.   Unless the routine you want to use can gain a lot of speed from the 
  411.   modification you make, it's likely that making the modification will cost 
  412.   more time than you'll save. The modification had better be good; unless 
  413.   your routine is completely relocatable, the working code will be preceded 
  414.   by a lot of NOPs.
  415.  
  416.   You can still determine the address of the routine, however. If you had 
  417.   grouped several routines together and put labels in your Turbo Basic 
  418.   program so you could call the one you wanted, wouldn't that allow you to 
  419.   include a "tell me the address" routine in with the others?
  420.  
  421.   The answer is no. Remember, Turbo Basic handles the RET instruction for you.
  422.   Since the routines are given different names, Turbo Basic will assume each 
  423.   is relocatable code. There is no guarantee that the separate routines will 
  424.   be in the same area of memory in the final .EXE file. Even if the routines 
  425.   are in the same area of memory and in the same order, you won't know how 
  426.   many bytes of code Turbo Basic put between them, and so you won't know 
  427.   where to go in each to make the changes you want.
  428.  
  429.   The third method of determining a routine's address is the signature method.
  430.   To use this, you search the computer's memory for a memory location 
  431.   containing the particular sequence of bytes that identify the routine you 
  432.   want to change.
  433.  
  434.   The signature method also has problems. First, such a search will take a 
  435.   good deal of time. Second, there is no guarantee that you have definitely
  436.   located the routine even if you match the signature. Third, each routine 
  437.   must contain a different signature; this wastes code space and adds to the
  438.   time needed to modify all routines.
  439.  
  440.   To make routines the program can modify, you need a better way of 
  441.   determining the address of the routine, and you need an easier way of 
  442.   altering the instructions in the routine.
  443.  
  444.   To find a solution to these problems, read the next section, where we 
  445.   consider a special way to use routines that you can modify from within 
  446.   your Turbo Basic program.
  447.  
  448.  
  449.   Hiding Strings
  450.   --------------
  451.   Turbo Basic allows a maximum of 64K for string space. Sometimes you will 
  452.   need every byte of that space, but quoted string constants (such as those
  453.   used for menus and prompts) also take up string space.
  454.  
  455.   Code space, however, is limited only to a maximum of 16 segments, each up 
  456.   to 64K long. Life would be grand if you were allowed to store some of 
  457.   those string constants in code space, where they wouldn't reduce the 
  458.   space available for dynamic string data. Fortunately, this is not too 
  459.   hard to do.
  460.  
  461.   Consider the following routine:
  462.  
  463.   ;This routine takes two integer parameters and returns
  464.   ;the segment and offset of the text in the body of the program.
  465.   ;
  466.           push   sp
  467.           mov    bp,sp
  468.           mov    dx,offset show         ;location of string
  469.           mov    ax,cs                  ;code segment to AX
  470.           les    di,[bp+6]              ;ES:DI point to parameter
  471.           mov    es:[di],dx             ;report string location
  472.           les    di,[bp+0ah]            ;next parameter
  473.           mov    es:[di],ax             ;report the code segment
  474.           jmp    fini                   ;and go back show
  475.           DB     'Any text we like here and as much as we want'
  476.           DB     'For as long as we want, terminated with any'
  477.           DB     'character we like. Here, a null.',0
  478.     fini  pop    bp
  479.  
  480.   The effect of this routine is somewhat different than the ones we proposed
  481.   earlier for program-modifiable inline code. For one thing, you're not 
  482.   storing code (though Turbo Basic will process the resulting .COM file as 
  483.   if it were all code); instead, you're storing data.
  484.  
  485.   The routine is returning the current address of the data stored within it.
  486.   If you wanted to know the length of the data, you could have the routine 
  487.   report that, too, though you'd have to pass a third integer parameter.
  488.  
  489.   Since you know where the text is in memory, you can use the PEEK 
  490.   instruction to read the string data into string space any time you want 
  491.   to print the message. When you've finished printing the message, you can
  492.   throw away the now unneeded string; it will still be available in code
  493.   space if you need it again.
  494.  
  495.   You can determine the number of bytes available in this routine. In 
  496.   particular, you can determine the number of bytes preceding the text. Just
  497.   replace all but the final instruction with your own code: Since you 
  498.   know where the routine is and how big it is, you can use BLOAD to 
  499.   overwrite it. As far as the Turbo Basic .EXE file is concerned, nothing 
  500.   will have changed--even though the whole routine is now different.
  501.  
  502.   Usually, this technique is not necessary. Saving strings in code space 
  503.   is sometimes useful, but replacing a whole routine with another is better
  504.   done by using CALL ABSOLUTE.
  505.  
  506.  
  507.   CALL ABSOLUTE
  508.   -------------
  509.   CALL ABSOLUTE is given a short mention in the Turbo Basic manual for several
  510.   reasons. The first one is that Turbo Basic has less control over such 
  511.   routines. Second, such routines are commonly used because they were written
  512.   for the Basic interpreter: Turbo Basic is different enough from the Basic
  513.   interpreter that those routines might not work. Third, future operating
  514.   systems may not allow CALL ABSOLUTE routines to be used. In particular, 
  515.   operating systems that make a clear distinction between code space and 
  516.   data space might refuse to allow the processor to execute instructions 
  517.   located in data space. Fourth, routines called by CALL ABSOLUTE may only be
  518.   passed simple integer variables. This is not as much of a restriction as 
  519.   it seems, since simple integer variables can contain segment and offset 
  520.   addresses of any type of variable. Still, it can make parameter-passing 
  521.   somewhat more time-consuming.
  522.  
  523.   For this discussion, we'll assume you're using MS-DOS 2.0 or greater and 
  524.   that the operating system permits the processor to execute instructions 
  525.   anywhere in memory.
  526.  
  527.  
  528.   CALL ABSOLUTE to Fixed Memory Locations
  529.   ---------------------------------------
  530.   If you have a family of programs that share the same set of routines, 
  531.   it makes sense to put those routines in a fixed location. Then, each 
  532.   program that needs to use those routines can call them at a particular
  533.   address. Turbo Basic allows you to safeguard addresses in high memory 
  534.   for this purpose with the MEMSET command.
  535.  
  536.   ENDMEM is frequently used with MEMSET. ENDMEM will return a long integer
  537.   that will be the last memory location the compiled Turbo Basic program can
  538.   use. Routines are commonly placed in high memory at some fixed location 
  539.   beginning below this limit.
  540.  
  541.   If you have such a set of routines, you will need to call them with the 
  542.   CALL ABSOLUTE command. To put them into high memory, use BLOAD. You will 
  543.   need to use DEF SEG to set the segment address into which the routines 
  544.   should be loaded, and specifically declare the offset address at which they
  545.   should be loaded.
  546.  
  547.   When you create these routines with Turbo Assembler, you should take care 
  548.   to follow these rules:
  549.  
  550.      1) Unless the routine is intended to run at one and only one
  551.         address, all transfers (JMPs and CALLs) in the program should be
  552.         completely relocatable. (A complete discussion of relocatable
  553.         code is beyond the scope of this chapter.)
  554.  
  555.      2) If the program is intended to be run at only one address, you
  556.         must specify that address in the ORG directive in the assembler
  557.         source code.
  558.  
  559.  
  560.   CALL ABSOLUTE to Other Locations in Memory
  561.   ------------------------------------------
  562.   Turbo Basic will also allow you to use CALL ABSOLUTE to memory locations 
  563.   that might vary each time you run a program. The most typical way to do 
  564.   this is to load the assembler routine into an array outside of normal data
  565.   space.
  566.  
  567.   Consider the following code fragment:
  568.  
  569.     DEFINT a-z
  570.     $DYNAMIC                                 'arrays will be dynamic
  571.     DIM RoutineArray(10000)                  '20,002 bytes allocated
  572.     'miscellaneous code here
  573.     whereseg% = VARSEG(ROUTINEARRAY(0))      'segment address
  574.     whereoffset% = VARPTR(ROUTINEARRAY(0))   'offset address
  575.     DEF SEG = whereseg%                      'set default segment
  576.     BLOAD"COMFILE", whereoffset%             'read routine in
  577.     CALL ABSOLUTE whereoffset%(parameter%)   'call the routine
  578.     DEF SEG                                  'return to default
  579.  
  580.   If you want to use a number of routines, you could load each in turn into 
  581.   the same array. If you wanted to use special versions of the routines, you 
  582.   could select which ones to load, and load each into a different array. 
  583.   Finally, if you wanted to alter portions of the array, you could do so by 
  584.   simply changing the values of selected array elements.
  585.  
  586.   As you can see, routines designed for use by CALL ABSOLUTE can be far more 
  587.   easily located and modified than $INLINE ones. The difficulty with the 
  588.   CALL ABSOLUTE routines is that they must be fully relocatable if they are 
  589.   to be generally useful. For short routines, this might not be a problem; 
  590.   for complex routines, it can be quite difficult to write fully relocatable
  591.   code.
  592.  
  593.   You can also BLOAD routines into string variables. Here, you must be 
  594.   especially careful. If you attempt to BLOAD a routine longer than the 
  595.   string variable, you will overwrite some other string. If that string is 
  596.   modified, part of your BLOADed routine might also be modified.
  597.  
  598.   String variables can move, too. Even if the routine is loaded correctly 
  599.   into a string, you should take care to use VARSEG and VARPTR to establish 
  600.   the address of the string immediately before attempting to call the routine.
  601.  
  602.   Turbo Basic strings are not stored the same way numeric variables are. 
  603.   If you perform VARPTR(A%), you'll get the address of the integer variable 
  604.   A%. If you do VARPTR(A$), you'll get the address of the string descriptor 
  605.   for A$. The memory location 2 bytes further along will contain the actual 
  606.   address of the string in string space. To come up with the same result as 
  607.   VARPTR(A%), you'd have to do something equivalent to this:
  608.  
  609.     A% = VARPTR(A$)
  610.     A% = A%+2
  611.     StringAddress% = CVI(CHR$(PEEK(A%)) + CHR$(PEEK(A%+1)))
  612.  
  613.   Though putting assembler routines into character strings used to be quite 
  614.   popular, it's a lot less appealing now that integer arrays can be 
  615.   dimensioned and erased. It would be better to use integer arrays for 
  616.   CALL ABSOLUTE routines and avoid the extra difficulties of accessing 
  617.   constantly movable string data.
  618.  
  619.   If you want to avoid using BLOAD, it's also possible to load .COM files 
  620.   into strings by using binary file I/O; that is, open the .COM file as type 
  621.   binary and read the correct number of bytes into the string. You can use 
  622.   the same approach to read the data into an integer array. BLOAD is faster 
  623.   and easier, however.
  624.  
  625.  
  626.   Other Problems with CALL ABSOLUTE
  627.   ---------------------------------
  628.   Code read from disk for use by CALL ABSOLUTE suffers from several important
  629.   disadvantages, the most important being the requirement for relocatability, 
  630.   which we mentioned previously.
  631.  
  632.   Another serious problem is that the routines must be read from disk 
  633.   separately from the main program, introducing several possibilities for 
  634.   error. The required code might not be present on the disk, or might be 
  635.   present but damaged.
  636.  
  637.   A third problem is that the time spent reading the code from disk might 
  638.   remove the very reason to have the routine in assembler--rather than in 
  639.   Turbo Basic--in the first place.
  640.  
  641.   Despite these stumbling blocks, the flexibility of pulling in different 
  642.   routines, of having code that can be modified under program control, and 
  643.   of reducing the amount of code that needs to be present in memory at any 
  644.   time can be strong enough reasons to consider using the CALL ABSOLUTE 
  645.   construction.
  646.  
  647.  
  648. 4. CALL INTERRUPT
  649. -----------------
  650.   The third and final way to access assembler routines from within Turbo 
  651.   Basic is perhaps both the easiest way to avoid assembler and the most 
  652.   difficult way to use assembler.
  653.  
  654.   Most programmers will use CALL INTERRUPT to access the normal MS-DOS 
  655.   services. In this situation, there is really no assembler to worry. Instead,
  656.   you need to remember the following:
  657.  
  658.     Name    Register
  659.  
  660.     REG 0   Flags
  661.     REG 1   AX
  662.     REG 2   BX
  663.     REG 3   CX
  664.     REG 4   DX
  665.     REG 5   SI
  666.     REG 6   DI
  667.     REG 7   BP
  668.     REG 8   DS
  669.     REG 9   ES
  670.  
  671.   To set the value of a register, use the REG statement:
  672.  
  673.     REG 3,&H0F01
  674.  
  675.   This sets the value of the CX register to hexadecimal 0F01. Register CH 
  676.   will be hexadecimal 0F, and CL will be 01.
  677.  
  678.   To read the value of a register, use the REG function:
  679.  
  680.     A%=REG(3)
  681.  
  682.   This assigns the current value of the CX register to A%.
  683.  
  684.   The following example causes the screen to do a reverse scroll, from line 1 
  685.   to 24:
  686.  
  687.     REG 3,0                  'row zero, column zero for top
  688.     REG 4,&H175F             'row 23, column 79 for bottom
  689.     REG 2,&H70               'color 7,0
  690.     REG 1,&H0701             'bios function 7, scroll 1 line
  691.     CALL INTERRUPT &H10      'video interrupt 10h
  692.  
  693.   The equivalent routine is more difficult to write in assembler and won't 
  694.   work any better. In fact, the CALL INTERRUPT form is easier to modify when
  695.   needed.
  696.  
  697.   The whole procedure is normally very easy. However, for more advanced 
  698.   programmers, interrupts can be used for other than the normal MS-DOS 
  699.   services.
  700.  
  701.   Interrupts are often used to manage devices (such as temperature sensors, 
  702.   remote recorders, timers, and samplers). To use such an interrupt, you must
  703.   first find an unused interrupt. (Many are used by MS-DOS, and others may 
  704.   be used by devices such as tape drives and storage devices such as the 
  705.   Bernoulli Box.)
  706.  
  707.   Within the Turbo Basic program, you will point the interrupt vector to the 
  708.   routine written with Turbo Assembler. As noted in the Turbo Basic manual, 
  709.   the interrupt routine should preserve the values of the SS and SP registers;
  710.   any of the others can be modified. At the end of the routine, control is 
  711.   passed back to the Turbo Basic program via an IRET instruction.
  712.  
  713.   It is possible to use the techniques mentioned already to determine the 
  714.   location of a routine and to put that location in the interrupt vector, but 
  715.   it's better to put interrupt routines either in high memory or to BLOAD 
  716.   them like routines for CALL ABSOLUTE.
  717.  
  718.   Interrupt routines included in your programs via the $INLINE command will 
  719.   need to be located somehow. BLOADed routines stored in integer arrays might 
  720.   not be in the same location from time to time, but at least the location 
  721.   will be known. Still, putting interrupt handlers into such arrays will mean 
  722.   that all of the code in the interrupt routine must be fully relocatable.
  723.  
  724.   For that reason, interrupt routines are usually put in fixed locations in 
  725.   high memory. If you decide to use that approach, be sure to include the 
  726.   proper ORG command in your Turbo Assembler source code.
  727.  
  728.  
  729.   Sample program
  730.   ---------------
  731.     FILLIT2$ = CHR$(&HFC)+CHR$(&HF3)+CHR$(&HAB)+CHR$(&HCB)
  732.     '          cld        rep       stosw       ret
  733.     DIM a%(100)               'integer array whose elements are all 0
  734.     WHERE%=VARPTR(FILLIT2$)   'this locations stores the length
  735.     WHERE%=WHERE%+2           'and this is the string location
  736.     CLS:PRINT PEEK(WHERE%),PEEK(WHERE%+1)
  737.     HERE%=PEEK(WHERE%)+256*PEEK(WHERE%+1)
  738.                               'and this is the string location
  739.     DEF SEG                   'not necessary here, but good
  740.                               ' programming practice
  741.     WHERE%=PEEK(0)+256*PEEK(1)
  742.     DEF SEG=WHERE%            'string segment is the first word in
  743.                               ' default DS
  744.     REGES%=VARSEG(a%(0))
  745.     REGSI%=VARPTR(a%(0))
  746.     REG 1,5%                  'put the fill value into AX
  747.     REG 3,101%                'number of elements to fill, 0 to 100
  748.                               ' inclusive into CX
  749.     REG 9,REGES%              'segment of the array to fill into ES
  750.     REG 6,REGSI%              'offset to first array element into SI
  751.     CALL ABSOLUTE HERE%       'fill the array with the value 5
  752.     DEF SEG
  753.     FOR i%=0 TO 100:PRINT a%(i%);:NEXT i%
  754.     PRINT
  755.     PRINT REG(1),REG(3),REG(9),REG(6):STOP
  756.     CALL FILLIT(a%(0),-1%,101%)  'fill the array with the value -1
  757.     FOR i%=0 TO 100:PRINT a%(i%);:NEXT i%
  758.     PRINT
  759.     END
  760.     SUB FILLIT INLINE
  761.     $INLINE &H55,&H8B,&HEC,&HC4,&H7E
  762.     $INLINE &HE,&H26,&H8B,&HD,&HC4
  763.     $INLINE &H7E,&HA,&H26,&H8B,&H5
  764.     $INLINE &HC4,&H7E,&H6,&HFC,&HF3
  765.     $INLINE &HAB,&H5D
  766.     END SUB
  767.  
  768.  
  769.     ;Routine to transfer an arbitrary number of elements with an
  770.     ;arbitrary value into an integer array for call absolute.
  771.     ;
  772.     ;Calling syntax is
  773.     ;REG 1,FILLVALUE%          'AX has the fill value
  774.     ;REG 3,FILLCOUNT%          'CX has the number of elements to fill
  775.     ;REG 9,VARSEG(ARRAY(0))    'ES has the segment of the array
  776.     ;REG 6,VARPTR(ARRAY(0))    'DI is the offset to first array elem
  777.     ;
  778.     ;CALL ABSOLUTE FILLIT2
  779.     ;FILLIT2 is the address of the absolute routine and DEF SEG
  780.     ;will have set the default program segment to that of
  781.     ;FILLIT2 before the CALL ABSOLUTE.
  782.     PROGRAM SEGMENT
  783.     START   PROC FAR        ;this will force a far return
  784.         ASSUME cs:PROGRAM
  785.         push bp             ;save the base pointer
  786.         cld                 ;clear direction flag
  787.         rep                 ;next instruction repeats until CX is 0
  788.         stosw               ;store AX to ES:DI and increment DI by 2
  789.         pop bp              ;restore base pointer
  790.         ret                 ;intersegment (far) return
  791.     START   ENDP
  792.     PROGRAM ENDS            ;end of segment
  793.         END
  794.  
  795.     ;Routine to transfer an arbitrary number of elements with an
  796.     ;arbitrary value into an integer array. Calling syntax is:
  797.     ;CALL FILLIT(ARRAY(0),FILLVALUE,NUMTIMES)
  798.        ORG 100h
  799.     PROGRAM SEGMENT
  800.         ASSUME cs:PROGRAM
  801.         push bp           ;save the base pointer
  802.         mov  bp,sp        ;move stack pointer to BP
  803.         les  di,[bp+0eh]  ;get offset address of # of elems to fill
  804.         mov  cx,es:[di]   ;number of elements to fill into CX
  805.         les  di,[bp+0ah]  ;get offset address of fill value
  806.         mov  ax,es:[di]   ;put fill value in AX
  807.         les  di,[bp+6]    ;offset address of array to fill
  808.         cld               ;clear direction flag
  809.         rep               ;next instruction repeats until CX is zero
  810.         stosw             ;store AX to ES:DI and increment DI by two
  811.         pop bp            ;restore base pointer
  812.     PROGRAM ENDS          ;end segment--no RET instruction
  813.         END
  814.