Êíèãà: Writing Windows WDM Device Drivers

DebugPrint Monitor

DebugPrint Monitor

The DebugPrint Monitor is a user mode application that keeps reading events from the DebugPrint driver and displays them to the user. The Monitor is a standard MFC application, so this chapter will not discuss all the code in detail. The source files can be found on the book software disk.

The Monitor program saves its current screen position and column widths in the registry. Consult the code to find out how this is done.

Design

The Monitor uses a single document interface. The view class CDebugPrintMonitorView is derived from CListView, which encapsulates a list view control. It is this list view control that stores the event information once it has been received by the Monitor. The corresponding document class CDebugPrintMonitorDoc does not store the event information. It simply implements the OnSaveDocument document method to save event information from the view to a .dpm file, and OnOpenDocument to load data.

The main work is carried out in a worker thread, which runs ListenThreadFunction in Listener.cpp. This thread chugs away in the background, reading any available events from the DebugPrint driver. It posts a message to the view class for each event, passing a pointer to the event data. The view class handles these messages in its OnDebugPrintEvent routine by inserting an appropriate event item at the end of the list control.

Win32 Worker Threads

The DebugPrint Monitor application class calls StartListener in its InitInstance method to start the listen worker thread. The ExitInstance method calls StopListener to stop it.

The code uses a LISTENER_INFO structure to pass information to the worker thread. There is only one instance of this object called ListenerInfo.

typedef struct _LISTENER_INFO {
 HANDLE DebugPrintDriver;
 bool KeepGoing;
} LISTENER_INFO, *PLISTENER_INFO;
LISTENER_INFO ListenerInfo;

StartListener first stores a safe HWND handle to the view window, to be used later.

It then finds the first DebugPrint driver device and opens a handle to it using GetDeviceViaInterface01. This is a variant on GetDeviceViaInterface, shown in Chapter 5. GetDeviceViaInterface01 opens the device for overlapped I/O by specifying the FILE_FLAG_OVERLAPPED bit in the dwFlagsAndAttributes parameter to CreateFile. The DebugPrint device handle is stored in ListenerInfo.DebugPrintDriver.

The code uses the KeepGoing field in ListenerInfo to signal when the worker thread should stop. KeepGoing is therefore set to true before the thread is started. The thread is started using AfxBeginThread, passing a pointer to the function to run, ListenThreadFunction, and a context to pass to it.

ListenThreadFunction loops, waiting either for event data or for KeepGoing to return false. ListenThreadFunction is in the file Dispatch.cpp on the CD-ROM.

DebugPrint_Event Class

The DebugPrint_Event class shown in Listing 14.11 is used to communicate events to the view class. Each class instance has Driver and Message strings and an MFC CTime Timestamp. A static method called SendEvent is used to post a DebugPrint_Event message to the view class.

DebugPrint_Event::SendEvent( "Monitor", "Starting to listen", CTime::GetCurrentTime(), false);

Listing 14.11 DebugPrint driver DebugPrint_Event class

const UINT WM_DEBUGPRINTEVENT = (WM_USER+1);
class DebugPrint_Event {
public:
 CString Driver;
 CTime Timestamp;
 CString Message;
 bool SetModified; // false to reset document SetModifiedFlag.
 static HWND ViewHwnd; // View Hwnd
 // Generate and send an event
 static void SendEvent(CString d, CString m, CTime t = 0, bool sm=true) {
  if (ViewHwnd==NULL) return;
  DebugPrint_Event* pEvent = new DebugPrint_Event;
  pEvent->Driver = d;
  if (t==0) t = CTime::GetCurrentTime();
  pEvent->Timestamp = t;
  pEvent->Message = m;
  pEvent->SetModified = sm;
  ::PostMessage(ViewHwnd, WM_DEBUGPRINTEVENT, 0, (LPARAM)pEvent);
 }
};

Win32 Overlapped I/O

The code in ListenThreadFunction mainly deals with overlapped I/O to the DebugPrint device. It must check the state of the KeepGoing flag. Overlapped I/O lets us issue a read request and get on with other tasks.

To do overlapped I/O, a Win32 event and an OVERLAPPED structure are needed. CreateEvent is used to initialize the FileIOWaiter manual event into the nonsignalled state. The OVERLAPPED structure (ol) stores the file pointer offset and FileIOWaiter event handle.

A standard ReadFile call is used to initiate a read request. The read buffer is 1024 bytes, which should be large enough for any DebugPrint event. ReadFile is passed a pointer to the OVERLAPPED structure. Overlapped I/O does work with device files in Windows 98, but does not work on ordinary file I/O.

ReadFile returns true if the read request completes straightaway. The number of bytes transferred is stored in TxdBytes. If the read request is held in the DebugPrint read queue, ReadFile returns false and GetLastError returns ERROR_IO_PENDING. The code checks for a real error return from ReadFile. If the DebugPrint Monitor application is run twice, the second incarnation will get an error here when the DebugPrint driver read routine fails an attempt to queue a second Read IRP.

If the read request is pending, ListenThreadFunction loops calling WaitForSingleObject with a timeout of 100ms. If WaitForSingleObject times-out, ListenThreadFunction checks to see if KeepGoing has returned false. If so, it calls CancelIo to cancel the pending Read IRP and exits. Incidentally, calling CancelIo in another thread, such as the StopListener routine, does not work.

WaitForSingleObject detects when the read request has finished when the FileIOWaiter event becomes signalled. In Windows 2000, ListenThreadFunction could wait on the file object instead of an event. However, this does not work in Windows 98, so I use events that work in both operating systems. ListenThreadFunction calls GetOverlappedResult to retrieve the number of bytes that were received.

The remaining code extracts the event information from the read buffer and builds an event object to post to the Monitor view class. The event timestamp that was generated in the DebugPrintMsg routine is a time in GMT. The GMTtoLocalTime function does all the necessary grovelling around to convert this into a local time.

ListenThreadFunction finally closes the event and file handles using CloseHandle.

Listing 14.12 DebugPrint driver ListenThreadFunction function

UINT ListenThreadFunction(LPVOID pParam) {
 PLISTENER_INFO pListenerInfo = (PLISTENER_INFO)pParam;
 if (pListenerInfo==NULL) return –1;
 CString StartMsg = "Starting to listen";
 // …
 DebugPrint_Event::SendEvent("Monitor", StartMsg, CTime::GetCurrentTime(), false);
 // Buffer for events
 const int MAX_EVENT_LEN = 1024;
 char Event[MAX_EVENT_LEN+1];
 // Create Overlapped read structure and event
 HANDLE FileIOWaiter = CreateEvent(NULL, TRUE, FALSE, NULL);
 if (FileIOWaiter==NULL) goto Exit2;
 OVERLAPPED ol;
 ol.Offset = 0;
 ol.OffsetHigh = 0;
 ol.hEvent = FileIOWaiter;
 // Keep looping, waiting for events, until KeepGoing goes false
 for(;;) {
  // Initiate overlapped read
  DWORD TxdBytes;
  ResetEvent(FileIOWaiter);
  memset(Event,0,MAX_EVENT_LEN+1);
  if (!ReadFile(ListenerInfo.DebugPrintDriver, Event, MAX_EVENT_LEN, &TxdBytes, &ol)) {
   // Check for read errors
   if (GetLastError() !=ERROR_IO_PENDING) {
    CString Msg;
    Msg.Format("Read didn't return pending %d", GetLastError());
    DebugPrint_Event::SendEvent("Monitor", Msg);
    goto Exit;
   }
   // Wait for read to complete (check for KeepGoing
   // going false every 100ms)
   while (WaitForSingleObject(FileIOWaiter, 100)== WAIT_TIMEOUT) {
    if (!ListenerInfo.KeepGoing) {
     // Cancel the pending read
     CancelIo(ListenerInfo.DebugPrintDriver);
     goto Exit;
    }
   }
   // Get read result, ie bytes transferred
   if (!GetOverlappedResult(ListenerInfo.DebugPrintDriver, &ol, &TxdBytes, FALSE)) {
    DebugPrint_Event::SendEvent("Monitor", "GetOverlappedResult failed");
    continue;
   }
  }
  // Check there's something there
  if (TxdBytes < sizeof(TIME_FIELDS)+2) {
   DebugPrint_Event::SendEvent("Monitor", "Short read msg");
   continue;
  }
  // Extract Timestamp, Driver and Msg, and post to View
  Event[MAX_EVENT_LEN] = '';
  PTIME_FIELDS pTF = (PTIME_FIELDS)Event;
  CTime gmtEventTime(pTF->Year, pTF->Month, pTF->Day, pTF->Hour, pTF->Minute, pTF->Second);
  CTime EventTime = GMTtoLocalTime(gmtEventTime);
  char* DriverName = Event+sizeof(TIME_FIELDS);
  CString CSDriverName = DriverName;
  CString CSDriverMsg = Event+sizeof(TIME_FIELDS)+strlen(DriverName)+1;
  DebugPrint_Event::SendEvent(CSDriverName, CSDriverMsg, EventTime);
 }
Exit:
 CloseHandle(FileIOWaiter);
Exit2:
 CloseHandle(ListenerInfo.DebugPrintDriver);
 ListenerInfo.DebugPrintDriver = NULL;
 DebugPrint_Event::SendEvent("Monitor", "Stopped listening");
 return 0;
}

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.094. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç