# Virtual Device Driver Skeleton

Now that you know something about VMM and VxD, we can learn how to code a VxD. You need Windows 95/98 Device Driver Development Kit. It's essential that you have it. Windows 95 DDK is available only to MSDN subscribers. However, Windows 98 DDK is available free of charge from Microsoft. You can also use Windows 98 DDK to develop VxDs even if it is oriented toward WDM. You can download Windows 98 DDK from  http://www.microsoft.com/hwdev/ddk/install98ddk.htm?
Windows 9x DDK contains several essential include files which are not included in MASM32 package.

### LE File Format

VxD uses linear executable (LE) file format. This is the file format designed for OS/2 version 2.0. It can contain both 16- and 32-bit code which is one of the requirement of VxDs. Remember that VxDs date back from the days of Windows 3.x. During that time, Windows boots from DOS so VxDs may need to do some initialization in real mode before Windows switches the machine into protected mode. Real-mode 16-bit code must be in the same executable file as the 32-bit protected mode code. So LE file format is the logical choice. Windows NT drivers blissfully don't have to deal with real mode initialization so they don't have to use LE file format. Instead they use PE file format.

Code and data in an LE file are stored in segments with different runtime attributes. Below are the available segment classes.

• LCODE  Page-locked code and data. This segment is locked into memory. In other words, this segment will not be paged to disk, thus you should use this segment class judiciously so as not to waste precious system memory. Code and data that must be present in memory at all time should be in this segment. Especially the hardware interrupt handlers.
• PCODE Pageable code. This segment is pageable by VMM. Code in this segment needs not be present in memory all the time. VMM will page this segment to disk if it needs physical memory.
• PDATA Pageable data.
• ICODE Initialization-only code. The code in this segment is used during initialization of the VxD only. After initialization, this segment will be discarded by VMM to reclaim physical memory.
• DBOCODE debug-only code and data. The code and data in this segment is used when you run the VxD under a debugger. It contains the handler for Debug_Query control message, for example.
• SCODE Static code and data. This segment will always be present in memory even when the VxD is unloaded. This segment is especially useful for dynamic VxD when it must be loaded/unloaded many times during a Window session and wants to remember the last configuration/state.
• RCODE Real-mode initialization code and data. This segment contains the 16-bit code and data for real mode initialization.
• 16ICODE USE16 protected-mode initialization data. This segment is a 16-bit one which contains the code that the VxD will copy from protected mode to V86 mode. For example, if you want to paste some V86 code into a VM, the code you intend to paste must be in this segment. If you put the code in other segment, the assembler will generate wrong code, i.e. it will generate 32-bit code instead of the intended 16-bit one.
• MCODE Locked message strings. This segment contains message strings which are compiled with the help of VMM message macros. This helps you create international versions of your driver.
It doesn't mean your VxD must have ALL those segments. You can choose the segments you want to use in your VxD. For example, if your VxD doesn't have real-mode initialization, it doesn't have to have RCODE segment.
Most of the time, you would use LCODE, PCODE and PDATA. It's your judgement as a VxD writer to choose the appropriate segments for your code/data. In general, you should use PCODE and PDATA as much as possible because VMM can page the segments in and out of memory if necessary. You should use LCODE to store hardware interrupt handlers and services that will be called by hardware interrupt handlers.
You don't use those segment classes directly. You must declare segments based on those classes. Those segment declarations are stored in the module definition file (.def). The full-scale module definition file for a VxD is below:
VXD FIRSTVXD
SEGMENTS
_STEXT      CLASS 'SCODE'    RESIDENT
_SDATA      CLASS 'SCODE'    RESIDENT
_RCODE      CLASS 'RCODE'
EXPORTS
FIRSTVXD_DDB  @1
The first statement is for declaration of the VxD name. The name of a VxD MUST be all uppercase. I experimented with the lowercase name, and the VxD refused to perform anything except loading itself into memory.
Next are the segment declarations. The declaration consists of three parts: the name of the segment, the segment class and the desired runtime property of the segment. You can see that there are many segments based on the same class, for example, _LPTEXT, _LTEXT, _LDATA are all based on LCODE segment class with exactly the same properties. These segments are declared to make coding easier to understand. For example, LCODE can contain both code and data. It'll be easier on the programmer if he can store the data in _LDATA segment and the code in _LTEXT segment. Eventually, both segments will be combined into only one segment in the final executable file.
A VxD exports one and only one symbol, its device descriptor block (DDB). The DDB is actually a structure which contains everything VMM needs to know about a VxD. You MUST export the DDB in the module definition file.
Most of the time, you can use the above .DEF file in new VxD projects. You would have to change only the name of the VxD in the first and the last lines of the .DEF file. The segment declarations are overkills for an asm VxD project. They are for use by a C VxD project but using them in an asm project is ok. You'll get a lot of warning messages but it will assemble. You can get rid of the annoying warning messages by deleting the segment declarations you don't use in your project.
vmm.inc contains many macros for declaring segments in your source file.

 _LTEXT VxD_LOCKED_CODE_SEG _PTEXT VxD_PAGEABLE_CODE_SEG _DBOCODE VxD_DEBUG_ONLY_CODE_SEG _ITEXT VxD_INIT_CODE_SEG _LDATA VxD_LOCKED_DATA_SEG _IDATA VxD_IDATA_SEG _PDATA VxD_PAGEABLE_DATA_SEG _STEXT VxD_STATIC_CODE_SEG _SDATA VxD_STATIC_DATA_SEG _DBODATA VxD_DEBUG_ONLY_DATA_SEG _16ICODE VxD_16BIT_INIT_SEG _RCODE VxD_REAL_INIT_SEG

Each macro has its ending counterpart. For example, if you want to declare an _LTEXT segment in your source file, you would do it like this:

VxD_LOCKED_CODE_SEG

VxD_LOCKED_CODE_ENDS

### VxD Skeleton

Now that you know about segments in LE file, we can go on to the source file. One thing you can observe about VxD programming is the heavy use of macros. You will find macros everywhere in VxD programming. It takes some getting used to. Those macros are provided to hide some gory details from programmers and in some ways, make the source code more portable. If you are curious, you can read the definition of those macros in various include files such as vmm.inc.
Here is the VxD skeleton source code:

.386p
include vmm.inc

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD

end

At first glance, the source code doesn't look like an asm source code. That's because of the use of macros. Let's analyze this source code and you'll soon understand it.

.386p
Tell the assembler that we want to use 80386 instruction set including the privileged CPU instructions. You can also use .486p or .586p.
include vmm.inc
You must include vmm.inc in every VxD source code because it contains the definitions of the macros you use in the source file. You can include other include files as needed.
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
As stated previously, the VMM learns everything it needs to know about a VxD from the VxD's device descriptor block (DDB). A device descriptor block is a structure that contains vital information about the VxD such as the VxD's name, its device ID, the entrypoints of its VxD services (if exist) and so on. You can look up this structure in vmm.inc. It's declared as VxD_Desc_Block. You'll export this structure in .DEF file. There are 22 members in this structure, but you'll usually need to fill only some of them. So vmm.inc contains a macro that will initialize and fill the structure members for you. That macro is DECLARE_VIRTUAL_DEVICE. It has the following format:

Declare_Virtual_Device   Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData

One thing you can observe is that, the labels in VxD source code is case-insensitive. You can use upper- or lowercase characters or combination of them. Let's examine each parameter of Declare_virtual_device.

• Name  The name of the VxD. Maximum length is 8 characters. It MUST be uppercase. The name should be unique among the VxDs in the system. The macro also uses the name to create the name of the DDB by appending _DDB to the device name. So if you use FIRSTVXD as the name of the VxD, Declare_Virtual_Device macro will declare the name of the DDB as FIRSTVXD_DDB. Remember that, you will also have to export the DDB in .DEF file too. You have to match the label in the source file with the one in the .DEF file.
• MajorVer and MinorVer The major and minor versions of your VxD
• CtrlProc The name of the device control procedure for your VxD. A device control procedure is a function that receives and processes control messages for a VxD. You can think of a device control procedure as the window procedure equivalence. Since we will use Begin_Control_Dispatch macro to create our device control procedure, we should use the standard name which is in the form of VxDName_Control. Begin_Control_Dispatch macro appends _Control to the name passed to it ( and we usually pass the name of the VxD to it) so we should specify the name of our VxD appended by _Control in CtrlProc parameter.
• DeviceID The 16-bit unique identifier of your VxD. You need the ID if and only if your VxD must handle one of the situations below
• Your VxD exports VxD services for other VxDs to use. Since int 20h interface uses the device ID to locate/identify the VxD, it's imperative that your VxD must have a unique identifier.
• Some real-mode software (TSR) will use int 2Fh, function 1605h to load your VxD.
If your VxD doesn't need a unique device ID, you can specify UNDEFINED_DEVICE_ID in this field. If you do need a unique ID, you have to ask Microsoft for one
• InitOrder Initialization order, or in short, load order. VMM loads VxDs in the order specified. Each VxD will have a load order number. For example,
VMM_INIT_ORDER        EQU 000000000H
DEBUG_INIT_ORDER      EQU 000000000H
DEBUGCMD_INIT_ORDER   EQU 000000000H
PERF_INIT_ORDER       EQU 000900000H
APM_INIT_ORDER        EQU 001000000H

You can see that VMM, DEBUG and DEBUGCMD are the first VxDs that are loaded, followed by PERF and APM. The VxD with lower value of initialization order is loaded first. If your VxD requires the services of other VxD during initialization, you should specify an initialization order value that is larger than that of the VxD you want to call so that by the time your VxD is loaded, that VxD is already there in memory, ready for you. If your VxD doesn't care about initialization order, specify UNDEFINED_INIT_ORDER in this parameter.

• V86Proc and PMProc Your VxD can export API for use by V86 and protected-mode programs. V86Proc and PMProc specify the addresses of those API. Remember that, VxDs exist primarily for supervising VMs and a VM other than the system VM runs a DOS or protected-mode application. It stands to reason for VxDs to provide API support for DOS and protected-mode programs. If you don't export those API, you can omit these fields.
• RefData Reference data used by Input Output Supervisor (IOS). The only occasion you would use this field is when you code a layer block driver for use with IOS. You can omit this field if your VxD is not a layer driver.
Next we have Begin_Control_Dispatch macro.
Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD
This macro and its counterpart define the device control procedure which is the function that VMM calls when there are control messages for your VxD. You must specify the first half of the name of the device control procedure, in our example we use FIRSTVXD. The macro will append _Control to the name you supplied.This name must match the one you specify in CtrlProc parameter of Declare_virtual_device macro. The device control procedure is always in a locked segment (VxD_LOCKED_CODE_SEG). The above device control procedure does nothing. You have to specify what control messages your VxD is interested in handling and the functions that will handle them. You use Control_Dispatch macro for this purpose.
Control_Dispatch message, function
For example, if your VxD processes only Device_Init message, your device control procedure would look like this:
Begin_Control_Dispatch  FIRSTVXD
Control_Dispatch  Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit is the name of the function that will handle Device_Init message. You can name your function anything you like.
You end the VxD source code with end directive.
To recapitulate, at a minimum, a VxD must have a device control block and a device control procedure. You declare a device control block with Declare_Virtual_Device macro and a device control procedure with Begin_Control_Dispatch macro. You must export the device control block by specifying its name under EXPORTS directive in .DEF file.

### Assembling the VxD

The assembling process is the same as the one used in assembling normal win32 applications. You invoke ml.exe on the asm source code and then link the object file with link.exe. The differences are in the command line switches used by ml.exe and link.exe

ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32  firstvxd.asm

-coff  Specify the COFF object format
-c   Assemble only. Do not call the linker to link the object file, since we will call link.exe later with more parameters.
-Cx  Preserve the case of public, extern labels.
-D<text> defines a text macro. For example, -DBLD_COFF defines a text macro BLD_COFF which will be used in conditional assembly. If you're interested, you can search for BLD_COFF in the include files and see for yourself what effect it has on the assembly process. So in the command line switches above, three text macros are defined: BLD_COFF, IS_32, and MASM6. If you're familiar with C, this process is identical to:

#define BLD_COFF
#define IS_32
#define MASM6

-vxd specifies that we want to build a VxD from the object file
-def:<.DEF file> specifies the name of the module definition file of the VxD

I find it more convenient to use makefile but you can create a batch file to automate the assembling process if you don't like makefile approach. Here's my makefile.

NAME=firstvxd

$(NAME).vxd:$(NAME).obj
link -vxd -def:$(NAME).def$(NAME).obj

$(NAME).obj:$(NAME).asm
ml -coff -c -Cx  -DMASM6 -DBLD_COFF -DIS_32 \$(NAME).asm

[Iczelion's Win32 Assembly Homepage]
0 0