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)
This page was last updated 01 February 2010