DOS to 32 bit DLL under XP - an example

To implement our XP video driver for Autotrax we had to get a 16 bit application (in our case Autotrax) to control the hardware on an XP computer. As a rule, under windows 16 bit Apps are not allowed to control the hardware (there is good rationale behind this that we will not delve into right here). The solution is to write a 32 bit Virtual Device Driver, and use NTVDM calls from the DOS application to talk to the driver.

We have documented here basically what we did to make the VDD calls operate, most of this text is a repeat of comments in the source code. Our driver is open source, so you can download the code as an example - we have about 25 different functions passed through to the DLL, (although only a few basic return codes are passed back to the app) so it is a pretty comprehensive example. For those just starting out, we also have a bare bones example available for download.

When running under an XP/NT operating system a 16 bit application cannot directly manipulate system hardware. In effect, under XP a 16 bit app is being run by a microsoft beastie called NTVDM (NT Virtual Dos Machine). However, for your benefit microsoft have provided some NTVDM routines that enable us to build "Virtual Device Drivers" (VDD's) that can translate between 16 bit MS-DOS applications and "special purpose hardware" (or in our case, just video, not so special).

We write a VDD and use the NTVDM calls provided to run DirectX from Autotrax. Also here, we offer a much simpler tutorial example (just 3 pages) that shows the calls to a DLL that adds one to a letter (ie "A" becomes "B").

The VDD Calls

The calls are so brilliantly simple it defies imagination. I feel in awe of the engineer who included these in NTVDM - they are simple, basic and clean. Actually, I haven't said that about that big companies products before, ever.

On the 16 bit app side, you just place 4 bytes in your code, for execution:

0C4h 0C4h 58h 0Nh

  • Where N selects for:
  • 2 = DispatchCall
  • 1 = UnRegisterModule
  • 0 = RegisterModule

At the time of calling DispatchCall or UnRegisterModule the AX register should be set to the VDD handle (...an identifying token...) that you obtain when you first call RegisterModule. (The DispatchCall does the actual interface task.)

This first call to RegisterModule also sets up the interface. The interface is actually between our 16 bit DOS app and two seperate 32 bit routines in a DLL When we call RegisterModule we pass pointers in registers indicating our DLL:

DS:SI Pointer to zero terminated ASCII string being the DLLname autoxpdv.dll
ES:DI Pointer to zero terminated ASCII string: DLL Init routine axinit
DS:BX Pointer to zero terminated ASCII string: DLL Dispatch routine axdisp

RegisterModule will return the VDD handle, or, if there is a problem, a code:

  • AX = 1, Carry = 1: DLL not found
  • AX = 2, Carry = 1: Dispatch routine not found
  • AX = 3, Carry = 1: Init routine not found
  • AX = 4, Carry = 1: Insufficient memory available
  • AX=MMMM,Carry = 0: Successful operation, File handle MMMM is returned

By the nature of DLL's, there is actually a third routine in the 32 bit code, usually called LIBMAIN or DLLMAIN. This routine is invoked when windows first brings up the DLL. If it suits you, you could make the RegisterModule DLL Init routine redundant - and just use the LIBMAIN routine to this job. If the RegisterModule DLL init routine is unused, set ES:DI to 0 in RegisterModule.

The information presented here is the distilled version of what is needed to produce a 16 bit app to 32 bit DLL interface - google on some of the terms used and you will find the original information, with lots more detail (and lots of red herrings also - thunking and VXDs are alternative strategies we do not use)

On the 32 bit side, we provide a DLL that must have 3 exposed routines as already mentioned - the LIBMAIN/DLLMAIN that all DLL's have, an Init routine (in our case axinit) and a dispatch routine (in our case axdisp).

Regretfully, no data is automatically passed to our DLL. Instead we ask NTVDM for the contents of the registers that were passed to it by the 16 bit app, and then tell NTVDM the register contents we want passed back to the 16 bit app. We do this through calls to "NTVDM.EXE" for functions of the style "getBX" and "setBX". Each of these return or take (respectively) a 32 bit value, the lower 16 bits of which are the register contents. There is a seperate call for each of the CPU registers (eg getAX, getBX, getCX ,getDX, getDI, getSI, getDS etc) You will need to include or declare function definitions in your DLL code.

The original source for these functions (if you cannot type them in) is NTVDM.LIB We say "cannot type them in" because we are not at liberty to distribute NTVDM.LIB, it is available in the microsoft SDK (a many hundreds of megabytes download.) There are also functions for 8 bit and 32 bit registers, and much more besides.

Credit is due to Japheth: www.japheth.de - for help and advice on the VDD calls during November 2004.

Download the tutorial example, vddtest.zip containing:

  • vddtest.asm - 16 bit app example (needs masm51 and link to assemble)
  • vddtest.exe - 16 bit app example (assembled and ready to run)
  • vddtest.bas - 32 bit dll example (needs powerbasic to compile)
  • vddtest.dll - 32 bit dll example (compiled and ready to run)

©2013 AirBorn - Last updated 01 May 2013