[内核编程] 4.3 键盘过滤的请求处理
4.3 键盘过滤的请求处理
4.3.1 通常处理
(1) 最通常的处理就是直接发到真实设备,跳过虚拟设备的处理。这里和前面的串口过滤的方法一样。
NTSTATUS c2pDispatchGeneral( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { // 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备 // 的设备对象。 KdPrint(("Other Diapatch!")); IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(((PC2P_DEV_EXT) DeviceObject->DeviceExtension)->LowerDeviceObject, Irp); }
注:代码中直接使用了设备扩展。
(2) 与电源相关的IRP处理:
(a)在调用IoSkipCurrentIrpStackLocation之前,先调用PoStartNextPowerIrp。
(b)用PoCallDriver代替IoCallDriver。
//只处理主功能号为IRP_MJ_POWER的IRP NTSTATUS c2pPower( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PC2P_DEV_EXT devExt; devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension; PoStartNextPowerIrp( Irp ); IoSkipCurrentIrpStackLocation( Irp ); return PoCallDriver(devExt->LowerDeviceObject, Irp ); }
注:c2pPower只处理主功能号为IRP_MJ_POWER的IRP;而c2pDispatchGeneral处理我们不关心的所有IRP。
4.3.2 PNP处理
唯一需要处理的就是,当有一个设备被拔出时,则解除绑定,并删除过滤设备。代码的实现如下:
NTSTATUS c2pPnP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PC2P_DEV_EXT devExt; PIO_STACK_LOCATION irpStack; NTSTATUS status = STATUS_SUCCESS; KIRQL oldIrql; KEVENT event; // 获得真实设备。 devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension); irpStack = IoGetCurrentIrpStackLocation(Irp); switch (irpStack->MinorFunction) { case IRP_MN_REMOVE_DEVICE: KdPrint(("IRP_MN_REMOVE_DEVICE\n")); // 首先把请求发下去 IoSkipCurrentIrpStackLocation(Irp); IoCallDriver(devExt->LowerDeviceObject, Irp); // 然后解除绑定。 IoDetachDevice(devExt->LowerDeviceObject); // 删除我们自己生成的虚拟设备。 IoDeleteDevice(DeviceObject); status = STATUS_SUCCESS; break; default: // 对于其他类型的IRP,全部都直接下发即可。 IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->LowerDeviceObject, Irp); } return status; }
当PNP请求过来时,是没有必要担心还有未完成的IRP的。这是因为Windows系统要求卸载设备,此时Windows自己应该已经处理掉了所有未决的IRP。这是和我们自己要求卸载过滤驱动不同的地方。
4.3.3 读处理
前面章节中,见到的请求,都是处理完毕后,直接发送到下层驱动之后就不管了。但在处理键盘请求时,就不行。
改变后的处理方式:先把这个请求下发完之后,再去看这个键盘扫描码的值是多少。要完成请求,可采用以下步骤:
(1) 调用IoCopyCurrentIrpStackLocationToNext 把当前IRP栈空间拷贝到下一个栈空间(这和前面的调用IoSkiCurrentIrpStackLocation跳过当前栈空间形成对比)。
(2) 给这个IRP设置一个完成函数。完成函数的含义是:如果这个IRP完成了,系统会回调这个函数。
(3) 调用IoCallDriver把请求发送到下一个设备。
另外一个需要解决的问题就是键计数器的处理。即请求到来时,gC2pKeyCount加 1,等完成之后再减 1。
完整的读处理请求如下:
NTSTATUS c2pDispatchRead( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status = STATUS_SUCCESS; PC2P_DEV_EXT devExt; PIO_STACK_LOCATION currentIrpStack; KEVENT waitEvent; KeInitializeEvent( &waitEvent, NotificationEvent, FALSE ); if (Irp->CurrentLocation == 1) { ULONG ReturnedInformation = 0; KdPrint(("Dispatch encountered bogus current location\n")); status = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Status = status; Irp->IoStatus.Information = ReturnedInformation; IoCompleteRequest(Irp, IO_NO_INCREMENT); return(status); } // 全局变量键计数器加1 gC2pKeyCount++; // 得到设备扩展。目的是之后为了获得下一个设备的指针。 devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension; // 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。 // 剩下的任务是要等待读请求完成。 currentIrpStack = IoGetCurrentIrpStackLocation(Irp); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine( Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE ); return IoCallDriver( devExt->LowerDeviceObject, Irp ); }
4.3.4 读完成的处理
读请求完成之后,应该获得输出缓冲区,按键信息就在输出缓冲区中,全局变量gC2pKeyCount应该减 1 。
NTSTATUS c2pReadComplete( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_STACK_LOCATION IrpSp; ULONG buf_len = 0; PUCHAR buf = NULL; size_t i,numKeys; PKEYBOARD_INPUT_DATA KeyData; IrpSp = IoGetCurrentIrpStackLocation( Irp ); // 如果这个请求是成功的。很显然,如果请求失败了,这么获取 // 进一步的信息是没意义的。 if( NT_SUCCESS( Irp->IoStatus.Status ) ) { // 获得读请求完成后输出的缓冲区 buf = Irp->AssociatedIrp.SystemBuffer; KeyData = (PKEYBOARD_INPUT_DATA)buf; // 获得这个缓冲区的长度。一般的说返回值有多长都保存在 // Information中。 buf_len = Irp->IoStatus.Information; numKeys = buf_len / sizeof(KEYBOARD_INPUT_DATA); //… 这里可以做进一步的处理。我这里很简单的打印出所有的扫 // 描码。 //for(i=0;i<buf_len;++i) for(i=0;i<numKeys;++i) { //DbgPrint("ctrl2cap: %2x\r\n", buf[i]); DbgPrint("\n"); DbgPrint("numKeys : %d",numKeys); DbgPrint("ScanCode: %x ", KeyData->MakeCode ); DbgPrint("%s\n", KeyData->Flags ?"Up" : "Down" ); print_keystroke((UCHAR)KeyData->MakeCode); if( KeyData->MakeCode == CAPS_LOCK) { KeyData->MakeCode = LCONTROL; } } } gC2pKeyCount--; if( Irp->PendingReturned ) { IoMarkIrpPending( Irp ); } return Irp->IoStatus.Status; }