Class encapsulated driver
The preceding clsint is too simple to answer the following question: what are the benefits of using classes in the kernel? The simclass project cannot answer the above questions. I just use it to solve some basic problems. Next, let's think about the question: in terms of the driver itself, how can we encapsulate the kernel driver into a class?
The kernel driver has some data structures: Driver objects, device objects, file objects, IRP, etc. The processing of these data structures is the kernel function: a wdm driver is a dispatch function, and a WDF is an event ).
Isn't that right? The above two are exactly the basic elements of class encapsulation! Class, data addition method. I will use all the data structures used, such as driver objects and Device objects, as member data, and use the distribution functions, events, and callbacks as member functions. A "Driver Class" is beginning to expose itself.
The idea is good, but there are two problems, which are described below.
6.2.1 find a suitable storage center
The first problem to be solved before defining a class is that after a class object is created, its lifecycle is basically the same as that of the driver. Where can I save the class object? Creating global variables is certainly a method, but there will be conflicts when there are multiple driver instances. In the WDM driver, there are device extensions that can save their own variables. Kmdf is richer, and the author finally decides to save class objects in wdfdriver objects. The effect achieved is 6-5.
Driver objects and Device objects are the core of the driver, and callback functions are the core. In Figure 6-5, the callback functions of the driver object and the device object are implemented in the drvclass class. In order to keep the lifecycle of the C ++ class object consistent with the drive object, a wdmmemory object is encapsulated and used as the sub-object of the drive object, which is automatically maintained by the framework, when the driver object exists, the C ++ class object will always be valid.
First, let's take a look at how to save a custom content to the driver object. This requires the "environment variable" concept of the Framework object. We have learned how to set environment variables for the device object, now it's the turn to drive the object. Let's try again.
Figure 6-5 object module diagram
Step 2: Define a function to get the environment block pointer.
Wdf_declare_context_type_with_name (driver_context,
Getdrivercontext );
The macro above defines a function named getdrivercontext. The pseudo code of this function is as follows:
* Driver_context getdrivercontext (wdfobject object)
{
// XXX is a fixed address. Its definition cannot be known because it is not documented.
Return (driver_context *) object-> xxx;
}
In the future, you only need to make the following calls to obtain the environment block pointer of the driver object (provided that the correct object handle is passed in ).
// Obtain environment variables
Driver_context * pcontext = getdrivercontext (wdfdriver );
Step 2: When wdfdrivercreate creates a framework driver object, set the structure of the Environment Variable through wdf_driver_config. The previous part of the code below implements this step.
Step 2: Call getdrivercontext to obtain the environment variable, encapsulate it into a wdfmemory object, and specify the driver object created in step 1 as its parent object, to enable the Framework to automatically maintain its lifecycle. The code below is followed by this step.
Ntstatus drvclass: DriverEntry (
In pdriver_object driverobject,
In punicode_string registrypath)
{
Kdbg (dpfltr_info_level, "drvclass: DriverEntry ");
Wdfmemory hdriver;
Wdf_object_attributes attributes;
Wdf_driver_config config;
Ntstatus status = STATUS_SUCCESS;
Wdfdriver;
// Set the length of the drive environment Block
// The macro calls sizeof (...) Evaluate the length of the struct and obtain its name using the consortium sign (#).
Wdf_object_attributes_init_context_type (& attributes, driver_context );
Wdf_driver_config_init (& config, drvclass: pnpadd_sta );
Status = wdfdrivercreate (driverobject, // WDF driver object
Registrypath,
& Attributes,
& Config, // configure Parameters
& Wdfdriver );
// Obtain the driver environment Block
Pdrivedr_context pcontext = getdrivercontext (wdfdriver );
Assert (pcontext );
Pcontext-> par1 = (pvoid) This;
// Encapsulate the class object with the wdfmemory object and use it as the sub-object of the wdfdriver object
Wdf_object_attributes_init (& attributes );
Attributes. parentobject = wdfdriver;
Attributes. evtdestroycallback = drvclassdestroy;
Wdfmemorycreatepreallocated (& attributes, (pvoid) This,
Sizeof (drvclass), & hdriver );
Kdbg (dpfltr_info_level, "This = % P", this );
Return status;
}
The Driver Dynamically creates a class object in the entry function DriverEntry and immediately calls the method drvclass: DriverEntry to create the driver object and store it as the object.
What makes it possible to achieve this method is that object maintenance is automated and we don't have to worry too much about it. Everything looks perfect. The following is the implementation of the drvclassdestroy function. The WDF framework will automatically call the function when the memory object is destroyed. We will destroy the Class Object in it.
Void drvclassdestroy (in wdfobject object)
{
Pvoid pbuf = wdfmemorygetbuffer (wdfmemory) object, null );
Delete pbuf;
}
6.2.2 class methods and event Functions
The event functions in kmdf are separated: The drive objects include evtdriverdeviceadd and evtdriverunload. We will implement the former; the device object has a series of PNP/power events; and Other Object event functions, which are ignored, for details, see the code.
Event functions are called callback functions. This parameter is added after compilation, so it cannot be a callback function. Only static functions can be used, and the member functions can be called back through static functions. This is a common implementation method. Taking the evtdriverdeviceadd event function as an example, we need to define two related functions for it in the class.
Class drvclass
{
// Define a class static function, which is global and can be used as a callback function
Static ntstatus pnpadd_sta (
In wdfdriver driver,
In pwdfdevice_init deviceinit );
// Re-define the class member function, which will be called internally by the static function
Virtual ntstatus pnpadd (
In wdfdriver driver,
In pwdfdevice_init deviceinit,
Drvclass * pthis );
// Other interface functions
//......
}
You must be able to call back the member function through a static function, that is, call back the pnpadd function through pnpadd_sta. The premise is to be able to get the object pointer, because we have saved the object pointer in the Environment block of the drive object, so it is not difficult to achieve this goal. The Code is as follows:
Ntstatus drvclass: pnpadd_sta (in wdfdriver driver,
In pwdfdevice_init deviceinit)
{
// Obtain the environment Block
Pdrivedr_context pcontext = getdrivercontext (driver );
// The Environment block contains the object pointer.
Drvclass * pthis = (drvclass *) pcontext-> par1;
// Call the member function again
Return pthis-> pnpadd (driver, deviceinit );
}
All other event functions must be implemented in the same way.
6.2.3 kmdf driver implementation
In fact, the above content has always been explained around kmdf. The DriverEntry member function in drvclass has already been explained. Now let's see how to define the real entry function.
Extern "C" ntstatus DriverEntry (
In pdriver_object driverobject,
In punicode_string registrypath
)
{
// Dynamically create an object. This step will be modified later.
Drvclass * mydriver = new (nonpagedpool, 'cy01') drvclass ();
If (mydriver = NULL) return status_unsuccessful;
Return mydriver-> DriverEntry (driverobject, registrypath );
}
The driver was so clean that it quickly moved closer to the class we defined at the beginning of loading. As for the 1st-line code to dynamically create an object, the current implementation is complete, but will be modified later to support polymorphism.
6.2.4 WDM driver implementation
If the WDM method is used for class encapsulation, for non-PNP class drivers, you can create a control device object in the entry function and save the class object in the device extension of the device object; for PNP drivers, class objects should be created when the device stack is created in the adddevice function, and stored in the device extensions of functional device objects. I will take the previous article as an example to briefly introduce the implementation. Wdmclass sample project. The reader can easily expand the driver with more comprehensive functions based on the code.
The specific encapsulation process is listed here. The first is class definition. define a common distribution function as follows:
Class wdmdrvclass {
Public:
Static ntstatus dispatchfunc_sta (
Device_object device,
Pirp );
Virtual ntstatus dispatchfunc (
Device_object device,
Pirp );
// Others ......
};
Similarly, a static function and a class member function are defined. A static function calls a member function through an object pointer. The entry function should be defined as follows:
Typedef struct {
Wdmdrvclass pthis;
//......
} Device_extension;
Ntstatus DriverEntry (pdriver_object driver,
Punicode_string register)
{
// Create a dynamic object
Wdmdrvclass * pdrv = new (nonpagedpool, 'samp') wdmdrvclass ();
// Set the distribution function, all pointing to dispatchfunc_sta
For (INT I = 0; I
Driver-> dispatchfunction [I] = pdrv-> dispatchfunc_sta;
}
// Create a control device object and create a device extension area
Iocreatedeviceobject (..., sizeof (device_extension ));
// Save the object pointer to the device extension
Device_extension * pcontext = (device_extension *) deviceobject-> deviceextension;
Pcontext-> pthis = pdrv;
Return STATUS_SUCCESS;
}
After everything is ready, let's take a look at how to implement dispatchfunc_sta. As we know, the first parameter of all driver distribution functions is always the device object, which we created. Through it, we can always get object pointers in static functions. The following is the implementation of the dispatchfunc_sta function.
Ntstatus wdmdrvclass: dispatchfunc_sta (
Device_object device, pirp)
{
Pdevice_extension pcontext = device-> deviceextension;
Wdmdrv pthis = pcontext-> pthis;
Return pthis-> dispatchfunc (device, IRP );
}
Similar to the preceding kmdf implementation, for more details, see the project code.