Porting to DMC++

32-bit protected-mode DOS programs with a linear address space of 4
gigabytes (4GB) can be created by compiling with either of two memory
models.
Programs compiled with these memory models can run on any IBM
PS/2 or PC/AT computers or compatibles with an 80386, 80486,
Pentium or later x86 CPU and can take advantage of all the features
of the 32 bit 80386 architecture.

The X32VM DOS Extender, which is a free download
from dosextender.com.
To use it, compile with the DOSX memory
model (-mx) and link with the OPTLINK linker.

The Phar Lap 386|DOS Extender, which is available
separately from Phar Lap. To use it, compile with the Phar
Lap 386 memory model (-mp) and link with the Phar Lap
linker (OPTLINK does not support linking Phar
Lap programs). Contact Phar Lap for licensing
information.

Hardware requirements

Programs compiled with the DOSX 386 or Phar Lap 386 memory
models run only on computers with an 80386, 80486, or Pentium
CPU.

Hardware-sensitive items that can affect DOSX programs include the
enabling hardware for the A20 line and 8259 interrupt controllers. If
these are compatible with either their PC/AT or IBM PS/2
counterparts, they should not affect DOSX applications.

If the A20 enabling hardware is not PC/AT compatible or IBM PS/2
compatible, an XMS extended memory manager (XMM) compatible
with that computer may be required. (An extended memory manager
is a program installed at boot time by inserting the appropriate line
in the config.sys file.) If this is the case, the program will exit
with the error message:

Cannot enable the A20 line, XMS memory manager required

The vast majority of 80386 equipped personal computers can run
DOSX programs without the need for extended memory manager
software.

Extended memory is not required to run DOSX 386 programs. If
extended memory is available, however, it will be added to the
heap/stack space. This means that DOSX 386 programs will run on
80386 computers that have no extended memory if there is sufficient
conventional memory available.

The DOSX 386 memory model will not operate on computers
equipped with a 16 bit processor 80286 or 8088.
If the startup code detects any 16 bit
processor older than a 80386, it will exit with the error
message:

Fatal error, 80386 in real mode is required

Software requirements

DOSX programs are compatible with IBM's vdisk.sys, Microsoft's
ramdrive.sys, smartdrv.sys, himem.sys and other drivers
that may or may not use extended memory. The DOSX extender is
known to be compatible with these drivers:

Qualitas 386^MAX Version 5.0 and later (not compatible
with earlier versions)

IBM vdisk.sys

Quarterdeck's QEMM memory manager

Note:
Some extended memory managers, such as
himem.sys, are not compatible with vdisk.sys.
Installing both of those programs simultaneously
results in no extended memory being available for
other application programs, regardless of the
amount of extended memory that is not actually
being used.

When it starts up, a DOSX program allocates all the extended and
conventional memory it needs. If there is an XMM available, the
program uses it to allocate the extended memory and enable the A20
line. If there is no XMM, the program allocates the memory and
enables the A20 line itself.

DOSX programs contain code to protect driver buffers in extended
memory so that invalid pointers cannot write over them. You can
access memory allocated to an extended memory RAM disk only by
calling the RAM disk code.

The DOSX memory model is not compatible with drivers that leave
the processor in V86 mode unless the driver is VCPI compatible, like
Qualitas 386^MAX version 5.0 and higher, or DPMI compatible, like
Microsoft Windows 3.0 and higher. If a DOSX program determines
that the processor is in V86 mode and the software that switched it
to that state is neither VCPI nor DPMI compatible, the DOSX
program exits with the message:

Fatal error, 80386 in real mode is required

DOSX memory limitations

DOSX has the following memory limits:

With no memory managers at all: 64MB. This is because
the BIOS function call INT 15 function 0x88 returns the
number of kilobytes of extended memory in register AX,
which can hold a value no bigger than 65,535. Thus, the
maximum amount of memory the BIOS can indicate is
64MB.

With XMS compatible devices like Microsoft's
himem.sys: 64MB. This is due to limitations in the XMS
interface.

With a VCPI host, memory is limited only by the host.

Under DPMI hosts, DOSX allocates only 3/4 of available
memory, to allow the host to run other applications
simultaneously. The DPMI interface specification supports
up to 4GB, so DOSX can allocate up to 3GB.

Note:
If less than 1Mb of extended memory is available,
Windows 3.0 in 386 enhanced mode does not
reliably run protected mode programs except from
the DOS prompt. In this instance you need to add
memory or upgrade to Windows 3.1.

The 80386 detects errors and exits with a diagnostic rather
than crashing.

DOSX programs have a simple, built-in interface to DOS. All I/ O,
such as printf(), write(), and getchar() calls, use the built-in
DOS interface code transparently.

To the user, a DOSX program looks like a typical executable. It
requires no special loaders or device drivers because everything it
needs to run is contained within it. A DOSX program is only about
10KB larger than if it had used the Large memory model.

When it runs, a DOSX program places the processor in protected
mode, allocates all available extended and conventional memory,
and executes all instructions in protected mode. It returns to real
mode only when it needs to interface with real mode code such as
DOS, or hardware interrupt handlers.

Managing memory

When a DOSX program starts up, it checks for extended memory
management software in this order:

DPMI hosts, such as Windows 3.0 and higher

VCPI hosts, such as Qualitas 386^MAX version 5.0 and
higher

XMS compatible devices, such as himem.sys

The BIOS function call INT 15 function 0x88

A DOSX program uses the first device it finds to allocate all the
extended memory and enable the A20 line. If it cannot find any of
the above devices, it looks for any driver that uses extended
memory, such as ramdrive.sys, smartdrv.sys, or
vdisk.sys. It allocates all unused extended memory.

If you are using extended memory software that isn't a DPMI host,
the program also allocates all the unused memory and combines it
with the extended memory in an area called the X memory space.
Your program treats this block of memory as a large, single,
continuous block. The extended memory that drivers have allocated
is not part of this block, so errant pointers cannot overwrite it. The
first megabyte of memory, including the video display buffer, is also
not part of this block, and you can access it as you would in any
other memory model.

Your program loads its code and static data into the bottom of the X
memory space. The heap grows up from the top of static data
towards the stack, and stack grows down from the top of the X
memory space towards the heap.

Figure 20-1 X memory space
Stack
I
V
Unused
^
I
Heap
Static data
Code

The stack can grow until it reaches the first 4KB boundary above
the top of the heap. If the stack tries to grow beyond that, the
program is aborted.

The default minimum size for a DOSX program's stack is 4096 bytes.
Change the minimum stack size with the =nnnn command
option, or with the _stack global variable:

unsigned int _stack = nnnn;

This number is rounded up to the nearest 4KB boundary. DOSX
uses this number to prevent the heap from growing into the
stack. Memory allocation functions such as malloc() and
calloc() fail when the heap has grown to the largest size possible
given the current minimum stack size.

Running on DPMI hosts in 386 enhanced mode

To handle interrupts such as 0x08 to 0x0F, 0x70 to
0x77, 0x1B, 0x1C, 0x23, or 0x24, or use the msm_signal function,
all code, data, and stack that the interrupt handler might access must
be locked. This prevents the interrupt handler from being swapped
out to disk. You can lock the necessary memory with the function
__x386_memlock. Use __x386_memunlock to unlock memory.
These functions can be called even if the program is not running
under DPMI, in which case they will always return success. You also
need to lock the code, data, and stack segments when using the
functions cerror_open and cerror_close.

When you link a program for the DOSX memory model, OPTLINK
adds code to your program to handle all interrupts except hardware
interrupts. When the processor detects a fault, it issues an interrupt.
The DOSX code then prints out diagnostic information and
terminates the program. For example, if you use a null pointer, the
output might be:

When you use a 32-bit memory model, there are no far calls or far
pointers. Pointers contain only a 32-bit offset. Arrays are limited by
the amount of available memory, up to 4GB. The amount of data
that functions like read() and write() can transfer is limited by
the amount of available disk space, up to 4GB.

Differences affecting library functions

These standard library functions behave differently in 32-bit memory
models. For more information see the function's description in the
Run-Time Library Reference.

Special functions for the DOSX memory model

There are numerous ways to do it, either by using
library functions or by creating a special 32-bit
protected mode pointer:

The easiest way to write code to directly access video ram
for DOS, DESQView, DOSX and Win32 is to use the
disp package.
Of course, this method has the advantage of being maximally portable.

Use a selector with an appropriate base address.

The sample code below allocates a new selector with a base address
that points to 0x0B8000, which is in the video buffer for a color
monitor. This calls a function that returns a far pointer in
DX: EAX. If the selector is allocated successfully, the offset in
EAX will be zero. If not, EAX will hold the requested address and DX
will hold a base address of absolute zero (equivalent to
_x386_zero_base_selector in the next example). Note that all
the code fragments in this section are part of the same program. The
following example defines the variables used in the other examples
as well:

The next block of sample code writes to the screen using near
pointers based on the default selector normally found in DS. Your
code must first call the function _x386_get_abs_address to
determine the base address of DS (DGROUP). Since this value is
always greater than 1MB, you need to use a negative offset to access
the video buffer. The segment limits DOSX sets on DS allow the use
of negative offsets, as illustrated by the following:

Use the _disp_open library function to identify the
video card and create a selector that points to the video
buffer.
This code fragment calls the _disp_open run-time library
function (from the Display package) to identify the video card and
create a selector that points to the video buffer. When you use this
method to access video memory, you should inspect the value in
_disp_open to make sure it is non-zero. If _disp_open fails to
identify the video card and uses the BIOS to access the video buffer,
_disp_base will equal zero.

ECX must not be greater than 63 (two-byte) words; therefore there is
a maximum of 126 bytes that can be transferred. Making ECX zero
will make the call slightly faster and preserve real mode stack space.
The real mode procedure receives control with a stack of about 300
bytes in size. The dword return address is immediately placed on the
stack and any copied parameters are placed above that. All general
registers are preserved when a protected mode function calls a real
mode function, as well as when the real mode function returns
control to the protected mode function. The real mode function
receives ds = cs; all other segment registers are undefined. Upon
return to protected mode, all general registers are as they were left
by the real mode code. Segment registers are as they were prior to
the int 21h call. The stack will be unchanged even if the real mode
function changed the values of the parameters on the real mode
stack; the real mode function cannot return values on the stack.

Note that it is difficult to get the real mode segment value, since
there are no segment fixups in the protected mode code. To get the
segment value of the real mode code:

mov ax,2509h ;get system segments and selectors
int 21h

All general registers will be destroyed and filled with various real
and protected mode segments and selectors. The value of interest is
the real mode code segment returned in BX. The following program
demonstrates the use of a real mode procedure:

While function call 250eh is similar to Pharlap's function 250eh, it is
not identical; function 2509h as used above is totally different from
Pharlap's version, and Pharlap function 2510h which allows the
caller to specify all registers is not supported in DOSX. Other
relevant Pharlap-like function calls which DOSX supports are as
follows:

The real mode data buffer (function 250d) is shared with the DOS
extender and is overwritten during disk i/ o or screen i/ o, so it should
only be used for temporary storage. The normal size of this buffer is
16K bytes, but it may be smaller if there is insufficient conventional
memory. Keep in mind that real mode libraries will have some
difficulty accessing protected mode data.

The example program which shows how to access real mode
memory from protected mode. In this example, 45K bytes of static
data are allocated in the real mode code segment; this is roughly the
maximum that can be used.

include macros.asm
include x386mac.asm
comment&
This program demonstrates how to allocate static
real mode memory, and access it both from real
mode code and 32-bit protected mode code. The
function main places the number 12345678h in the
real mode array "real_data". main then calls the
real mode function "real_proc", which retrieves
that number and returns to protected mode with
that number in EAX. main then causes a GP fault
so that the registers can be examined.
&
begcode_16 ; define start of real mode code
; segment
real_data db 45000 dup (0) ; real mode data
real_proc proc far ; procedure to call from
; protected mode
assume DS:__X386_CODESEG_16 ; name of real mode code segment
mov EAX, dword ptr DS:real_data[10000] ; mov the dummy data into EAX
retf
real_proc endp
endcode_16 ; end of real mode code segment
begdata ; protected mode data segment
extrn __x386_zero_base_selector:word
; data selector enddata
begcode ; protected mode code segment
public _main
_main proc near
mov AX,2509h ; get system segments and
; selectors
int 21h ; get real mode segment in BX
comment&
There are no fixups done on 32-bit code, so you
cannot use segment names as immediate values;
you must use function 2509h. This function
destroys all registers, filling each 32-bit
register with two segments or two selectors.
&
push EBX ; save real mode segment which is in BX
mov ES,__x386_zero_base_selector
; note that the above selector is not hardwired,
; but is available through the public variable
; as shown above. It has a base of 0, 4GB limit
movzx EBX,BX ; zero upper word
shl EBX,4 ; convert segment to absolute
; address
assume ES: nothing
mov dword ptr ES: real_data[EBX+10000],12345678h
; above instruction puts dummy data in
; real_data[ 10000], the selector in ES
; points to zero. EBX gives the offset of the
; start of the real mode segment. Ensure that ES
; does not have any "assumes" on it
pop EBX ; restore real mode segment in BX
rol EBX,16
mov BX, offset real_proc ; EBX now = cs:ip of
; real_proc
xor ECX,ECX ; copy zero parameters on stack
mov AX, 250eh
int 21h ; call real_proc
; make a general protection fault to examine
; registers
push CS
pop SS
; illegal value in ss will terminate program and
; dump registers to screen.
;eax should have the value 12345678h
_main endp
endcode
end

To access any part of the first 1Mb of memory in a DOSX program,
you need to perform one of these operations:

Use far pointers

Use a "segment wrap" algorithm to access the firs 1Mb
with near pointers

Both these methods are illustrated in the sample code that follows.
Which method you choose depends on the situation: for one-time
access, far pointers would typically be easiest; for multiple accesses,
near pointers might be best.

include macros.asm
begdata extrn __x386_zero_base_selector:word
; data selector for using far pointers
extrn __x386_get_abs_address:dword
; function pointer for using near pointers
enddata
begcode
public _main
_main proc near
comment&
The far pointer method: A selector is stored in
a global variable as shown in the extrn
definition above. This selector has a base
address of zero and a 4 Gb address limit. It is
primarily useful for addressing the first 1Mb
since everything above the first 1Mb is remapped
with the paging mechanism and thus is not
readily usable. This selector can be used to
access the video buffer as follows:
&
; place an X in the upper left corner
; of a color monitor
mov ES,__x386_zero_base_selector
mov byte ptr ES:[ 0b8000h], 'X'
comment&
The segment wrap method: This method is a bit
more complex initially, but the use of near
pointers is a great advantage in some cases. It
relies on the fact that the 80386 "wraps" around
the 4 Gb address just like an 8088 wraps around
the 1 mbyte address. The segment limits have been
set up such that the default DS selector cannot access
addresses low enough to corrupt the code; however,
there is no limit on upper
addresses other than generating page faults if you
try to read or write to empty space. To make
the segment wrap work, you first have to find the base
address of DGROUP. This varies
depending on whether the system is DPMI or non DPMI.
The example below determines this at run
time so it will work in either situation.
&
push DS
push 0 ; put far pointer to start of DGROUP
; on stack
call dword ptr __x386_get_abs_address
; returns address in EAX
add ESP, 8 ; pop far pointer from stack
; then subtract address in EAX from
; address in DS to get to zero
neg EAX
; now DS:[EAX] points to absolute zero. Add the
; value required to EAX to access an address in
; the first 1Mb.
; To place X in upper right corner of a
; color monitor:
mov byte ptr DS:[0b809Eh + EAX],'X'
ret
_main endp
endcode
end

You can use a protected mode pointer to access video memory (or
other memory in the first 1Mb not assigned to your program).
Memory above the 1Mb boundary not assigned to your program is
protected, except under DMIP where protection is less strict. All
linear addresses above the 1Mb boundary point to memory that is
owned by the application. Extended memory that is not owned by
the application is simply not mapped, and does not exist from the
application's point of view.

Here is some code to manipulate pointers.
Compile and run for 16 bit large model and 32 bit DOSX model as well.
It comes from code debugged and tested for years,
but I had to do some cut and paste to make this.

While DOSX programs will run under Win32, they still suffer from inherent
problems from being DOS programs. For example, they cannot deal with long
filenames. Long command lines will not work. Some versions of Win32 have
poor support for 32 bit DOS. Win32 does not do a good job of transitioning
environment variable settings when crossing Win32/DOS boundaries. The time
of day DOS functions are less accurate than the Win32 versions, which can
cause problems when comparing file times.

The best solution is to create a dual mode DOSX/Win32 program.
A dual mode program is simply two entirely distinct programs
bundled together into one EXE file. When running under DOS, the
DOSX program is run, because the DOS program loader does not
recognize a Win32 program. When running under Win32, the Win32
loader will recognize and run the Win32 program.

Thus, a dual mode program delivers the best of both worlds -
compatibility with DOS, and no compromise when running under
Win32.

A dual mode program is built by first compiling the program, for
example hello.c for DOSX. Then, build a .def file that names
the DOSX version as the stub executable. Build the Win32 program
and link with the .def file:

DOSX does emulate some Phar Lap function calls. However, in some
cases the emulation is not exact, and is only compatible in the
context in which the DOSX library uses the function
calls. There are also many Phar Lap functions that are not
implemented. If you use the library functions, most compatibility
issues should not be a concern. Assembly language programmers
might have problems with function calls like 0x 2516 (not
implemented in DOSX) or 0x2509 (implemented but incompatible
with the corresponding Phar Lap function).

Functions that are not implemented will return EAX = 0xA5A5A5A5,
with the carry flag set; this is the Phar Lap convention for handling
unimplemented functions.

Add 'class=CODE' in the segment declaration. DOSX sets the code
segment to execute only, and does not set the execution privilege
bits for code segments, so it is necessary to be accurate about
which segments are code and which are data.
Not doing this will cause the resulting program to crash.