home *** CD-ROM | disk | FTP | other *** search
/ Jason Aller Floppy Collection / 240.img / TASM20-1.ZIP / MANUAL.ZIP / BASIC.DOC next >
Encoding:
Text File  |  1990-05-07  |  36.5 KB  |  837 lines

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