Run-Time Fixup Facts; Pointer to STRINGASSIGN Hangs If No /O (68186)



The information in this article applies to:

  • Microsoft Basic Professional Development System (PDS) for MS-DOS and MS OS/2 7.1
  • Microsoft Basic Professional Development System (PDS) for MS-DOS and MS OS/2 7.0

This article was previously published under Q68186

SUMMARY

The C and assembly languages support "pointers to functions" (or "function pointers" for short). However, function pointers to routines that are in the Basic run-time module (such as STRINGLENGTH, STRINGADDRESS, and STRINGASSIGN) do not work unless you compile with BC /O. Function pointers to routines in the Basic run-time don't work properly without /O because the "back patcher" used in Basic's run-time system can't back patch indirect far calls.

(Function pointers to routines in the Basic run-time module do work with Basic's /O because /O uses no back-patched fixups.)

Your assembler or C program can only make direct calls (by name) to run-time module routines, such as STRINGLENGTH, STRINGADDRESS, and STRINGASSIGN, when you compile the invoking Basic program without /O. This limitation is by design.

MORE INFORMATION

Note that the STRINGLENGTH, STRINGADDRESS, STRINGASSIGN, and STRINGRELEASE routines allow you to use Basic PDS's far variable-length strings in mixed-language (C, assembler, and Pascal) programming. For more information, see pages 368-375 of the "Microsoft Basic 7.0: Language Reference" (for 7.00 and 7.10).

How "Back Patching" Operates in Basic's Run-Time Module

If you compile without the BC /O option, you cannot use a function pointer to a Basic run-time routine, such as STRINGLENGTH, STRINGADDRESS, or STRINGASSIGN, because of the back-patched fixups used by the run-time module.

Under MS-DOS, Basic run-time modules are analogous to (but not exactly the same as) an OS/2 or Windows DLL (dynamically linked library). Under OS/2, a Basic run-time module really is a DLL. Below is a quick description of how Basic's run-time module system works under MS-DOS:

  1. At LINK time, a small Basic .LIB file satisfies (provides "fixups" for) all of the calls for run-time support. For example, you might link to BRT71ENR.LIB (for 7.10) or BRT70ENR.LIB (for 7.00).
  2. During run time, a call to STRINGADDRESS jumps to the fixup location determined at LINK time. At this fixup location is information that describes the offset in the run-time module (BRT7nxxx.EXE) where this call should really jump. (This information is available only at run time, and not at LINK time.)
  3. Now that the call to STRINGADDRESS really knows where to jump, the call is physically "back patched" with the correct far address for the actual call to STRINGADDRESS. (This is called a back-patched fixup.)
  4. The first call to STRINGADDRESS can now execute the correct routine located in the BRT7nxxx.EXE run-time module.
  5. Now that the first STRINGADDRESS call has been "back patched," all subsequent calls to STRINGADDRESS will jump straight to the STRINGADDRESS routine in BRT7nxxx.EXE (the Basic run-time module), instead of sidetracking through the small fixup routine provided in BRT7nxxx.LIB.
The problem with attempting to use a function pointer to the STRINGADDRESS routine when not compiled with /O is that the run-time back patcher doesn't know how to back patch indirect far calls. The indirect far call uses 4 bytes of stack space, whereas the back-patch pointer expects to use 5 bytes of stack space, so the back patch is off by 1 byte on indirect far calls, which causes the hanging problem. Correct back patching can only be done for direct calls to STRINGADDRESS and STRINGLENGTH.

When compiled with /O, Basic directly calls support routines instead of back patching the calls. This explains why the code example below works with BC /O, but fails when compiled without BC /O.

The following three methods work around this design limitation:

  1. Compile all programs with /O. -or-

  2. Change the function pointer calls (calls to stradr and strlen in the assembler example below) to direct calls to STRINGADDRESS and STRINGLENGTH. -or-

  3. Write two assembly PROCedures, with names such as stradr and strlen. The PROC called stradr would directly call STRINGADDRESS. The PROC strlen would directly call STRINGLENGTH.

Code Example

The Basic routine below works correctly when compiled with BC /O, but when compiled without /O, the Basic routine hangs when calling the assembly routine (at run time).

TEST.BAS

' Basic code to call the assembly procedure below.
DECLARE SUB TestText (a$, length%, SegAddress&)
DIM length%, SegAddress&
a$ = "blah"
PRINT "A$'s segmented address is: ", HEX$(SSEGADD(a$))
PRINT "Calling the assembly routine."
CALL TestText(a$, length%, SegAddress&)
PRINT length%
PRINT "A$'s segmented address is: ", HEX$(SegAddress&)
' This should be the same as the value printed above.
PRINT "Back from the assembly call."
END
				

TESTTEXT.ASM

        extrn   STRINGADDRESS:FAR
        extrn   STRINGLENGTH:FAR
.MODEL MEDIUM, Basic
.DATA
        stradr  dd  STRINGADDRESS  ; Attempt to make a pointer to
                                   ; StringAddress
        strlen  dd  STRINGLENGTH   ; A pointer to STRINGLENGTH
.CODE
        ; Pointer to  a$'s adescriptor is at [BP + 10]
        ; Pointer to length%    is at [BP + 8]
        ; Pointer to SegAddress is at [BP + 6]
        PUBLIC TestText
TestText PROC
         push   bp
         mov    bp,sp
         mov    ax, [BP+10] ; Address of A$'s descriptor off the stack
         push   ax          ; Pass it to STRINGLENGTH.

         ; call   STRINGLENGTH  ; This direct call always works.
         call   DWORD PTR strlen ; This fails without /O.

         mov    BX,[BP+8]   ; BX Now points to length%
         mov    [BX],AX     ; Put the value return by STRINGLENGTH in
                            ; length%
         mov    ax, [BP + 10] ; Get the address of the descriptor.
         push   ax            ; Pass it to StringAddress.

         ; call   STRINGADDRESS ; ** This direct call always works.
         call   DWORD PTR stradr ; ** This fails without /O.

         mov    bx,[BP+6]     ; Get the address of SegAddress&
         mov    [bx],ax       ; The segment was stored in ax,
         mov    [bx+2],dx     ;  the offset was stored in dx.
         pop    bp            ; Restore the BP register's value.
         ret    2
TestText ENDP
         END
				

REFERENCES

A fixup record (or "fixup" for short) is composed of binding and relocation information (for the linker) put into the object code (.OBJ) by the compiler. For background information about object code format and fixup records, see pages 650-651 and 682-693 in the "MS-DOS Encyclopedia" published by Microsoft Press (1988).

Modification Type:MajorLast Reviewed:10/20/2003
Keywords:KB68186