(C) Copyright 1992, Qualitas, Inc. All Rights Reserved Qualitas C/C++ Libraries for DPMI ** Contents of this file ** 1. Introduction 2. Requirements 3. Limitations 4. Building the Example Programs 5. Building the DPMI libraries 6. Using the C library 7. Using the C++ class library ********************************************************************** 1. Introduction The Qualitas C/C++ Libraries for DPMI provide the C/C++ programmer with easy access to DPMI. The purpose of the libraries is to allow developers to gain familiarity with DPMI concepts, to aid in understanding of how DPMI can be used, and to serve as a platform for experimentation with DPMI hosts via the creation of small model C/C++ applications. The directories are structured as follows: top | +------------------+--------+---------+------------------+ | | | | BC2 BC3 MSC6 MSC7 | | | | +-------+------+ +-------+------+ +-------+------+ +-------+------+ | | | | | | | | | | | | | | | | LIB | | | LIB | | | LIB | | | LIB | | | LIBSRC | | LIBSRC | | LIBSRC | | LIBSRC | | INCLUDE | INCLUDE | INCLUDE | INCLUDE | EXAMPLES EXAMPLES EXAMPLES EXAMPLES For convenience of installation, the distribution provides a copy of the source code for the libraries and examples in each compiler directory. The primary differences between compiler directories are (1) the makefiles differ, and (2) some of the source code distributed with the compilers has been modified for operation in conjunction with the DPMI libraries, and those modules are included in their respective directories. \ this README file .\MSC7LIB Microsoft C/C++ 7.0 support .\MSC6LIB Microsoft C 6.0 support .\BC3LIB Borland C/C++ 3.0 support .\BC2LIB Borland C/C++ 2.0 support .\xxx\LIBSRC Source code for the DPMI libraries .\xxx\INCLUDE Header files required for source and examples .\xxx\EXAMPLES Source code for the example programs We have include a batch file to simplify installation. Use the following syntax to install the Qualitas DPMI Library: INSTALL path library [source drive] where "path" points to the directory into which the DPMI library will be installed and "library" is the specific version of the DPMI library you wish installed. Source drive is the floppy drive echo from which you are installing (default = A) Select the library from the following: BC2 Borland C++ version 2.x comaptible library BC3 Borland C++ version 3.x comaptible library MSC6 Microsoft C verson 6.x comaptible library MSC7 Microsoft C verson 7.x comaptible library Example: INSTALL C:\TOOLS MSC6 B would install the Microsoft C 6 compatible DPMI library from B: to C:\TOOLS. If you prefer to install the library manually, use the following commands. This example would install the Borland C++ version 3.0 library: C> md DPMILIBS C> cd DPMILIBS C:\DPMILIBS> cd a:\BC3LIB C:\DPMILIBS> xcopy a: /s ********************************************************************** 2. Requirements The requirements depend upon which C compiler you are using: Compiler Processor Assembler Linker Librarian -------- --------- --------- ------ --------- Microsoft v7 80386 MASM 6.0 LINK 5.30 LIB 3.20 Microsoft v6 80286 MASM 6.0+5.1 LINK 5.10 LIB 3.18 Borland v3 80286 TASM 3.0 TLINK 5.0 TLIB 3.02 Borland v2 80286 TASM 2.5 TLINK 4.0 TLIB 3.02 NOTE: The librarian and assembler are only required if you wish to rebuild the libraries. They are not required to build the examples or other applications. Each compiler requires the small model libraries. The Microsoft compilers require the include files for the startup source to rebuild the DPMI libraries. ********************************************************************** 3. Limitations * Only small model is supported. It is possible, however, to use far pointers that are dynamically created, for example, by using the MK_FP macro. Statically initialized far pointers will work only if they refer to the default code segment or the default data segment. * Floating point is not supported. The run-time requirements create complications which are beyond the scope of this implementation. * Errors may result from use of run time library functions that require running clean-up routines at exit time, if such clean-up routines utilize real mode far pointers created prior to entering protected mode. ********************************************************************** 4. Building the Example Programs The .\EXAMPLES directory contains the following programs: * HELLO.C A simple C program that illustrates how to enter protected mode, and how to call the DPMI C library. * QSORT.C A program that sorts stdin to stdout. It uses DPMI to allocate buffers, which enables it to sort very large files in memory. * XMEM.CPP A C++ program that illustrates the use of the classes in the DPMI class libraries that are related to memory usage. * XINTR.CPP A C++ program that illustrates the use of the classes in the DPMI class libraries that are related to interrupt handling. * XREAL.CPP A C++ program that illustrates the use of the classes in the DPMI class libraries that are related to calling real mode functions and real mode call-backs. * XCEPT.CPP A C++ program that illustrates the use of the classes in the DPMI class libraries that are related to exception handling. More detail about using the libraries and classes is found below. ** To build the examples with Microsoft C 7.0 - Your current directory must be the MSC7\EXAMPLES directory. - CL and LINK for MS C 7.0 must be on your path. - Your INCLUDE and LIB environment variables must be set for a normal MS C 7.0 build. - Issue the command NMAKE. ** To build the examples with Microsoft C 6.0 NOTE: the C++ examples are not built for MS C 6.0 - Your current directory must be the MSC6\EXAMPLES directory. - CL and LINK for MS C 6.0 must be on your path. - Your INCLUDE and LIB environment variables must be set for a normal MS C 6.0 build. - Issue the command NMAKE. ** To build the examples with Borland C++ 3.0 - Your current directory must be the BC3\EXAMPLES directory. - BCC and TLINK for BC++ 3.0 must be on your path. - The BC3LIB variable in the MAKEFILE must be set to the directory where your Borland C++ libraries reside. Edit MAKEFILE and locate the BC3LIB line. Change the right hand side of the statement as needed for your environment. - Issue the command MAKE. ** To build the examples with Borland C++ 2.0 - Your current directory must be the EXAMPLES directory. - BCC and TLINK for BC++ 2.0 must be on your path. - The BC2LIB variable in the MAKEFILE must be set to the directory where your Borland C++ libraries reside. Edit MAKEFILE and locate the BC2LIB line. Change the right hand side of the statement as needed for your environment. - Issue the command MAKE. ********************************************************************** 5. Building the DPMI libraries It is not necessary to rebuild the libraries in order to build the examples or to build other applications. However, in the event you wish to modify the library source code, the following instructions are provided for rebuilding the libraries: ** To build the libraries with Microsoft C 7.0 - Your current directory must be the MSC7\LIBSRC directory. - CL, MASM (v6.0) and LINK for MS C 7.0 must be on your path. - Your INCLUDE and LIB environment variables must be set for a normal MS C 7.0 build. - The C7DIR variable in the makefile must be set to the directory where your Microsoft C++ distribution resides. Edit MAKEFILE and locate the C7DIR= line Change the right hand side of the statement as needed for your environment. - Issue the command NMAKE. ** To build the libraries with Microsoft C 6.0 NOTE: the C++ libraries are not built for MS C 6.0 - Your current directory must be the MSC6\LIBSRC directory. - CL and LINK for MS C 6.0 must be on your path. - Your INCLUDE and LIB environment variables must be set for a normal MS C 6.0 build. - The C6DIR variable in the MAKEFILE must be set to the directory where your Microsoft C distribution resides. Edit MAKEFILE and locate the C6DIR= line. Change the right hand side of the statement as needed for your environment. - The ASM51 variable in the MAKEFILE must be set for the 5.1 version of MASM in order to assemble the MS C 6.0 startup code. - Issue the command NMAKE. ** To build the libraries with Borland C++ 3.0 - Your current directory must be the BC3\LIBSRC directory. - BCC, TASM, and TLINK for BC++ 3.0 must be on your path. - Issue the command MAKE. ** To build the libraries with Borland C++ 2.0 - Your current directory must be the LIBSRC directory. - BCC, TASM, and TLINK for BC++ 2.0 must be on your path. - Issue the command MAKE. ********************************************************************** 6. Using the C Library There is roughly a one-to-one mapping between the set of DPMI version 0.9 functions and the entry points of the DPMI C library. A copy of the DPMI specification may be a useful reference. A set of three higher level calls is provided for simplified memory management. DPMImalloc(), DPMIresize(), and DPMIfree() provide a means to allocate and address blocks of memory allocated from DPMI. These calls are implemented using lower level calls of the library. Refer to DPMIMALL.C in the LIBSRC directory to see how. Definitions for the C library are found in INCLUDE\DPMI.H. This file contains structure definitions, function prototypes, and descriptions of each call. Two example programs are provided in the EXAMPLES directory. HELLO.C shows basic usage: how to enter protected mode and call a library routine. QSORT.C is a useful utillity for sorting large files, which is built using the DPMImalloc family of memory management routines. ********************************************************************** 7. Using the C++ Class Library The class library provides a set of C++ classes that abstract several important DPMI objects. The header files provide descriptions and brief examples on using the classes. Definitions for the DPMI classes are contained in these header files, found in the INCLUDE directory: DPMIHOST.H DPMI host class SEGMENT.H All memory related classes REALPROC.H Real procedure class REALINT.H Real interrupt class CALLBACK.H Real mode call-back class DPMIINT.H Interrupt handler classes EXCEPTIO.H Processor exception handler class General note: Many of the constructors make calls to DPMI. Since the constructors for statically allocated class instances are called prior to main(), declaring instances from the DPMI classes statically may result in attempting to call DPMI prior to entering protected mode, which probably will cause a system crash. Therefore, DPMI objects should be created dynamically, either as automatics (on a function's local stack frame) or with the new operator. It is certainly allowable to statically allocate pointers to DPMI objects, and initialize them after entering protected mode in order to gain global addressability. *-------------------------------------------------------------------- Header File: DPMIHOST.H See examples XMEM, XREAL, XCEPT, XINTR Classes Members ------- ------- DPMIhost DPMIhost Constructor; checks presence of a DPMI host, and gathers needed host information. getStatus Used to verify detection of DPMI host enterProtectedMode Makes the initial switch into protected mode. getProcessor Returns processor code getVersion Returns major and minor version. getSelectorDelta Returns delta between consecutive selectors. When you declare a DPMIhost object in your program, the constructor tests for the presence of a DPMI host on your system. If found, the constructor collects information from the host needed for the switch into protected mode. You can determine the success or failure of the constructor by calling the getStatus member, which returns DPMIok if the constructor found a DPMI host and was successfully initialized. The members getProcessor, getVersion, and getSelectorDelta simply return the appropriate host parameters. You use the member enterProtectedMode for the initial switch into protected mode, and after that it should not be called. It first allocates a block of DOS memory to satisfy the host's private data area requirements. It then calls the mode switch entry point obtained by the constructor. When this returns, the processor is running in protected mode. Finally, this member relocates the executable image for protected mode operation (see Implementation Notes for details). The enterProtectedMode member returns TRUE if the processor is successfully switched to protected mode. *-------------------------------------------------------------------- Header File: SEGMENT.H See example XMEM Classes Members ------- ------- Block Block Constructor; allocates raw block of memory from DPMI. ~Block Destructor; release block to DPMI setSize Resize function reallocates block. blockHandle Returns block handle. blockSize Returns block size. blockBase Returns base linear address. AbstractSegment segmentSize Returns size of segment. segmentBase Returns base of segment. resize Resizes segment. move Changes segment base. operator+ Adds a segment property. operator- Removes a segment property. queryProp Tests for segment property. Segment : AbstractSegment Segment(void) Generic constructor. Segment(selector_t) Specific descriptor. Segment(AbstractSegment&) Alias to existing segment. ~Segment(void); Releases descriptor to DPMI. CommonRealSegment : AbstractSegment CommonRealSegment Constructor DOSMemory : AbstractSegment DOSMemory Constructor ~DOSMemory Destructor HugeSegment : AbstractSegment HugeSegment Constructor ~HugeSegment Destructor MemorySegment : AbstractSegment, Block MemorySegment(uShort) Constructor MemorySegment(uShort, Constructor for specific descriptor selector_t) ~MemorySegment Destructor-frees block and descriptor HugeMemorySegment : HugeSegment, Block The various memory classes provide structured access to DPMI's memory management facilities. Note that of the classes defined, DOSMemory, MemorySegment, and HugeMemorySegment are unique in they that combine allocated memory and descriptors to address that memory. The other classes are either just memory (e.g. Block) or just descriptors (e.g. Segment, HugeSegment). The distinctions between memory and descriptors are important to the conceptualization of the memory classes. *...................................................................... The Block class corresponds to raw memory blocks allocated via DPMI function 501h. Defined operations on Blocks are limited to resize, and free. The class also supports query of the DPMI memory handle and query of the block size. Blocks by themselves are not especially useful, since the memory is not addressable. Its purpose is to serve as a base class for MemorySegment and HugeMemorySegment. *...................................................................... AbstractSegment is an abstract class the corresponds to a descriptor. DPMI provides several means to create descriptors, all with slightly different behavior. Although most members of AbstractSegment are pure, a key member that it does implement is ptrTo, which returns a far pointer that may be used to reference the linear address space that the corresponding descriptor points to. *...................................................................... The SegmentProperty enumeration provides an elegant means to add, remove, and query properties of instances of derived classes of the AbstractSegment class. It is defined as: typedef enum SegmentProperty { present, executable, readable, writable, big, expandDown } SegmentProp_t; The AbstractSegment class defines members operator+(SegmentProp_t) and operator-(SegmentProp_t) that allow properties to be added and removed with a natural syntax. For example, the code Segment mySeg(); mySeg - present; creates a descriptor and makes it not present. *...................................................................... The Segment class corresponds to generic, fully modifiable LDT descriptors. There are three DPMI services that create such descriptors, and thus three constructors for the class. The first corresponds to DPMI function 0000, which allocates a descriptor. The second corresponds to DPMI function 000Dh, which allocates a specific descriptor. The last constructor corresponds to DPMI function 000Ah, which creates an alias (same base and limit) to an existing segment. The default properties are present and writable. New Segments have a base of zero and a size of one. The argument for the resize method is in bytes, and size==0 specifies a size of 64 KB. Specific descriptors must be in the range 04h to 0FFh. *...................................................................... The CommonRealSegment class corresponds to descriptors created by DPMI function 0002h, Convert Paragraph to Selector. The intended use of this function is to provide addressability from protected mode for commonly accessed regions of the low megabyte, such as the BIOS data area (paragraph 40h) or the video display area (B800h etc.). DPMI restricts modification of descriptors created by function 0002h, and therefore the resize, move, operator+(SegmentProp_t) and operator-(SegmentProp_t) members all return FALSE. Other members are inherited from AbstractSegment. *...................................................................... The DOSMemory class corresponds to DPMI function 100h. The constructor uses this function to allocate a block of memory from DOS. DPMI provides a descriptor that allows addressability of the block from protected mode. The argument to the constructor is the desired size in paragraphs of the block. If the allocation fails, the ptrTo function returns zero. The paragraph address of the block is available via the segmentBase(void) member inherited from AbstractSegment. This code fragment: DOSMemory d(0x100); char far *p = (char far *)d.ptrTo(); allocates 0x100 paragraphs of DOS memory and initializes a pointer to it. If the allocation fails, p is a null pointer. The resize member for DOSMemory maps to DPMI function 102h, Resize DOS Memory Block; similarly, the destructor maps to function 101h, Free DOS Memory Block. Because DPMI restricts any other modifications to these blocks, the operator+(SegmentProp_t), operator-(SegmentProp_t), and move members all return FALSE; *...................................................................... A HugeSegment corresponds to a set of consecutive descriptors, which are set up to span a address region of arbitrary size. The difference between the base addresses of consecutive descriptors is 64 KB. The argument to the constructor is the size in bytes of the segment. For example, the code fragment HugeSegment h(0x28000); h.move(0x500000); results in the allocation of three descriptors having sizes of 0x10000, 0x10000, and 0x8000. Calling the move member sets the base addresses of the three descriptors to 0x500000, 0x510000, and 0x520000. Operations on a HugeSegment, such as adding or removing a property, affect all of its component descriptors. Likewise, the destructor frees all the descriptors. HugeSegments cannot be grown if the new desired size requires additional descriptors to span the region. Shrinking a HugeSegment frees descriptors no longer required to span it, and these cannot be reallocated to return the segment to its original size. *...................................................................... A MemorySegment is a generic descriptor mapped to a block of DPMI memory. The size is restricted by the argument to the constructor to be 64 KB or less. Declaring a MemorySegment results in allocating both memory and a descriptor initialized to address that memory. The code fragment MemorySegment m(0x500); short far *p = (short far *)m.ptrTo(); makes p a far pointer to a 0x500 byte block of memory. An zero argument to the constructor requests a 64 KB block. An alternate constructor allows the MemorySegment to be allocated with a specific descriptor. The resize and property members work as expected, but the move member returns FALSE since DPMI provides no means to move memory blocks. *...................................................................... A HugeMemorySegment is to a MemorySegment as a HugeSegment is to a Segment. A HugeMemorySegment is an arbitrarily large block of memory with a set of consecutive descriptors that are set up to span it. The argument to the constructor is the size in bytes. As for MemorySegment, the move member returns FALSE. HugeMemorySegments cannot be grown if the new desired size requires additional descriptors to span the region. Shrinking a HugeMemorySegment frees descriptors no longer required to span it, and these cannot be reallocated to return the segment to its original size. *-------------------------------------------------------------------- Header File: REALPROC.H See example XREAL Classes Members ------- ------- RealProcedure RealProcedure Constructor. operator() Call operator invokes real mode procedure. operator char() char cast extracts char return value from real mode call structure. operator int() int cast extracts int return value from real mode call structure. operator long() long cast extracts long return value from real mode call structure. Applications occasionally require certain routines that cannot run in real mode, and must switch to real mode to execute them. The RealProcedure class corresponds to DPMI mechanisms for invoking (far) functions in real mode. The arguments for the constructor are the address of the real procedure and the number of bytes of arguments the real procedure expects on the stack when it is invoked. The second argument is optional and defaults to zero. The procedure address is near, and is assumed to be in the default code segment. To invoke the RealProcedure, simply call it. The class overrides the call operator, and the member operator() makes the necessary DPMI calls to run the registered routine in real mode. The stack arguments, if any, are copied to the stack on which the real mode routine is invoked. The class ensures that DS==SS when the real mode routine executes. Thus the real mode routine can use any near pointers. An additional feature of the RealProcedure class is the ability to return values. Each class instance contains a dpmiRegs_t structure that is used to pass the register state between modes. Upon return to protected mode, this structure holds the register state in effect when the real routine returned, and return values are contained therein. To access them, use the cast override members: operator char(), operator int() and operator long(). Refer to the example code to see exactly how this is done. *-------------------------------------------------------------------- Header File: REALINT.H See example XREAL Classes Members ------- ------- RealInterrupt RealInterrupt Constructor operator() Call operator invokes interrupt handler in real mode. A RealInterrupt corresponds to the DPMI function that switches to real mode and issues a particular interrupt after installing a register state supplied by the caller. Unlike the RealProcedure class, the interface to RealInterrupt assumes register-based argument passing. The argument to the constructor is the number of the interrupt, from zero to 255. The member operator() (call override operator) is used to invoke the real mode interrupt. This requires a dpmiRegs_t structure as an argument. Upon return from the interrupt, the dpmiRegs_t structure reflects the register state at the time the real mode interrupt handler returned. The caller is required to set up the dpmiRegs_t structure that is passed when the real interrupt is called. This includes setting up the stack (SS:SP fields) and the flags. Setting SS and SP requests the DPMI host to provide a stack. The flags value should be chosen carefully, especially with regard to the Interrupt Enable bit and the Trace bit. An example of using a RealInterrupt is included in the TestCallBack function of the XREAL.CPP example. *-------------------------------------------------------------------- Header File: CALLBACK.H See example XREAL Classes Members ------- ------- CallBack CallBack Constructor; allocates real mode call-back and initialize register structure; saves callback address ~CallBack Destructor; releases call back. getCallBackAddress Returns the call back address. DPMI provides a mechanism for call protected mode routines from real mode. Unfortunately, the mechanism is rather complex. The CallBack class goes a long way in simplifying the task of using real mode call backs. The single arguemnt to the constructor is the address of the call back handler. The call back handler is a routine that called from real mode but runs in protected mode. The class includes dispatching logic that allows you to code the handler as a normal near routine, and allows you to assume that DS==SS. The argument to the handler is a dpmiRegs_t structure that reflects the interrupt state at the time of the call. Any changes the handler makes to this structure are reflected in the register state when the handler returns to real mode. The handler should only be invoked by calling to the far callback address from real mode. The callback address is obtained with the getCallBackAddress member. See function TestCallBack in the XREAL example. *...................................................................... Header File: DPMIINT.H See examples XINTR, XREAL Classes Members ------- ------- InterruptHandler InterruptHandler Constructor; hooks interrupt. ~InterruptHandler Destructor; unhooks interrupt. callPrevious Invokes previous handler. RealInterruptHandler : InterruptHandler RealInterruptHandler Constructor; hooks real interrupt. ~RealInterruptHandler Destructor; unhooks real interrupt. The interrupt handling classes make it very easy to create handlers for hardware and software interrupts, both in real mode and protected mode. The class handles all the necessary interaction with DPMI for saving the previous vector, and for setting the interrupt vector to the user's handler. The class further simplifies creation of handlers by allowing the user to code the handler as a normal function, i.e, the user does not need to declare it as _interrupt, and may assume that DS==SS, which is important in small model programs. This is accomplished by the interrupt dispatching logic built into the class (see Implementation Notes for more information). The arguments to the constructor are the interrupt number and the address of the routine that the user assigns to handle the interrupt. The argument to the user's handler routine is a dpmiRegs_t structure, which reflects the register state at the time the interrupt occurs. Changes to the dpmiRegs_t argument made by the handler are reflected as changes to the register state when the handler returns. For example, a software interrupt handler that returns a value in AX modifies the drAX field of the dpmiRegs_t argument. Likewise, a hardware interrupt handler which preserves the register state must make no changes to the dpmiRegs_t argument. The member callPrevious is useful for calling the handler that was in effect when the user handler was created. The argument to callPrevious is a pointer to the dpmiRegs_t structure, which is necessary in order to present the correct register state to the previous handler. Any changes made to the register state by the previous handler are reflected in the dpmiRegs_t structure upon return. RealInterruptHandlers inherit this member. The destructors restore the previous vector. *-------------------------------------------------------------------- Header file: EXCEPTIO.H See example XCEPT Classes Members ------- ------- ExceptionHandler ExceptionHandler Constructor. Hooks exception. ~ExceptionHandler Destructor. Restores vector callPrevious Invokes previous handler. The ExceptionHandler class makes it easy to create handlers for processor exceptions, such as General Protection violation, Segment Not Present, etc. As with InterruptHandlers, the class provides dispatching such that handlers run with DS==SS, so that all near pointers are valid, and handlers are declared as normal near functions. The arguments to the constructor are the exception number to handle, and the address of the handler. The class takes care of calling DPMI to get the previous vector and set up for the new handler. When the handler is invoked, there are two structures passed to it. The first is a dpmiRegs_t structure which reflects the register state at the time the exception occurred. The second is the DPMI exception frame, which gives the CS:IP, SS:SP, and flags at the time of the exception. The handler may modify fields in the register structure and/or the exception frame structure, and such changes are observed by the host when the handler returns. The member callPrevious is used to invoke the previous handler for the exception. It is passed pointers to the register state and to the exception frame, which it may modify.