Implementing Stream Functions
Having created the DLL subproject, you can open the source code file in Visual Studio and add the required functions to implement the stream interface and required driver functionality. The following code listing shows the definition of the stream interface functions.
// SampleDriver.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved) {
return TRUE;
}
DWORD SMP_Init(LPCTSTR pContext, LPCVOID lpvBusContext) {
// Implement device context initialization code here.
return 0x1;
}
BOOL SMP_Deinit(DWORD hDeviceContext) {
// Implement code to close the device context here.
return TRUE;
}
DWORD SMP_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode) {
// Implement open context initialization code here.
return 0x2;
}
BOOL SMP_Close(DWORD hOpenContext) {
// Implement code to close the open context here.
return TRUE;
}
DWORD SMP_Write(DWORD hOpenContext, LPCVOID pBuffer, DWORD Count) {
// Implement the code to write to the stream device here.
return Count;
}
DWORD SMP_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count) {
// Implement the code to read from the stream device here.
return Count;
}
BOOL SMP_IOControl(DWORD hOpenContext, DWORD dwCode,
PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut,
DWORD dwLenOut, PDWORD pdwActualOut) {
// Implement code to handle advanced driver actions here.
return TRUE;
}
void SMP_PowerUp(DWORD hDeviceContext) {
// Implement power management code here or use IO Control.
return;
}
void SMP_PowerDown(DWORD hDeviceContext) {
// Implement power management code here or use IO Control.
return;
}
Exporting Stream Functions
Making the stream functions in the driver DLL accessible to external applications requires the linker to export the functions during the build process. C++ provides several options to accomplish this, yet for driver DLLs compatible with Device
Manager, you must export the functions by defining them in the .def file of the DLL subproject. The linker uses the .def file to determine which functions to export and how to do so. For a standard stream driver, you must export the stream interface functions using the prefix that you specify in the driver's source code and registry settings. Figure 6-3 shows a sample .def file for the stream interface skeleton listed in the previous section.
Figure 6-3 A sample .def file for a stream driver
Sources File
Prior to building the newly created stream driver, you should also check the Sources file in the root folder of the DLL subproject to ensure that it includes all necessary files in the build process. As mentioned in Chapter 2, the Sources file configures the compiler and linker to build the desired binary files. Table 6-2 summarizes the most important Sources file directives for device drivers.
Table 6-2 Important Sources file directives for device drivers
Directive | Description |
---|---|
WINCEOEM=1 | Causes additional header files and import libraries from the %_WINCEROOT%\Public tree to be included to enable the driver to make platform-dependent function calls, such as KernelIoControl, InterruptInitialize, and InterruptDone. |
TARGETTYPE=DYNLINK | Instructs the Build tool to create a DLL. |
DEFFILE=<Driver Def File Name>.def | References the module-definition file that defines the exported DLL functions. |
DLLENTRY=<DLL Main Entry Point> | Specifies the function that is called when processes and threads attach and detach to and from the driver DLL (Process Attach, Process Detach, Thread Attach, and Thread Detach). |
Opening and Closing a Stream Driver by Using the File API
To access a stream driver, an application can use the CreateFile function and specify the desired device name. The following example illustrates how to open a driver called SMP1: for reading and writing. It is important to note, however, that Device Manager must already have loaded the driver, such as during the boot process. Lesson 3 later in this chapter provides detailed information about configuring and loading device drivers.
// Open the driver, which results in a call to the SMP_Open function
hSampleDriver = CreateFile(L"SMP1:",
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hSampleDriver == INVALID_HANDLE_VALUE) {
ERRORMSG(1,(TEXT("Unable to open the driver.\r\n"));
return FALSE;
}
// Access the driver and perform read,
// write, and seek operations as required.
// Close the driver
CloseHandle(hSampleDriver);
Dynamically Loading a Driver
As mentioned earlier in this lesson, an application can also communicate with a stream device driver after calling the ActivateDevice or ActivateDeviceEx function. ActivateDeviceEx offers more flexibility than ActivateDevice, yet both functions cause Device Manager to load the stream driver and call the driver's XXX_Init function. In fact, ActivateDevice calls ActivateDeviceEx. Note, however, that ActivateDeviceEx does not provide access to an already loaded driver. The primary purpose of the ActivateDeviceEx function is to read a driver-specific registry key specified in the function call to determine the DLL name, device prefix, index, and other values, add the relevant values to the active device list, and then load the device driver into the Device Manager process space. The function call returns a handle that the application can later use to unload the driver in a call to the DeactivateDevice function.