ExoSpace version 1.0 API (revision #1) -------------------------------------- ExoSpace(tm) is based on the Rational DOS/16M(tm) 286 DOS Extender. As a 286 DOS extender it executes 99% of the code written for real mode applications in protected mode. In fact, as long as a few protected mode rules are followed converting an application to protected mode can be relatively easy. Unfortunately, programmers who have no experience writing protected mode applications routinely break one of more of these rules and breaking just one rule in one location within a program can make the program unusable in protected mode. The purpose of this document is to review protected mode rules, to demonstrate where protected mode programming is radically different from real mode programming, and to define the API functions available from ExoSpace that may be necessary to your C or Assembler functions. When a program breaks one or more of the protected mode rules, a General Protection Fault or GP Fault occurs. This causes ExoSpace to print out a message and abort the program back to DOS. In truth, GP Faults are a terrific debugging tool. They either show where you are breaking protected mode programming rules or they highlight bugs that would normally trash memory in real mode. The quickest way to try to convert an application or third party library to protected mode is to link the application with ExoSpace creating a .MAP file with the "MAP A" command and then simply running the application to see if it works. If it generates GP faults then you can look at the map file to see what module or function generated the fault. You can then look at that module or function to see what caused the GP Fault. In most cases you can narrow down any problems using this method. It is also best to begin the process of conversion under plain DOS using a VCPI or XMS extended memory manager. Do not begin by running in a Windows DOS box or under any other memory manager that only emulates the DPMI protocol. This is because the DPMI protocol is much more restricting. When your program is not running under DPMI, it can expect the most used selector values to point to the same memory they do in real mode. For example, when not under DPMI you can expect selectors 0xB000 and 0xB800 to point to video memory. Once you have the application working under a non-DPMI manager you can then begin the process of making it compatible with DPMI by using the ExoProtectedPtr() function to create protected mode pointers to real mode addresses. We also have a protected mode debugger available for those who have a great deal of work to do in making their code compatible with protected mode. The debugger will halt the program on a GP fault allowing you to see exactly which instruction caused the problem. If you are having a great deal of trouble tracking down problems in your code, please contact us about the debugger. We also suggest that you take some time to read a few books about protected mode programming with DOS extenders. Your current code may be easy to convert to protected mode (some code may require absolutely no changes, especially C code), but protected mode programming is the future direction DOS will take and it may be useful to be more familiar with it. The book we found helpful was: Extending DOS Ray Duncan Addison Wesley Publishing ISBN 0-201-56798-9 (The first edition uses the Rational DOS/16M extender and the second edition uses the PharLap 286 DOS extender.) Protected Mode Rules -------------------- 1) A selector value (referred to as a segment value in real mode) loaded into DS, ES, SS, and CS actually references a "chunk" of memory that may occur anywhere within the physical address space and can even be moved around by the operating system. Therefore, when dealing with selector values: a) Never load DS or ES with "junk" values or a GP fault may occur. Never use DS or ES as "scratch" registers that can be loaded with arbitrary values. Everytime DS and ES is loaded with a value the microprocessor checks to make sure it is a valid selector value and generates a GP fault if it is not. b) Never perform "segment arithmetic" on a selector value. This is done in real mode code to either "normalize" a pointer or to access data that is greater than 64K in size. A selector value is a unique ID which has no relationship to the data it points to. c) All selectors have a size associated with them and attempts to access data at an offset greater than that size will result in a GP fault. This is one of the greatest strengths of a 286 DOS Extender because most obscure bugs in real mode code occur when a "wild pointer" is used. This will usually cause a GP fault in protected mode because either the selector or offset value will be invalid. d) DS and ES can be loaded with a selector value of 0 (a NULL pointer in C parlance), but never try to write to or read from a NULL pointer. A great deal of C code has this mistake in it. Attempts to read from or write to a NULL pointer will generate a GP fault. e) Segment values in real mode always point to a physical address equal to the segment value multiplied by 16, but selector values in protected mode may point to anywhere in memory. Therefore, you cannot assume standard addresses such as 0xB000 and 0xB800 (used to address a monochrome or color screen) will point to the same memory they do in protected mode. In truth, under DPMI only selector 0x40 is guaranteed to point to the BIOS data area - no other selector value is predefined. For maximum portability you will have to use the ExoProtectedPtr() function to address absolute physical addresses. 2) You cannot execute code in a data segment and you cannot write to data in the code segment. 3) ExoSpace handles most of the standard DOS interrupts transparently, but when passing pointers to buffers to software interrupt calls not handled transparently by ExoSpace, the buffers must exist in low DOS memory and the pointers must be converted from protected mode selector:offset pairs to real mode segment:offset pairs. ExoSpace errata --------------- Although ExoSpace does provide a DOS protected mode version of the int86/intdos() functions, they cannot be used when calling a real mode interrupt which returns a value in DS or ES. Real mode segment values stored in DS or ES are invalid in protected mode. When a real mode interrupt modifies DS or ES in an int86/intdos() call, ExoSpace will most likely replace the value with a 0 to prevent a GP fault. Use of ExoRMInterrupt() is always preferred because it should be slightly faster than the int86/intdos() functions and will return the real mode values of DS and ES. In 286 protected mode, the IN and OUT instructions work the same as they do in real mode. No special handling of the I/O ports in necessary. Under ExoSpace, the PSP is the same as it is in real mode. It is located in low DOS memory so both DOS and ExoSpace have access to it. All of the pointer values are real mode values except for the segment address of the environment at offset 0x2C. The DOS16M environment variable can be used to restrict the amount of memory ExoSpace uses. This can be used to test your applications with various amounts of memory. For example, to test your application under a 2MB configuration use "SET DOS16M=:2MB" (be sure to use the :). Interrupts ---------- Many interrupts are handled "transparently" by ExoSpace especially the DOS function interrupt 0x21. Interrupts not handled transparently by ExoSpace can safely be called as long as they don't pass pointers to buffers or return pointers to data. A brief breakdown of some of the important interrupts follows: Interrupt 0x08 (System Clock): If you install a handler with interrupt 0x21, function 0x25, ExoSpace will automatically call your interrupt handler in protected mode. Interrupt 0x1C handlers are NOT called. Interrupt 0x1C (User Timer Tick): Install a handler for interrupt 8 instead. Interrupt 0x0B-0C (Serial port interrupts): If a handler is installed with interrupt 0x21, function 0x25, ExoSpace will automatically call your interrupt handler. If you are doing high-speed interrupt-driven communications you will need to install bi-modal interrupt functions. Interrupt 0x10 (Video BIOS): Because the majority of video BIOS interrupts do not pass pointers to buffers, the majority of them work just fine with ExoSpace. Of those functions which do pass pointers to buffers, the following are handled transparently by ExoSpace WITHOUT the INT10 package: function 0x10, subfunctions 0x02, 0x09; function 0x13; function 0x1B; function 0x1C, subfunctions 0x01, 0x02. Interrupt 0x16 (Keyboard): These interrupts are handled transparently by ExoSpace, but function 0x01 and 0x11 are handled specially. Because these interrupts are resignaled in real mode and because doing a mode switch takes time, these interrupts are only signaled in real mode every timer tick (1/18th of a second) or after a call to interrupt 0x16, functions 0x00 or 0x10. This optimization helps performance. Interrupt 0x21 (DOS): Most of the documented interrupts are fully supported. Functions that require special notes are: 0x1F (Get DPB) - You must use ExoProtectedPtr() to translate the real mode address returned in DS:BX by this function. 0x26 (Create PSP) - Pass return value from ExoRealPtr(). 0x2A (Get date) - The real mode interrupt is resignaled only once every timer tick to help performance. 0x2C (Get time) - The real mode interrupt is resignaled only once every timer tick to help performance. 0x44 (IOCTL) - subfunction 0x0C, minor codes 0x45 and 0x65; and subfunction 0x0D, minor codes 0x40 - 0x42, 0x60 - 0x62 are not translated. Use ExoRealPtr() to translate any addresses passed to the functions, use ExoRMInterrupt() to call the functions, and use ExoProtectedPtr() to translate any return addresses. 0x48 (Allocate Memory) - Memory is most likely allocated from extended extended memory and the returned value in AX is a protected mode selector. It is recommended that you use Clipper's memory allocation functions. 0x50 to 0x53, 0x55, 0x5C, 0x5D - These functions are not transparently supported. Use ExoRealPtr() to translate any addresses passed to the functions, use ExoRMInterrupt() to call the functions, and use ExoProtectedPtr() to translate any return addresses. Interrupts 0x25 and 0x26 (Absolute Disk Read/Write): These interrupts ARE NOT handled transparently by ExoSpace. To use these interrupts you MUST link in the DOS25 package. When this package is linked in, the interrupts are handled transparently. Interrupt 0x31 (DPMI): When running under a DPMI host, all DPMI calls are passed on to the host. When the program is not running under a DPMI host, ExoSpace provides a handler for some of the most common DPMI calls: 0 - 3, 6 - C, 100 - 102, 200, 201, 204, 205, 300 - 304, 400, 500 - 503, 800, 801, 900 - 902 (all values are in hex). Interrupt 0x33 (Mouse): This interrupt is NOT handled transparently by ExoSpace. To use this interrupt you MUST link in the MOUSE package. When the MOUSE package is linked in, all functions are supported except for functions 0x18, 0x19, 0x1F, 0x20. Interrupt 0x5C (NetBIOS): This interrupt is NOT handled transparently by ExoSpace. To use this interrupt you MUST link in the NET5C package. When the NET5C package is linked in, all functions are supported except for functions 0x71 and 0x72. Interrupt 0x7A (IPX/SPX): This interrupt is NOT handled transparently by ExoSpace. To use this interrupt you MUST link in the IPX package. When the IPX package is linked in, all functions are supported. Interrupt 0x7F (8514/A): This interrupt is NOT handled transparently by ExoSpace. To use this interrupt you MUST link in the 8514 package. When the 8514 package is linked in, all functions are supported. Interrupts 0xF0 - 0xFF: These interrupts are reserved for use by ExoSpace. Packages -------- ExoSpace packages contain additional support for interrupts which pass pointers to buffers. The packages and what they support are listed below. INT10 - This package provides support for the video BIOS interrupts 0x10, 0x11, and 0x12. Function 0x10, subfunctions 0x12 and 0x17 are supported. Function 0x11, subfunctions 0x00, 0x10, 0x20, 0x21 are supported with the warning that the support is not fully tested. Function 0x12, subfunctions 0x00, 0x02, and 0x03 are supported with the warning that the support is not fully tested. Function 0x5F, subfunctions 0x91 and 0x92 are supported. Functions 0xF2 to 0xF5 and 0xF7 (the Microsoft mouse EGA support functions) are supported. DOS25 - This package provides support for the absolute disk read/write interrupts 0x25 and 0x26. Attempts to call these interrupts in an ExoSpace'd application without this package linked in will result in undesirable behavior. MOUSE - This package provides support for the Microsoft mouse interrupt 0x33 functions except for functions 0x18, 0x19, 0x1F, 0x20. Attempts to call many of the mouse functions in an ExoSpace'd application without this package linked in will result in undesirable behavior. NET5C - This package provides support for the NetBIOS interrupt 0x5C functions except for functions 0x71 and 0x72. Attempts to call the NetBIOS functions in an ExoSpace'd application without this package linked in will result in undesirable behavior. IPX - This package provides support for IPX/SPX. Attempts to call the IPX functions in an ExoSpace'd application without this package linked in will result in undesirable behavior. There has been one report of a problem with this package. We are interested in hearing from anyone using this package. 8514 - This package provides support for the IBM 8514 Display Adapter. Attempts to call the 8514 functions in an ExoSpace'd application without this package linked in will result in undesirable behavior. NOVM - This is not a package, but a switch to tell ExoSpace not to link in the ExoSpace VM system which uses the Rational DOS/16M VMM system. This switch is currently necessary to create executables that can be debugged with Rational's protected mode debugger. Otherwise, this switch is not currently recommended because it not only disables code and data swapping, but it also inhibits memory compaction. API Library Functions --------------------- The ExoSpace API library functions are all C callable functions and are documented below as C prototypes. The Rational DOS/16M 286 DOS Extender has a rich set of API functions and we have chosen to emulate a small subset of the most useful functions in our API. This API should satisfy 9 out of 10 developers doing C or Assembler programming. We will probably be providing a real mode "stub" library in the next few weeks for those who wish to try to create a "bimodal" library that works with and without ExoSpace. This "stub" library will contain dummy ExoSpace API functions which will return values expected for non-ExoSpace'd real mode code. You will be able to freely add this library to your own library. Thus, when linking with ExoSpace the ExoSpace API functions will be linked in and when using a real mode linker such as Blinker or RTLink the "stub" functions will be linked in from your library. /* definition needed for ExoRMInterrupt() below */ typedef struct _exoregs { unsigned ds, es; unsigned di, si, bp, sp; /* note: sp is ignored */ unsigned bx, dx, cx, ax; } EXOREGS; int ExoFreeSelector(unsigned int selector); int ExoIsDPMI(void); int ExoIsExoSpace(void); int ExoIsPM(void); int ExoIsVMM(void); void *ExoProtectedPtr(void *rmptr, unsigned int sizebytes); void *ExoRealPtr(void *pmptr); int ExoReside(void *pmptr); int ExoRMInterrupt(int, EXOREGS *inregs, EXOREGS *outregs); void (*ExoSegCSAlias(void *pmptr))(); void *ExoSegDSAlias(void *pmptr()); void *_xalloclow(unsigned int sizebytes); void _xfreelow(void *lowmemory); ExoFreeSelector() Purpose: Cancels a protected mode segment descriptor created by ExoProtectedPtr(), ExoSegCSAlias(), or ExoSegDSAlias(). Syntax: int ExoFreeSelector(unsigned int selector); Returns: Always returns 0. See also: ExoProtectedPtr(), ExoSegCSAlias(), or ExoSegDSAlias() Notes: This function is a primitive function; it corresponds to the DPMI function that cancels a descriptor. This function must be passed only the selector part of a pointer created with ExoProtectedPtr(), ExoSegCSAlias(), or ExoSegDSAlias(). Because selectors are a limited resource in protected mode (there are "usually" around 8000 available for code and data segments), you should free selectors you have created with ExoProtectedPtr(), ExoSegCSAlias(), or ExoSegDSAlias() as soon as you are done with them. We had intended to include the API function ExoSegCancel() which would accept a pointer instead of just the selector portion of a pointer, but it was inadvertently left out. A future revision of the API will include this function. Example: This code creates a protected mode pointer to video color memory, does some unknown operation with it and then frees the protected mode selector. void *rmvideo; void *pmvideo; /* set up pointer to point to real mode address 0xB800:0 which is the address of the color video card */ FP_SEG(rmvideo) = 0xB800; FP_OFF(rmvideo) = 0; /* create protected mode pointer for use in protected mode */ pmvideo = ExoProtectedPtr(rmvideo, 0x8000); ... more code using pmvideo pointer would normally be here ... /* free up selector used by pmpointer now that we are done with it */ ExoFreeSelector(FP_SEG(pmvideo)); ExoIsDPMI() Purpose: Returns true if running in a DPMI host environment. Syntax: int ExoIsDPMI(void); Returns: This function returns nonzero if the program is running in a DPMI host. If the program is not running under DPMI, ExoIsDPMI returns zero. See also: ExoIsExoSpace(), ExoIsPM() Notes: Use this function to determine whether your program is running under a DPMI host, such as Windows 3.x in enhanced mode. Example: This program fragment prints a message telling whether or not the program executing is running under DPMI. if (ExoIsDPMI()) printf("Program is running under DPMI"); else printf("Program is not running under DPMI"); ExoIsExoSpace() Purpose: Returns true if running under ExoSpace. Syntax: int ExoIsExoSpace(void); Returns: This function returns nonzero if an ExoSpace program is running. See also: ExoIsDPMI(), ExoIsPM() Notes: Use this function to determine whether your program is running under ExoSpace. If the program has been linked with the real mode "stub" library, this function will return zero. Example: This program fragment prints a message telling whether or not the program executing is an ExoSpace application. if (ExoIsExoSpace()) printf("ExoSpace program is running"); else printf("ExoSpace program is not running"); ExoIsPM() Purpose: Returns true if the CPU is running in protected mode. Syntax: int ExoIsPM(void); Returns: Nonzero if the CPU is executing in protected mode, zero otherwise. See Also: ExoIsDPMI(), ExoIsExoSpace() Notes: Use this function to determine whether the processor is running in protected mode. Example: This program fragment prints a message telling whether or not the CPU is in protected mode. if (ExoIsPM()) printf("cpu is in protected mode"); else printf("cpu not in protected mode"); int ExoIsVMM(void); Purpose: Returns true if the ExoSpace program is using the ExoSpace VMM system. Syntax: int ExoIsVMM(void); Returns: Nonzero if the program is using the ExoSpace VMM system, zero otherwise. See Also: ExoIsDPMI(), ExoIsExoSpace() Notes: Use this function to determine whether the ExoSpace VMM system is active. It will not be active if the option "EXOSPACE PACKAGE NOVM" was specified during the link or if you are running under a DPMI server such as a Windows or OS/2 DOS box. Example: This program fragment prints a message telling whether or not the program is using the ExoSpace VMM system. if (ExoIsVMM()) printf("program is using the ExoSpace VMM system"); else printf("program is not using the ExoSpace VMM system"); ExoProtectedPtr() Purpose: Creates a protected mode pointer from a real mode pointer. Syntax: void *ExoProtectedPtr(void *rm_ptr, unsigned size); Returns: The protected mode pointer, or NULL if the pointer can't be allocated. A new descriptor is allocated. See also: ExoRealPtr(), ExoFreeSelector() Notes: The protected mode data pointer created by this function points to a segment of size bytes. Since a value of 64KB is too large for an unsigned variable, use 0 to specify 64KB for size. The offset of pm_ptr is always 0. The base physical address of pm_ptr's segment is the absolute address of rm_ptr, which is segment(rm_ptr) * 16 + offset(rm_ptr). Because ExoProtectedPtr() is run with interrupts enabled, realtime applications should not call it from an external interrupt handler. The offset of the pointer returned by ExoProtectedPtr() will ALWAYS be 0. ExoFreeSelector() MUST be called to free the selector allocated with ExoProtectedPtr(). Selectors are a limited resource and repeated calls to ExoProtectedPtr() as well as ExoSegCSAlias() and ExoSegDSAlias() could use up all the available selectors causing the program to crash. Once you are done with a pointer created with ExoProtectedPtr(), free the selector unless you'll be using the pointer thoughout the program. Example: See example for ExoFreeSelector(). ExoRealPtr() Purpose: Creates a real mode pointer from a protected mode pointer. Syntax: void *ExoRealPtr(void *pm_ptr); Returns: The real mode pointer, or NULL if pm_ptr is in extended memory (not accessible in real mode). See also: ExoProtectedPtr() Notes: The pm_ptr argument is the protected mode pointer to be converted. Note that if the real mode pointer is 0:0, the returned value is the same as an error value. Example: See example for _xalloclow(). ExoReside() Purpose: Flags a VMM segment to remain resident in memory. Syntax: int ExoReside(void *pmptr); Returns: -1 on error, 0 or 1 otherwise. See also: ExoSegDSAlias() Notes: When the VMM system is active a code or data segment may be swapped out at any time. When data segments are swapped out they are always written to the swap file, but code segments are never written to the swap file when they are swapped out unless a data segment alias created by ExoSegDSAlias() exists at the time the code segment is swapped out. If you write to a data segment alias of a code segment when ExoSpace's VMM is active, you will need to mark the segment as resident before freeing the data segment alias with ExoFreeSelector(). Example: See example for ExoSegDSAlias(). ExoRMInterrupt() Purpose: Sets the registers, then signals a real mode interrupt. Syntax: int ExoRMInterrupt(int intno, EXOREGS *inregs, EXOREGS *outregs); Returns: The flags register. See also: None Notes: Use ExoRMInterrupt() to signal a real mode interrupt that requires you to set registers. This function allows you to set real mode segment register values from protected mode. When you use regular passdown interrupts, you can't set real mode register values. A call to ExoRMInterrupt() sets the registers to the values you provide in the structure to which inregs points; then it invokes interrupt intno. After the interrupt is processed, ExoRMInterrupt() stores the register values in the structure pointed to by outregs. The structures pointed to by inregs and outregs are both type EXOREGS. Note that you cannot set the SS and SP registers from a EXOREGS structure. Before ExoSpace signals the specified interrupt, it sets the CPU flags to the value they had when you called ExoRMInterrupt() in protected mode. Notice that the value of the flags register after the real mode interrupt call is made is returned by this function. When you use ExoRMInterrupt(), there must be a return from the real mode interrupt to match every call (unless your program is terminating). Also, if reentrance on ExoRMInterrupt() is possible, the interrupt should use less than 256 bytes of stack. You can get around this limit by switching to a different stack in the real mode handler/function. Example: This example signals DOS function 2Ch to get the time of day. As required for that call, the high order byte of the AX register is set to 2Ch. Note that the structure EXOREGS does not contain a field AH, so you must set the high order byte using the shift operator (<<). After the call to ExoRMInterrupt(), this example displays the contents of the outreg registers. The display shows the time in hours, minutes and seconds. Since the hours and seconds are stored in the high order byte, the shift operator is used again. int main() { EXOREGS inreg; EXOREGS outreg; inreg.ax = 0x2C << 8; /* DOS function "get time" - signal int 21h */ ExoRMInterrupt(0x21, &inreg, &outreg); printf("The time is %d:%02d:%02d\n", outreg.cx >> 8, outreg.cx&0xFF, outreg.dx >> 8); } ExoSegCSAlias() Purpose: Creates a code descriptor for the given data segment. Syntax: void (*ExoSegCSAlias(void *pm_ptr))(); Returns: A function pointer to the same linear address as pm_ptr, or NULL if an error occurred. See also: ExoSegDSAlias() Notes: The new descriptor created by ExoSegCSAlias points to the segment base of pm_ptr. This function enables you to execute code in a data segment. To store data in a code segment, use ExoSegDSAlias(). Because ExoSegCSAlias() is run with interrupts enabled, realtime applications should not call it from an external interrupt handler. The offset of the alias will ALWAYS equal the offset of the original pointer. ExoFreeSelector() MUST be called to free the selector allocated with ExoSegCSAlias(). Selectors are a limited resource and repeated calls to ExoSegCSAlias() as well as ExoSegDSAlias() and ExoProtectedPtr() could use up all the available selectors causing the program to crash. Once you are done with a pointer created with ExoSegCSAlias(), free the selector unless you'll be using the pointer thoughout the program. Example: This code fragment calls executable machine code written by the program to a data buffer. char writablecode[100]; void (*funcptr(void)); /* during execution the program would write executable machine code to the 100 byte writablecode buffer */ ... /* create a selector that is a code segment alias to the data segment containing the variable writablecode */ funcptr = ExoSegCSAlias(writablecode); /* set the offset of the function pointer to the offset of the writablecode buffer */ FP_OFF(funcptr) = FP_OFF(writablecode); /* call the code written to the writablecode buffer */ *funcptr(); /* cancel the code segment selector */ ExoFreeSelector(FP_SEG(funcptr)); ExoSegDSAlias() Purpose: Creates a data descriptor for the given code segment. Syntax: void *ExoSegDSAlias(void *pm_ptr()); Returns: A data pointer to the same linear address as pm_ptr, or NULL if an error occurred. See also: ExoSegCSAlias() Notes: This function creates a new data descriptor with the same base as the segment specified by pm_ptr. After the call to ExoSegDSAlias(), the descriptor indicated in pm_ptr still exists and retains its original segment type. The offset of the alias will ALWAYS equal the offset of the original pointer. ExoFreeSelector() MUST be called to free the selector allocated with ExoSegDSAlias(). Selectors are a limited resource and repeated calls to ExoSegDSAlias() as well as ExoSegCSAlias() and ExoProtectedPtr() could use up all the available selectors causing the program to crash. Once you are done with a pointer created with ExoSegDSAlias(), free the selector unless you'll be using the pointer thoughout the program. Example: The example below consists of two modules: one in assembly language and one in C. The int_instruction label in the assembly code marks the location of an instruction signaling Interrupt 0h. The C code overwrites this instruction so that a different interrupt is signaled. The C code calls ExoSegDSAlias() to get an alias data pointer to int_instruction. The example then increments the pointer by one byte to point to the location of "0h" in the instruction. The example overwrites this location with "0x88" and frees the data descriptor with ExoFreeSelector() since it is no longer needed. public _do_int, int_instruction _do_int: ... int_instruction: int 0h ... char *data; extern int_instruction(); /* create data segment alias and write interrupt number 0x88 */ data = ExoSegDSAlias(int_instruction); *(data + 1) = 0x88; /* mark code segment as resident so the VMM system won't swap it out and lose the changes made to it */ ExoReside(int_instruction); /* free the selector created by ExoSegDSAlias() since we don't need it anymore */ ExoFreeSelector(FP_SEG(data)); _xalloclow() Purpose: Allocates low DOS memory for use with real mode interrupts that must be passed buffers within the first 1 MB. Syntax: void *_xalloclow(unsigned int sizebytes); Returns: A protected mode pointer to the allocated memory. See also: ExoRealPtr(), ExoRMInterrupt(), _xfreelow() Notes: This function returns a protected mode pointer to the allocated memory. Use this pointer to write to or read from the allocated buffer. When calling a real mode interrupt you must get a real mode pointer to the allocated buffer with the ExoRealPtr() function and pass that address in the EXOREGS structure of a ExoRMInterrupt() call. You can allocate a 64K buffer by passing a size of 0. You must use _xfreelow() to free memory allocated with this function. Do not use xfree()! It is suggested that you free the allocated low DOS memory as soon as possible to keep down the amount of low DOS memory that may be allocated at the same time not only by your functions but also by other C/ASM code or by other third party libraries. ExoSpace has added the LOWMEM parameter to the CLIPPER environment variable to allow a developer to set aside low DOS memory for allocation with _xalloclow(). The LOWMEM parameter does not have to be specified in order for _xalloclow() to work; if there is no available low DOS memory when _xalloclow() is called, ExoSpace will free up low DOS memory it uses in order to meet the request. At this time because of the current design of ExoSpace's memory allocation scheme we recommend that you ignore the LOWMEM parameter. Future revisions or individual end-user needs may make the LOWMEM parameter more necessary. Example: This example maps a fake root directory of a specified drive under Novell NetWare. The function below accepts a drive number (0=default, 1=A:, ...) and path for fake root; it returns zero on success and an error code if the function failed. int maproot(int drive, char *path) { int returnvalue = 0xFFFF; int flags; char *buffer; char *realptr; EXOREGS inoutregs; /* allocate low memory buffer to store path */ if ((buffer = _xalloclow(strlen(path) + 1)) != NULL) { /* store path in low memory buffer */ strcpy(buffer, path); /* get real mode pointer to low memory buffer */ if ((realptr = ExoRealPtr(buffer)) != NULL) { /* fill in EXOREGS structure */ inoutregs.ax = 0xE905; inoutregs.bx = drive; inoutregs.ds = FP_SEG(realptr); inoutregs.dx = FP_OFF(realptr); /* call the real mode interrupt */ flags = ExoRMInterrupt(0x21, &inoutregs, &inoutregs); /* see if carry flag is set to indicate error */ if (flags & 0x0001) /* return error code in al */ returnvalue = (inoutregs.ax & 0x00FF); else /* return no error code */ returnvalue = 0; } /* free low memory buffer */ _xfreelow(buffer); } } _xfreelow() Purpose: Frees memory allocated by _xalloclow(). Syntax: void _xfreelow(void *lowmemory); Returns: Nothing. See also: _xalloclow() Notes: You must use this function to free memory allocated by _xalloclow(). Do not use xfree() to free memory allocated by _xalloclow()! Example: See _xalloclow() example. Problem Solving --------------- Question: How do I determine if I am running under ExoSpace? Answer: Simply use the ExoIsExoSpace() function. Question: How do I determine if I am running in protected mode? Answer: Simply use the ExoIsPM() function. This function has little use because ExoSpace does not document a way to switch to real mode. Question: How do I determine if Rational's VM system is active? Answer: Simply use the ExoIsVMM() function. If the application was created with the "EXOSPACE PACKAGE NOVM" option, this function will return zero (false). Question: How do I determine if I am running under DPMI? Answer: Simply use the ExoIsDPMI() function. Running under DPMI is more restrictive than running under any other memory manager and although you should construct your software to run under DPMI you could choose to restrict your software to run under non-DPMI memory managers. Question: I really need to write to data in the code segment. How can I do so? Answer: You can write data to the code segment is you create a "DS alias". You can NEVER write to the code segment using the CS selector register, but you can create an alias using the ExoDSAlias() function. This function returns a selector that can be loaded into the DS or ES registers. This selector points to the same memory used as a code segment. Question: I really need to execute code I have created in a data segment. How can I do so? Answer: You can use the ExoCSAlias() to create a code selector that corresponds to a data segment. You can then CALL or JMP to the code selector. Question: I want to address an absolute memory location such as the color video memory at 0xB8000. How do I do so? Answer: When not running under DPMI, many of the most used real mode segments are matched by protected mode selectors that are mapped to the same physical memory locations. When running under DPMI (such as in a Windows DOS box) the only selector that is predefined to match its real mode segment is selector 0x40 which points to the BIOS data area. Under DPMI, no other selector is predefined so trying to use selector 0xB800 to access color video memory will generate a GP fault. Note: Even when not running under DPMI, code which uses selector 0 to access the BIOS data area will generate a GP fault. This is because selector 0 is a special NULL selector that cannot be read from or written to. Under DPMI it is necessary to use the ExoProtectedPtr() function to create a selector which points to a physical memory location such as segment 0xB800. It is recommended that you use ExoProtectedPtr() for maximum portability since it also works when not running under DPMI. Simply pass ExoProtectedPtr() a real mode pointer to a physical memory address in the lower 1 MB and a size. It will return a pointer to that memory address. The size parameter determines the size of the protected mode segment that the selector points to. Attempting to access an offset greater than the size of the segment will generate a GP fault; again, a very good protection against writing to memory outside of a designated range. Question: How do I call an interrupt in real mode? Answer: If the interrupt you are calling is a documented DOS interrupt, it will be handled transparently by ExoSpace. In addition, some interrupts which are not normally handled transparently by ExoSpace will be handled if the application is linked with one of the ExoSpace packages. For example, ExoSpace does not handle the mouse interrupt transparently unless you link in the mouse package with the command "EXOSPACE PACKAGE MOUSE". Interrupts which do not pass pointers to data, but only pass values in registers can be called from protected mode with no modifications to your code. This works because ExoSpace will trap the interrupt, check to see if it is a special case which ExoSpace handles, and if not, it will reissue the interrupt in real mode. The only problems occur when the real mode interrupt expects a pointer to a data buffer to be in a register pair such as ES:DI. When the interrupt is reissued in real mode the pointer is no longer valid because it was a protected mode pointer and it will instead point to garbage in the first 1 MB. To issue an interrupt you can use C's int86..() functions or ExoSpace's ExoRMInterrupt() function. ExoSpace's ExoRMInterrupt() function should be slightly faster in most circumstances. Question: How do I pass a pointer to data to a real mode interrupt? Answer: You can use the ExoRealPtr() function to convert a protected mode pointer to a real mode pointer. The return value from ExoRealPtr() should be loaded into the REGS structure that ExoRMInterrupt() or int86..() uses. The protected mode pointer must point to a memory location within the first 1 MB. Question: How do I allocate a block of memory so that I know it is in the first 1 MB and therefore addressable in real mode? Answer: Use the _xalloclow() function to allocate memory in the lower 1 MB. This is only needed for buffers that are passed to real mode interrupt handlers which can only address the first 1 MB. Use _xfreelow() to free the allocated memory. It is recommended that your code allocate and then free low DOS memory after every use to keep the amount of allocated low DOS memory in use at any one time down to a minimum. Currently, _xalloclow() will steal memory from Clipper if it must to meet the request. Allocating a great deal of low DOS memory could adversely affect performance. Question: How can I have the user reserve an amount of low DOS memory for allocation by my program/library? Answer: ExoSpace has added the LOWMEM parameter to the CLIPPER environment variable. This parameter specifies the amount of low DOS memory to reserve for allocation with _xalloclow(). If your program/library allocates up to 25K at one time with _xalloclow(), the user could specify a LOWMEM parameter of 25K. Actually, it is recommended at this time that you ignore the LOWMEM parameter due to the design of ExoSpace's low DOS memory allocation scheme, but this may change in the future or a particular end-users needs may require the LOWMEM parameter. Question: How do I install an interrupt handler? Answer: In most cases you can install a software interrupt handler using the DOS function 0x25. This requires no changes to your source code. Conflicts can occur when installing hardware interrupt handlers. ExoSpace handles hardware interrupts in various ways and it is beyond the scope of this document to discuss hardware interrupts. If you are installing a hardware interrupt, see if it works. If it doesn't, contact us. If it does work be aware that hardware interrupts that occur frequently such as serial port interrupts may degrade system performance and even come too fast to be handled properly without special programming. If you are dealing with serial hardware interrupts or other fast hardware interrupts you are probably a candidate for the full version of DOS/16M. It contains more documentation and example code for handling fast hardware interrupts. Question: How can I allocate and use more than 64K at one time? Answer: Currently, there is no documented ExoSpace API function to do this. The DOS memory allocation function can allocate more than 64K at one time, but only the first 64K can be addressed by the returned selector. A special kind of selector arithmetic must be done to address data beyond the first 64K. A future API revision may include functions to use more than 64K of data, but for now it is best to allocate large amounts of memory in 64K chunks. Sample Code ----------- We have provided some sample code with this draft API. Additional code samples will be forthcoming in the next two weeks.