这个例子是从《windows驱动开发技术详解》的光盘上copy的,我只是自己稍微改了一下。
入口函数DriverEntry
#pragma INITCODE extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) { KdPrint(("Enter DriverEntry\n")); pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = pDriverObject->MajorFunction[IRP_MJ_CREATE] = pDriverObject->MajorFunction[IRP_MJ_READ] = pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine; pDriverObject->DriverUnload = HelloWDMUnload; KdPrint(("Leave DriverEntry\n")); return STATUS_SUCCESS; }
提供4个派遣函数:
HelloWDMAddDevice
HelloWDMPnp
HelloWDMDispatchRoutine
HelloWDMUnload
在DriverEntry里面简单的设置一下。
AddDevice函数
AddDevice函数是WDM驱动特有的,NT驱动没有,这也是主要的区别之一。先给出代码:
#pragma PAGEDCODE NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject) //DriverObject就是指向本驱动程序的一个对象,是由PNP管理器传递进来的。 //PhysicalDeviceObject是PNP管理器传递进来的底层驱动设备对象,这个东西在NT驱动中是没有的。通常称之为PDO,确切的说是由总线驱动创建的。 { PAGED_CODE(); KdPrint(("Enter HelloWDMAddDevice\n")); NTSTATUS status; PDEVICE_OBJECT fdo; UNICODE_STRING devName; RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//设备名称,设备名称只能被内核模式下的其他驱动所识别。 //创建FDO(Function Device Object) status = IoCreateDevice( DriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo); if( !NT_SUCCESS(status)) return status; PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension; pdx->fdo = fdo; //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。 pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject); UNICODE_STRING symLinkName; //创建链接符号,这样用户模式的应用就可以访问此设备。内核模式下,符号链接是以\??\开头的(或者\DosDevices\)。用户模式下则是\\.\开头。 //这里就可以在用户模式下用\\.\HelloWDM来访问本设备。 RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM"); pdx->ustrDeviceName = devName; pdx->ustrSymLinkName = symLinkName; status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName); if( !NT_SUCCESS(status)) { IoDeleteSymbolicLink(&pdx->ustrSymLinkName); status = IoCreateSymbolicLink(&symLinkName,&devName); if( !NT_SUCCESS(status)) { return status; } } fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定义为“缓冲内存设备” fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保证设备初始化完毕,必须的。 KdPrint(("Leave HelloWDMAddDevice\n")); return STATUS_SUCCESS; }
IoCreateDevice有个设备类型参数,这里使用FILE_DEVICE_UNKNOWN。Windows已经预先定义了一些常见的设备类型,如果驱动设备并不在这些类型里面,有两种办法:
1. 使用FILE_DEVICE_UNKNOWN;
2. 使用>=0x8000(32768 - 65535)的值
详见:http://msdn.microsoft.com/en-us/library/windows/hardware/ff563821(v=vs.85).aspx
在DriverEntry函数里面通过pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;设置AddDevice的例程函数。我画了一个简单的图来描述AddDevice基本流程:
AddDevice函数有2个参数:DriverObject和PDO。
DriverObject就是当前驱动程序的一个实例,PDO是物理设备对象(由总线驱动创建)。
AddDevice函数主要工作就是创建一个功能设备对象FDO,然后附加在传递进来的PDO上面。
PNP IRP处理函数
WDM支持PNP(即插即用),这也是不同于NT驱动的一个主要特征。所有WDM驱动都需要设置PNP IRP的派遣函数。如:
pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
这个是在入口函数DriverEntry里面设置的。
看具体代码:
#pragma PAGEDCODE NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HelloWDMPnp\n")); NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice里面创建的。 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//获取irp信息 switch (stack->MinorFunction)//根据PNP irp的minor function code来调用相应的处理函数。 { case IRP_MN_REMOVE_DEVICE: status = HandleRemoveDevice(pdx, Irp); break; default: status = DefaultPnpHandler(pdx, Irp); break; } KdPrint(("Leave HelloWDMPnp\n")); return status; }
Pnp派遣函数有2个参数:fdo和Irp。
fdo就是由AddDevice函数创建的那个功能设备对象,Irp是I/O管理器传进来的一个包(I/O Request Packet)。
这个例子里面简单处理了IRP_MN_REMOVE_DEVICE这个minor function code,其他所有的minor function code统统用一个函数来处理DefaultPnpHandler。
先看一下IRP_MN_REMOVE_DEVICE的处理函数:
#pragma PAGEDCODE NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HandleRemoveDevice\n")); //设置IRP的完成状态。 Irp->IoStatus.Status = STATUS_SUCCESS; //将IRP请求向底层驱动转发。 NTSTATUS status = DefaultPnpHandler(pdx, Irp); //删除符号链接 IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName); //调用IoDetachDevice()把fdo从设备栈中脱开: if (pdx->NextStackDevice) IoDetachDevice(pdx->NextStackDevice); //删除fdo: IoDeleteDevice(pdx->fdo); KdPrint(("Leave HandleRemoveDevice\n")); return status; }
里面做了一些简单的删除工作: 设置IRP完成状态 -> 将IRP请求向下一层驱动转发 -> 删除符号链接 -> 删除功能设备对象(FDO)。
再看一些DefaultPnpHandler函数:
#pragma PAGEDCODE NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp) { PAGED_CODE(); KdPrint(("Enter DefaultPnpHandler\n")); IoSkipCurrentIrpStackLocation(Irp); KdPrint(("Leave DefaultPnpHandler\n")); return IoCallDriver(pdx->NextStackDevice, Irp);//将irp传递给下层驱动 }
啥也没做,就是跳过本层堆栈,直接将irp传递到下层驱动。
IoSkipCurrentIrpStackLocation:修改IRP的IO_STACK_LOCATION,使下层驱动可以获得跟本层驱动一样的IRP。
MSDN解释:
The IoSkipCurrentIrpStackLocation macro modifies the system'sIO_STACK_LOCATION array pointer, so that when the current driver calls the next-lower driver, that driver receives the sameIO_STACK_LOCATION structure that the current driver received.
IoCallDriver: 将irp传递给下层驱动设备对象。
现在可以看到,这个驱动程序例子只是处理了Remove Device,对于其他所有PNP请求,只是简单的传递给下层驱动处理。
DriverUnload函数
因为IRP_MN_REMOVE_DEVICE的函数里面已经处理了设备删除,DriverUnload函数里面不需要处理什么事情了,只是简单打印一些log。
DriverEntry函数里面的代码,简单设置一下派遣函数,如:
pDriverObject->DriverUnload = HelloWDMUnload;
HelloWDMUnload的实现:
#pragma PAGEDCODE void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject) { PAGED_CODE(); KdPrint(("Enter HelloWDMUnload\n")); KdPrint(("Leave HelloWDMUnload\n")); }
啥都没做,就是打印log。
最后剩下一个函数是一个派遣函数,处理CREATE,READ, WRITE等irp。
DispatchRoutine
看DriverEntry里面的几行代码:
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = pDriverObject->MajorFunction[IRP_MJ_CREATE] = pDriverObject->MajorFunction[IRP_MJ_READ] = pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
这个驱动例子里面,将DEVICE_CONTROL, CREATE, READ 和WRITE的IRP用同一个派遣函数来处理。
#pragma PAGEDCODE NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HelloWDMDispatchRoutine\n")); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); //打印IRP的major function code和minor function code。 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", stack->MajorFunction, stack->MinorFunction, code)); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; // no bytes xfered IoCompleteRequest( Irp, IO_NO_INCREMENT ); KdPrint(("Leave HelloWDMDispatchRoutine\n")); return STATUS_SUCCESS; }
HelloWDMDispatchRoutine是一个派遣函数,它有2个参数:fdo和irp(跟PNP处理函数一样)
这里只做了2个事情:
1. 打印一些log
2. 设置irp完成状态,调用IoCompleteRequest函数来完成这个irp。不需要往下层驱动传irp了,因为这个irp已经完成了。
好了,代码基本介绍完毕,看一下完整代码:
/************************************************************************ * 文件名称:HelloWDM.cpp * 作 者:张帆 * 完成日期:2007-11-1 *************************************************************************/ #include "HelloWDM.h" /************************************************************************ * 函数名称:DriverEntry * 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象 * 参数列表: pDriverObject:从I/O管理器中传进来的驱动对象 pRegistryPath:驱动程序在注册表的中的路径 * 返回 值:返回初始化驱动状态 *************************************************************************/ #pragma INITCODE extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) { KdPrint(("Enter DriverEntry\n")); pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = pDriverObject->MajorFunction[IRP_MJ_CREATE] = pDriverObject->MajorFunction[IRP_MJ_READ] = pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine; pDriverObject->DriverUnload = HelloWDMUnload; KdPrint(("Leave DriverEntry\n")); return STATUS_SUCCESS; } /************************************************************************ * 函数名称:HelloWDMAddDevice * 功能描述:添加新设备 * 参数列表: DriverObject:从I/O管理器中传进来的驱动对象 PhysicalDeviceObject:从I/O管理器中传进来的物理设备对象 * 返回 值:返回添加新设备状态 *************************************************************************/ #pragma PAGEDCODE NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject) //DriverObject就是指向本驱动程序的一个对象,是由PNP管理器传递进来的。 //PhysicalDeviceObject是PNP管理器传递进来的底层驱动设备对象,这个东西在NT驱动中是没有的。通常称之为PDO,确切的说是由总线驱动创建的。 { PAGED_CODE(); KdPrint(("Enter HelloWDMAddDevice\n")); NTSTATUS status; PDEVICE_OBJECT fdo; UNICODE_STRING devName; RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//设备名称,设备名称只能被内核模式下的其他驱动所识别。 //创建FDO(Function Device Object) status = IoCreateDevice( DriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo); if( !NT_SUCCESS(status)) return status; PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension; pdx->fdo = fdo; //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。 pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject); UNICODE_STRING symLinkName; //创建链接符号,这样用户模式的应用就可以访问此设备。内核模式下,符号链接是以\??\开头的(或者\DosDevices\)。用户模式下则是\\.\开头。 //这里就可以在用户模式下用\\.\HelloWDM来访问本设备。 RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM"); pdx->ustrDeviceName = devName; pdx->ustrSymLinkName = symLinkName; status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName); if( !NT_SUCCESS(status)) { IoDeleteSymbolicLink(&pdx->ustrSymLinkName); status = IoCreateSymbolicLink(&symLinkName,&devName); if( !NT_SUCCESS(status)) { return status; } } fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定义为“缓冲内存设备” fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保证设备初始化完毕,必须的。 KdPrint(("Leave HelloWDMAddDevice\n")); return STATUS_SUCCESS; } /************************************************************************ * 函数名称:DefaultPnpHandler * 功能描述:对PNP IRP进行缺省处理 * 参数列表: pdx:设备对象的扩展 Irp:从IO请求包 * 返回 值:返回状态 *************************************************************************/ #pragma PAGEDCODE NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp) { PAGED_CODE(); KdPrint(("Enter DefaultPnpHandler\n")); IoSkipCurrentIrpStackLocation(Irp); KdPrint(("Leave DefaultPnpHandler\n")); return IoCallDriver(pdx->NextStackDevice, Irp);//将irp传递给下层驱动 } /************************************************************************ * 函数名称:HandleRemoveDevice * 功能描述:对IRP_MN_REMOVE_DEVICE IRP进行处理 * 参数列表: fdo:功能设备对象 Irp:从IO请求包 * 返回 值:返回状态 *************************************************************************/ #pragma PAGEDCODE NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HandleRemoveDevice\n")); //设置IRP的完成状态。 Irp->IoStatus.Status = STATUS_SUCCESS; //将IRP请求向底层驱动转发。 NTSTATUS status = DefaultPnpHandler(pdx, Irp); //删除符号链接 IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName); //调用IoDetachDevice()把fdo从设备栈中脱开: if (pdx->NextStackDevice) IoDetachDevice(pdx->NextStackDevice); //删除fdo: IoDeleteDevice(pdx->fdo); KdPrint(("Leave HandleRemoveDevice\n")); return status; } /************************************************************************ * 函数名称:HelloWDMPnp * 功能描述:对即插即用IRP进行处理 * 参数列表: fdo:功能设备对象 Irp:从IO请求包 * 返回 值:返回状态 *************************************************************************/ #pragma PAGEDCODE NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HelloWDMPnp\n")); NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice里面创建的。 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//获取irp信息 switch (stack->MinorFunction)//根据PNP irp的minor function code来调用相应的处理函数。 { case IRP_MN_REMOVE_DEVICE: status = HandleRemoveDevice(pdx, Irp); break; default: status = DefaultPnpHandler(pdx, Irp); break; } KdPrint(("Leave HelloWDMPnp\n")); return status; } /************************************************************************ * 函数名称:HelloWDMDispatchRoutine * 功能描述:对缺省IRP进行处理 * 参数列表: fdo:功能设备对象 Irp:从IO请求包 * 返回 值:返回状态 *************************************************************************/ #pragma PAGEDCODE NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HelloWDMDispatchRoutine\n")); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); //打印IRP的major function code和minor function code。 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", stack->MajorFunction, stack->MinorFunction, code)); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; // no bytes xfered IoCompleteRequest( Irp, IO_NO_INCREMENT ); KdPrint(("Leave HelloWDMDispatchRoutine\n")); return STATUS_SUCCESS; } /************************************************************************ * 函数名称:HelloWDMUnload * 功能描述:负责驱动程序的卸载操作 * 参数列表: DriverObject:驱动对象 * 返回 值:返回状态 *************************************************************************/ #pragma PAGEDCODE void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject) { PAGED_CODE(); KdPrint(("Enter HelloWDMUnload\n")); KdPrint(("Leave HelloWDMUnload\n")); }
接下来就是如何安装了。
WDM驱动安装
WDM驱动安装需要一个inf文件,这里就直接引用《windows驱动开发技术详解》里面的,没有做任何改动,直接贴出来:
;; The Win2K DDK documentation contains an excellent INF reference. ;--------- Version Section --------------------------------------------------- [Version] Signature="$CHICAGO$" Provider=Zhangfan_Device DriverVer=11/1/2007,3.0.0.3 ; If device fits one of the standard classes, use the name and GUID here, ; otherwise create your own device class and GUID as this example shows. Class=ZhangfanDevice ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0} ;--------- SourceDiskNames and SourceDiskFiles Section ----------------------- ; These sections identify source disks and files for installation. They are ; shown here as an example, but commented out. [SourceDisksNames] 1 = "HelloWDM",Disk1,, [SourceDisksFiles] HelloWDM.sys = 1,MyDriver_Check, ;--------- ClassInstall/ClassInstall32 Section ------------------------------- ; Not necessary if using a standard class ; 9X Style [ClassInstall] Addreg=Class_AddReg ; NT Style [ClassInstall32] Addreg=Class_AddReg [Class_AddReg] HKR,,,,%DeviceClassName% HKR,,Icon,,"-5" ;--------- DestinationDirs Section ------------------------------------------- [DestinationDirs] YouMark_Files_Driver = 10,System32\Drivers ;--------- Manufacturer and Models Sections ---------------------------------- [Manufacturer] %MfgName%=Mfg0 [Mfg0] ; PCI hardware Ids use the form ; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd ;改成你自己的ID %DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999 ;---------- DDInstall Sections ----------------------------------------------- ; --------- Windows 9X ----------------- ; Experimentation has shown that DDInstall root names greater than 19 characters ; cause problems in Windows 98 [YouMark_DDI] CopyFiles=YouMark_Files_Driver AddReg=YouMark_9X_AddReg [YouMark_9X_AddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,HelloWDM.sys HKR, "Parameters", "BreakOnEntry", 0x00010001, 0 ; --------- Windows NT ----------------- [YouMark_DDI.NT] CopyFiles=YouMark_Files_Driver AddReg=YouMark_NT_AddReg [YouMark_DDI.NT.Services] Addservice = HelloWDM, 0x00000002, YouMark_AddService [YouMark_AddService] DisplayName = %SvcDesc% ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %10%\System32\Drivers\HelloWDM.sys [YouMark_NT_AddReg] HKLM, "System\CurrentControlSet\Services\HelloWDM\Parameters",\ "BreakOnEntry", 0x00010001, 0 ; --------- Files (common) ------------- [YouMark_Files_Driver] HelloWDM.sys ;--------- Strings Section --------------------------------------------------- [Strings] ProviderName="Zhangfan." MfgName="Zhangfan Soft" DeviceDesc="Hello World WDM!" DeviceClassName="Zhangfan_Device" SvcDesc="Zhangfan"
有关里面的说明,以后再讲。
有了inf后,就可以通过控制面板->安装硬件来安装这个驱动。给出最后一个截图,其他略。
安装成功后,可以在设备管理里面看到:
这样就安装成功了。
搞了半天,这个驱动有啥用呢?当然我们可以写一个用户模式的测试程序。
调用驱动(用户模式和内核模式通信)
写个很简单的测试例子:
// TestWDMDriver.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #define DEVICE_NAME L"\\\\.\\HelloWDM" int _tmain(int argc, _TCHAR* argv[]) { HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hDevice != INVALID_HANDLE_VALUE) { for(int i = 0; i < 10; i++) { DWORD RetBytes = 0; BOOL b = DeviceIoControl(hDevice, 0xAB, NULL, 0, NULL, 0, &RetBytes, NULL); printf("Test number %d, DeviceIoControl result: %d, byte: %d\n", i + 1, b, RetBytes); Sleep(1000); } CloseHandle(hDevice); } else printf("CreateFile failed, err: %x\n", GetLastError()); return 0; }
相当的简单,通过CreateFile函数来打开HelloWDM驱动,通过设备名字\\.\HelloWDM。这个名字对应驱动的AddDevice函数里面设置的符号链接\\DosDevices\\HelloWDM。
这里有必要讲一下驱动的几个名字概念:
1. 首先是设备名称,就是IoCreateDevice里面用到的那个名字。这个名字只能被内核模式的程序(如其他驱动)所识别。用户模式的程序是不知道这个名字的。
2. 符号链接,驱动程序里面可以为某个设备设置符号链接,以\??\开头(或者\DosDevices\),这样用户模式的程序就可以通过这个符号链接来访问这个设备。
3. 用户模式程序里面的设备名称,以\\.\开头
比如这个例子里面内核模式的设备名称是"\Device\MyWDMDevice",符号链接是"\DosDevices\HelloWDM",然后用户模式程序通过"\\.\HelloWDM"访问这个设备。
当CreateFile成功打开这个设备后,就可以操作这个设备了。测试例子里面简单往这个设备调用了10次DeviceIoControl函数(也就是发送一个IRP_MJ_DEVICE_CONTROL类型的IRP)。
运行测试程序,在debugview里面可以看到:
哈哈,驱动程序成功地收到了来自测试程序(用户模式)的信息。
这里只是简单打印一下信息,
Major function code 是e(16进制),看一下WDM.H
#define IRP_MJ_CREATE 0x00 #define IRP_MJ_CREATE_NAMED_PIPE 0x01 #define IRP_MJ_CLOSE 0x02 #define IRP_MJ_READ 0x03 #define IRP_MJ_WRITE 0x04 #define IRP_MJ_QUERY_INFORMATION 0x05 #define IRP_MJ_SET_INFORMATION 0x06 #define IRP_MJ_QUERY_EA 0x07 #define IRP_MJ_SET_EA 0x08 #define IRP_MJ_FLUSH_BUFFERS 0x09 #define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a #define IRP_MJ_SET_VOLUME_INFORMATION 0x0b #define IRP_MJ_DIRECTORY_CONTROL 0x0c #define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d #define IRP_MJ_DEVICE_CONTROL 0x0e
确实0x0e对应的是IRP_MJ_DEVICE_CONTROL。
然后测试例子里面发送了一个0xAB的控制码给驱动,那么驱动里面通过stack->Parameters.DeviceIoControl.IoControlCode得到的控制码也是0xab,看上面的debugview信息。
这样就大功告成了。首先我们写了一个简单的WDM驱动,然后安装,之后在用户模式里面访问这个驱动。这也就是驱动的一般流程。
当然真正的驱动程序远没有这个例子简单。我个人对于驱动大概也就是小学生的水平,需要继续学习,研究。
我将完整代码打包上传了,有兴趣可以下载:http://download.csdn.net/detail/zj510/4794275
(用DDK编译驱动,简单调用build命令即可。用VS2008编译用户模式的测试程序)