|
Last modified: 8 February 1999. |
While a complete port to Win32 is the ideal solution, resource constraints may make this impossible in the short term. If hardware access is the main stumbling block then you can write a Win32 device driver which is accessible to the DOS or Win16 code, provided you have access to the source of the legacy code.
Note that the technique described is not to emulate the hardware.
It is best to isolate your hardware access code into a separate module, or DLL for Win16. Have a DOS or Win16 version which accesses the hardware directly for DOS, Windows 3.x and Windows 95/98. The NT/W2000 version of this module or DLL accesses the Win32 device driver.
The NT/W2000 installation routine must install the device driver. Depending on your detailed requirements, the device driver could start when NT/W2000 starts. A better approach might be to start the device driver just before the Win16 program starts and close it afterwards. A Win32 wrapper program is needed to do this job, using the Win32 Service Control Manager functions OpenSCManager, OpenService, ControlService, StartService and CloseServiceHandle.
However a link from LPT1 is output only. The most useful DOS device names are COM1 to COM9 which are bidirectional. Note that you should not use a colon at the end of the device name and the \\.\
should be omitted at the start of device name.
Now, NT/W2000�s own parallel and serial drivers will attempt to allocate DOS device names for each of the present ports. It is possible to override this allocation by ensuring that your device is loaded before the relevant system device (called "parallel"). The NT/W2000 parallel port arbitrator "parport" driver is in group "Parallel arbitrator". The parallel class driver "parallel" is one of several drivers in group "Extended Base". If you make your driver load after "parport" and "Parallel arbitrator" but before "Extended Base" then it can reserve the name LPT1 before "parallel" does. "parallel" will only moan minorly to the event log. Obviously this approach means that the driver will have to load at boot time.
However it is friendlier just to use a spare DOS device name, eg COM9. It does not matter that the driver talks to the parallel port eventually.
Note that DOS and Win16 do not have an equivalent of the DeviceIoControl function, so only reads and writes can be used. The Win16 functions OpenFile, _lopen, _lwrite, _lread and _lclose should be used, or - in DOS - the unbuffered standard _open, _write, _read and _close.
Storing the Device Name The device driver and the legacy module need to know what device to open. For DOS, you will need to hardcode the device name or store it in a file somewhere.
However Win16 applications have basic access to the registry using the functions
RegOpenKey, RegQueryValue
and RegCloseKey. These functions just access the
"(Default)"
string value for a key, and cannot access String, Binary, DWORD, etc. The device driver can access the "(Default)"
string value using the RtlQueryRegistryValues function, setting the RTL_QUERY_REGISTRY_TABLE Name
field to a blank Unicode string (L""
). The following registry key might be appropriate for an AbcDriver driver.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AbcDriver\Parameters
In addition the driver should read another DWORD parameter from this same key, eg called Win16Port
. The driver would then create a Win32 symbolic link between the "(Default)"
string name and the required NT/W2000 device name. For example if "(Default)"
is "COM9" and Win16Port
is 1, then a symbolic link is set up between COM9 and \Device\AbcDriver0, ie talking to COM9 communicates with the first parallel port.
Here is the required Win16 code to read the device name from registry. If successful, it returns a non-zero value and sets the device name in PortName
.
int NEAR GetPicLptDriverDeviceName(LPSTR PortName,int len) { int rv = 0; HKEY HKEY_LOCAL_MACHINE = 0x80000002; HKEY hkParameters; PortName[0] = '\0'; if( RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\AbcDriver\\Parameters", (HKEY FAR*)&hkParameters) == ERROR_SUCCESS) { LONG cb = len; if( RegQueryValue(hkParameters,NULL,PortName,&cb) == ERROR_SUCCESS) { if( PortName[0]!='\0') rv = 1; } RegCloseKey(hkParameters); } return rv; }This is driver code to read the registry and set up the appropriate symbolic link, visible to DOS and Win16 applications.
static NTSTATUS AbcCreateWin16Port( IN PDRIVER_OBJECT pDriverObject, IN ULONG NumParallelPorts) { NTSTATUS status; RTL_QUERY_REGISTRY_TABLE QueryTable[3]; UNICODE_STRING number, linkName, deviceName; WCHAR numberBuffer[10]; WCHAR linkNameBuffer[ ABC_MAX_NAME_LENGTH ]; WCHAR deviceNameBuffer[ ABC_MAX_NAME_LENGTH ]; ///////////////////////////////////////////////////////////////////////// // Assume no Win 16 port given Win16LPTNo = ABC_WIN16_UNDEFINED; // Initialise unicode strings number.Buffer = numberBuffer; number.MaximumLength = 20; number.Length = 0; linkName.Buffer = linkNameBuffer; linkName.MaximumLength = ABC_MAX_NAME_LENGTH*2; linkName.Length = 0; deviceName.Buffer = deviceNameBuffer; deviceName.MaximumLength = ABC_MAX_NAME_LENGTH*2; deviceName.Length = 0; ///////////////////////////////////////////////////////////////////////// // Fabricate a Registry query RtlZeroMemory( QueryTable, sizeof(QueryTable)); QueryTable[0].Name = L"Win16Port"; QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; QueryTable[0].EntryContext = &Win16LPTNo; QueryTable[1].Name = L""; // Default value QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT; QueryTable[1].EntryContext = &Win16Device; // Look for the Win16Port and device name values in the Registry. if( !NT_SUCCESS( RtlQueryRegistryValues( RTL_REGISTRY_SERVICES, ABC_DRIVER_NAME L"\\Parameters", QueryTable, NULL, NULL)) || (Win16LPTNo==ABC_WIN16_UNDEFINED) || (Win16Device.Length==0)) { // If no Win16Port and device name values // then issue warning and just return // .. issue warning Win16LPTNo = ABC_WIN16_UNDEFINED; return STATUS_SUCCESS; } // Form Win16 symbolic link name RtlAppendUnicodeToString( &linkName, ABC_DOS_DEVICES); RtlAppendUnicodeStringToString( &linkName, &Win16Device); // Check port number is possible if( Win16LPTNo<1 || Win16LPTNo>NumParallelPorts) { if( Win16LPTNo==0) { Win16LPTNo = ABC_WIN16_UNDEFINED; return STATUS_SUCCESS; } Win16LPTNo = ABC_WIN16_UNDEFINED; // .. issue warning return STATUS_INSUFFICIENT_RESOURCES; } // Form the base NT device name... RtlAppendUnicodeToString( &deviceName, ABC_NT_DEVICE_NAME); number.Length = 0; RtlIntegerToUnicodeString( Win16LPTNo-1, 10, &number); RtlAppendUnicodeStringToString( &deviceName, &number); // Create a symbolic link so our device is visible to Win32... status = IoCreateSymbolicLink( &linkName, &deviceName); if( !NT_SUCCESS(status)) { // If already taken, report error // .. issue warning Win16LPTNo = ABC_WIN16_UNDEFINED; return STATUS_INSUFFICIENT_RESOURCES; } // Log a message saying that Win16 link has been created // .. log message return STATUS_SUCCESS; }
Also look at the DefineDosDevice and QueryDosDevice functions.