DOS 32 (DOSX) Programming Guidelines
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.For more information on the 32-bit protected mode memory models, see Choosing a Memory Model.
- DOS extender compatibility and requirements
- Building and running DOSX programs
- Memory protection mechanisms
- Differences between 32-bit and 16-bit DOS programs
- Accessing video ram in DOSX
- Interfacing to real mode functions
- Accessing real mode memory from protected mode
- Accessing the first megabyte of memory
- Calling a real mode interrupt not implemented by DOSX
- Passing a buffer to a real mode interrupt
- Using protected mode pointers
- Accessing memory mapped I/O devices
- Sample code to manipulate pointers
- Dual mode DOSX/Win32 programs
- DOSX compatibility with Phar Lap
- Using NASM built .obj files with DOSX
DOS Extender Compatibility and Requirements
Use either of these DOS extenders:- 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 requiredThe 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:- Microsoft himem.sys
- Microsoft ramdrive.sys
- Microsoft smartdrv.sys
- Microsoft Windows Version 3.0 and 3.1 in Standard mode, Real mode, and Enhanced 386 mode
- Qualitas 386^MAX Version 5.0 and later (not compatible with earlier versions)
- IBM vdisk.sys
- Quarterdeck's QEMM memory manager
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.
Building and running DOSX programs
Building a DOSX Executable
To compile and link a DOSX program, perform the following steps:- Compile with the DOSX memory model (-mx).
- The predefined macro DOS386 is defined for DOSX compilations; add #ifdef directives to code where necessary. See the source code in ..\src\core for examples of how to do this.
- The compiler can call the linker, as in this
example:
dmc -mx test1.obj test2.obj -otest.exe
To perform the link step manually, link the DOSX startup code in cx.obj. You must link with OPTLINK. For example:optlink \dm\lib\cx+test1+test2,tmp;
- The only library for DOSX is sdx.lib. x386.lib is part of sdx.lib, and is included in source form for rebuilding sdx.lib.
Running a DOSX Program
DOSX allows use of all the features of the 80386. In particular:- Extended memory is used as conventional memory, in a manner that is transparent to the programmer.
- Arrays are limited only by available memory size.
- Integers are 32-bit values, the native data size for 80386 processors.
- Code runs faster.
- All pointers, calls, and jumps are near.
- The 80386 detects errors and exits with a diagnostic rather than crashing.
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
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 CodeThe 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.Memory protection mechanisms
The X memory model uses the 80386 protection mechanisms to check for invalid pointers, detect stack overflow, and protect code from overwrites.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:
INTERRUPT 0DH, GENERAL PROTECTION FAULT possible illegal address error code = 0000 cs = 002B eip = 00000E6A ss = 003B esp = 000B6FF0 ds = 0033 ebp = 000B6FFC es = 0033 eax = 00000000 fs = 0000 ebx = 0000FF00 gs = 0000 ecx = 00000000 eflags = 00013246 edx = 00002B74 esi = 000000D4 edi = 00002E55 absolute start address of DGROUP 10000000 available conventional memory 00025000 to 0009D000 available extended memory 001C0000 to 00200000 X memory space located at 10000000 to 100B7000To execute an interrupt, return to real mode by calling int86() or int86x() before executing the interrupt.
Differences between 32-bit and 16-bit DOS programs
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.bdos() bdosx() _chkstack() getDS() int86() int86x() intdos() intdosx() peek() poke() segread() write()
Standard library functions not supported with DOSX
You cannot use the following functions in programs compiled with the DOSX memory model:bios_disk() dos_abs_disk_read() dos_abs_disk_write() farmalloc() farcalloc() farcoreleft() farfree() farrealloc()
Special functions for the DOSX memory model
There are a number of special functions provided for use with the DOSX memory model. For more information see the function descriptions in the Run-Time Library Reference.Accessing video ram in DOSX
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:
include macros.asm begdata extrn _x386_zero_base_selector: word, _disp_base:word enddata begcode extrn _x386_mk_protected_ptr:dword, _x386_get_abs_address,dword extrn _disp_open:near public _main _main proc near push 0b8000h call _x386_mk_protected_ptr ; returns pointer in DX: EAX mov es,dx ; should reference a video selector mov byte ptr es:[0],'X' ; places an X on the screen
- Use a selector with a base address of absolute zero.
This sample code writes to the screen using a selector with an address of absolute zero. This selector is stored in a global variable and is available for applications to use as required:
mov es,_x386_zero_base_selector ; load the selector mov byte ptr es:[0b8002h],'Y' ; places a Y on the screen
- Use near pointers based on the selector in DS.
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:
push ds ; push far pointer ds:0 push 0 ; returns address in EAX call _x386_get_abs_address ;returns address in EAX neg eax ; DS:EAX now points to zero mov byte ptr ds:[eax+0b8004H],'Z' ; places a Z on the screen
- 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.
call _disp_open ; initialize _disp_base mov es,_disp_base mov byte ptr es:[6],'T' ; places a 'T' on the screen mov ah,4ch int 21h ; terminates sample program _main endp endcode end
- Use _x386_zero_base_ptr that is declared in dos.h as
void *_x386_zero_base_ptr;
You now can build a 32 bit absolute address by adding a 32bit offset to this pointer:char *ScreenAtA000 = (char*)_x386_zero_base_ptr + 0xA0000;
Note that you don't use segment:offset in this case but a linear memory address.(Thanks to Heinz Saathof)
Interfacing to real mode functions
To enclose real mode code, use the begcode_16 and endcode_16 macros found in x386mac. asm. Real mode code must return with a far return value. To call the real mode procedure:mov AX,250Eh mov EBX, ; seg:offset of real mode procedure mov ECX, ; number of words to copy from ; protected stack to real mode stack int 21hECX 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 21hAll 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:
include macros.asm include x386mac.asm begcode_16 ; define start of real mode ; code segment real_proc proc far mov EAX,[ESP+4] ; mov first parameter into EAX retf real_proc endp endcode_16 ;end of real mode code segment begcode public _main _main proc near push 12345678h ; parameter to send to real ; mode code mov AX,2509h ; get system segments and ; selectors int 21h ; get real mode segment in BX rol EBX,16 ; put segment in high word of EBX mov BX, offset real_proc ; EBX now = cs:ip mov ECX,2 ;copy two words or one dword mov AX,250eh int 21h ;call real mode procedure ; make a GP fault to examine registers: push CS pop SS ; illegal value in SS will terminate program ; and dump registers to screen. EAX should ; equal 12345678h _main endp endcode endWhile 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:
Function | Description |
---|---|
2502-2507 | Deals with real and protected mode interrupt vectors |
2508 | Gets base address of selector |
250c | Gets hardware interrupt vectors |
250d | Gets real mode data buffer address and real mode call back device address |
2511 | Executes interrupt in real mode |
252b | Subfunctions 5 and 6; locks and unlocks virtual memory (useful under DPMI) |
Accessing real mode memory from protected mode
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
Accessing the first megabyte of memory
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
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
Calling a real mode interrupt not implemented by DOSX
Call int86_real() or int86x_real or define something like this if you want your code to compile on DOSX and large model as well:#if (sizeof(int)==4) #define _CALL_INT(intnum,regsin,regsout) int86_real(intnum,&(regsin),&(regsout)) #define _CALL_INTS(intnum,regsin,regsout,segregs) int86x_real(intnum,&(regsin),&(regsout),&(segregs)) #else #define _CALL_INT(intnum,regsin,regsout) int86(intnum,®sin,®sout) #define _CALL_INTS(intnum,regsin,regsout,segregs) int86x(intnum,&(regsin),&(regsout),&(segregs)) #endif
Passing a buffer to a real mode interrupt
The real mode interrupt expects a real mode pointer (segment:offset) below the 1 Mbyte limit. Allocate a buffer in real mode memory and pass its real mode address to the interrupt:unsigned short _x386_convmemalloc(unsigned size); //allocate a conventional memory block //return the conventional memory segment, 0 if error //!!! allocate 4 k byte minimum !!! //!!! there is no way to deallocate this memory !!! unsigned short _x386_convmemalloc(const unsigned size) { REGS regs; regs.x.bx = (unsigned short)((size + 15) >> 4); regs.h.ah = 0x48; int86(0x21, ®s, ®s); return (regs.x.flags & _cf_) ? 0 // DOS error number is in ax : regs.x.ax; }example:
#if (sizeof(int)==4) unsigned convmemseg,dosptr; // real mode segment, pointer void* dosxptr; // DOSX pointer #define DGROUP MKFP(getDS(),0) convmemseg = (unsigned)_x386_convmemalloc(size); dosptr = convmemseg << 16; // offset = 0 dosxptr = (void*)( - _x386_get_abs_address(DGROUP) + (convmemseg << 4)); #endif
Using protected mode pointers
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.Accessing memory mapped i/o devices
DOSX code cannot access memory mapped i/o devices above the 1Mb boundary.Sample code to manipulate pointers
Roland writes: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.
//----------------------------------------------------------------------- // written by Roland VARICHON for RONE Technologies 69100 FRANCE #endif #if (sizeof(int)==2) #define __INTSIZE 2 #else #define __INTSIZE 4 #endif #if (__INTSIZE==4) #include//getDS #endif //----------------------------------------------------------------------- //some definitions and declaration: #if (__INTSIZE==2) #define dword unsigned long #else #define dword unsigned int #endif #define dosptr dword //real mode far pointer: segment:offset #define dosptr_tooff(ptr) ( ((((unsigned)(ptr))>>12) & ~0xf)+(((unsigned)(ptr)) & 0xffff) ) //transform a dosptr to an offset based on 0000:0000 addresse extern "C" dosptr off_todosptr(const dword s); //transform an offset based on 0000:0000 addresse to a dosptr //see asm code below extern "C" dosptr dosptr_align(const dosptr ptr); //align a dosptr: transform it so that offset <= 0xf //see asm code below extern "C" dosptr dosptr_add(const dosptr ptr, const dword v); //add v to a dosptr, return an aligned dosptr //!! v must be >=0 //see asm code below extern "C" dosptr dosptr_seek(const dosptr ptr, const long v); //same as dosptr_add but v can be <0 //see asm code below //----------------------------------------------------------------------------- //C++ code #if (__INTSIZE==2) #define _dosptr_toptr(ptr) ( ((void*)(ptr)) ) //nop on 16 bit lare model #define _ptr_align(ptr) ( ((void*)dosptr_align((dosptr)(ptr))) ) //align a real mode far pointer: transform it so that offset <= 0xf #define _ptr_add(ptr,v) ( ((void*)dosptr_add((dosptr)(ptr),v)) ) //add an offset to a real mode far pointer, return an aligned real mode far pointer //!! v must be >=0 #define _ptr_seek(ptr,v) ( ((void*)dosptr_seek((dosptr)(ptr),v)) ) //same as _ptr_add but v can be <0 #else //__INTSIZE==4 //#include //_x386_get_abs_addresse //commented here because in a SC++ version, if included here it didn't compile, i don't know why #define DGROUP MK_FP(getDS(),0) #define _offtoptr(off) ( ((void*)(-_x386_get_abs_address(DGROUP)+off)) ) //transform an offset based on 0000:0000 addresse to a DOSX near pointer #define _dosptr_toptr(ptr) ( ((void*)(-_x386_get_abs_address(DGROUP)+dosptr_tooff(ptr))) ) //transform a real mode far pointer to a DOSX near pointer #define _ptr_align(ptr) ( ((void*)(ptr)) ) //nop in DOSX model #define _ptr_add(ptr,v) ( ((void*)(((byte*)(ptr))+((unsigned)(v)))) ) //add an offset to a pointer #define _ptr_seek(ptr,v) ( ((void*)(((byte*)(ptr))+((int)(v)))) ) //same as _ptr_add but v is int #endif //__INTSIZE==2 //----------------------------------------------------------------------------- //ASSEMBLY CODE ; you may have to rewrite the functions as i can't give ; you all the macro i wrote for assembly ;it may be enough to be understandable ;just know that all names beginning with '@' charactere are macros ;extern "C" dword dosptr_tooff(dosptr s); @CS @PROC _dosptr_tooff,<_dosptr ??s>; @enter movzx eax,WORD PTR P.??s movzx edx,WORD PTR P.??s[WORD] shl edx,4 add eax,edx IF (INTG EQ 2) shld edx,eax,16 ENDIF @leave @ENDP @ENDS ;extern "C" dosptr off_todosptr(dword s); ;// @CS @PROC _off_todosptr,<_dword ??s>; @enter mov eax,P.??s mov edx,eax shr edx,4 and _ax,0fh IF (INTG EQ 4) shl eax,16 shrd eax,edx,16 ENDIF @leave @ENDP @ENDS ;extern "C" dosptr dosptr_align(dosptr ptr); ;// @CS @PROC _dosptr_align,<_dosptr ??ptr>; @enter movzx eax,WORD PTR P.??ptr mov edx,eax shr _dx,4 add dx,WORD PTR P.??ptr[WORD] and _ax,0fh IF (INTG EQ 4) shl eax,16 shrd eax,edx,16 ENDIF @leave @ENDP @ENDS ;extern "C" dosptr dosptr_add(dosptr ptr, dword v); ;// @CS @PROC _dosptr_add,<_dosptr ??ptr, _dword ??v>; @enter movzx eax,WORD PTR P.??ptr add eax,P.??v mov edx,eax shr edx,4 add dx,WORD PTR P.??ptr[WORD] and _ax,0fh IF (INTG EQ 4) shl eax,16 shrd eax,edx,16 ENDIF @leave @ENDP @ENDS ;extern "C" dosptr dosptr_seek(dosptr ptr, long_short v); ;// @CS @PROC _dosptr_seek,<_dosptr ??ptr, _dword ??v>; @enter movzx eax,WORD PTR P.??ptr movzx edx,WORD PTR P.??ptr[WORD] shl edx,4 add eax,edx add eax,P.??v mov edx,eax shr edx,4 and _ax,0fh IF (INTG EQ 4) shl eax,16 shrd eax,edx,16 ENDIF @leave @ENDP @ENDS
Dual mode DOSX/Win32 programs
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:
hello.c
#include <stdio.h> int main() { #if _WIN32 printf("hello world from WIN32!\n"); #else printf("hello world from DOS!\n"); #endif }hello.def
EXETYPE NT STUB 'hellodos.exe'To build:
dmc -mx hello -ohellodos.exe dmc hello hello.def
DOSX compatibility with Phar Lap
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.
Using NASM built .obj files with DOSX
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.(Thanks to Laurentiu Pancescu)