A Better Way to Provide VxD to VxD Services
When a VxD wants to export services to other VxDs, the conventional approach is to create a VxD service table. The VxD service table contains the addresses of the services to be exported, and there is a pointer to the table in the VxD's Device Data Block (DDB). Other VxDs call the exported services using macro VxDCall. This macro issues instruction INT 20h, which is trapped by the system. The system's INT 20h handler examines the DWORD that immediately follows the INT instruction to determine which VxD service is being called. The exporter VxD is identified by a device ID constant, and the service within that VxD by an ordinal value. The INT 20h locates the DDB of the exporter VxD, finds its service table, extracts the function address, and then patches over the INT 20h in the caller's instruction stream with a CALL instruction that invokes the target VxD service.
While this mechanism is certainly tried and true, it has some disadvantages. First, the declaration of the VxD service table requires use of a few ugly macros. Then the address of the table and the service count must be inserted into the DDB. These details create opportunities to introduce bugs. Ensuring matching calling sequences and calling conventions (i.e. __stdcall vs. __cdecl) complicates it further. Another headache is obtaining a unique device ID to designate the exporter VxD. VxD ID constants are allocated only by Microsoft, so if you arbitrarily assign one, then your VxD could conflict with some other VxD.
There is an alternative approach to exporting VxD services which addresses some of these problems. You can use the system service Directed_Sys_Control to send a control message to the exporter VxD. Typically, the control message that you send is the general purpose W32_DEVICEIOCONTROL. In other words, a VxD can export a set of services, each of which corresponds to a unique identifier passed with the W32_DEVICEIOCONTROL message.
In the VxD that exports the services, using this method is as simple as adding cases to a switch statement in the handler for W32_DEVICEIOCONTROL. However, the first step is to create a shared include file that defines the set of identifiers for the services. This is done with macro CTL_CODE:
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS)
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS)
The code above defines three constants that identify the exported services. So the handler for W32_DEVICEIOCONTROL in the exporter VxD looks like this (using VtoolsD):
DWORD OnW32Deviceiocontrol(PIOCTLPARAMS p)
case DIOC_OPEN: // CreateFile
case DIOC_CLOSEHANDLE: // handle closed
// implement service 1
// implement service 2
// implement service 3
return 1; // bad function
In order to use Directed_Sys_Control from the calling VxD to send a message to the exporter VxD, you must first locate the DDB of the exporter VxD. This has to be done just once, not each time the calling VxD sends a message.
To locate the DDB of the target device, use system service Get_DDB. Once you have a valid DDB pointer, use this function to call the exported service:
PDDB pDdb, // pointer to DDB of exporter VxD
ULONG IoctlCode, // dioc_IOCtlCode
PVOID InputBuffer, // dioc_InBuf
ULONG SizeOfInputBuffer, // dioc_cbInBuf
PVOID OutputBuffer, // dioc_OutBuf
ULONG SizeOfOutputBuffer,// dioc_cbOutBuf
PULONG pBytesReturned // dioc_bytesret
ALLREGS regs; // register struct passed to Directed_Sys_Ctrl
IOCTLPARAMS io; // Ioctl param struct passed to target device
if ( pDdb == NULL )
// set up the ioctl params
io.dioc_IOCtlCode = IoctlCode;
io.dioc_InBuf = InputBuffer;
io.dioc_cbInBuf = SizeOfInputBuffer;
io.dioc_OutBuf = OutputBuffer;
io.dioc_cbOutBuf = SizeOfOutputBuffer;
io.dioc_bytesret = pBytesReturned;
// clear the regs structure
memset(®s, 0, sizeof(regs));
// put a pointer to the ioctl param struct in the register struct
regs.RESI = (DWORD)&io;
// send the message to the target device
Directed_Sys_Control(pDdb, W32_DEVICEIOCONTROL, ®s);
return (regs.REAX == DEVIOCTL_NOERROR);
The parameters to the SendDeviceIoControl become the indicated members of the IOCTLPARAMS structure that the exporter VxD receives in its handler for control message W32_DEVICEIOCONTROL. You can customize a set of services using this general mechanism.
Compared to the conventional VxD service table method, the Directed_Sys_Control method has some clear advantages. It avoids the use of complicated and error prone macros. There is a single fixed calling convention which can be safely adapted to an arbitrary set of services. You don't need to obtain a unique device ID constant from Microsoft. And if you will be porting the VxD to an NT or WDM driver in the future, the code will be much easier to port.
One note of warning. Directed_Sys_Control does not validate the DDB pointer passed to it, so the exporter VxD and its caller should establish a protocol that provides the caller of some notification when the exporter unloads.