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;
}
时间: 2024-10-05 15:47:45

4.3 键盘过滤的请求处理的相关文章

4.1 技术原理 &amp; 4.2 键盘过滤框架

4.1 预备知识 符号链接:符号链接其实就是一个“别名”.可以用一个不同的名字来代表一个设备对象(实际上),符号链接可以指向任何有名字的对象. ZwCreateFile是很重要的函数.同名的函数实际上有两个:一个在内核中,一个在应用层.所以在应用程序中直接调用CreateFile,就可以引发对这个函数的调用. 它不但可以打开文件,而且可以打开设备对象(返回得到一个类似文件句柄的句柄).所以后面会常常看见应用程序为了交互内核而调用这个函数,这个函数最终调用NtCreateFile. PDO:Phs

键盘过滤驱动

在笔者接触驱动到如今以来一以后大半个月的时间,从中让我深深的体会到了万事开头难,以及学习持之以恒的重要性.笔者也是个驱动新人,開始接触驱动的时候看着张帆的<Windows驱动开发技术具体解释>讲的挺细,对新手来说是个不错的学习资料,可是更重要的还是自己要多动手练习,笔者在学习到同步操作的相关知识的时候,实在是看天书.最后还是放弃了学习本书.再找了本楚狂人的资料学习,感觉本书对新手来说还是比較吃力的,当中笔者就是这样,非常多知识点不是非常明确,仅仅能凭借自己的感觉去做,只是造成的后果就是无情的蓝

键盘过滤第一个例子ctrl2cap(4.1~4.4)汇总,测试

完整源代码 /// /// @file ctrl2cap.c /// @author wowocock /// @date 2009-1-27 /// #include <wdm.h> #include <ntddkbd.h> #include "ctrl2cap.h" typedef struct _C2P_DEV_EXT { // 这个结构的大小 ULONG NodeSize; // 过滤设备对象 PDEVICE_OBJECT pFilterDeviceOb

键盘过滤驱动源码

#include <wdm.h> // Kbdclass驱动的名字 #define KBD_DRIVER_NAME L"\\Driver\\Kbdclass" typedef struct _C2P_DEV_EXT { // 这个结构的大小 ULONG NodeSize; // 过滤设备对象 PDEVICE_OBJECT pFilterDeviceObject; // 同时调用时的保护锁 KSPIN_LOCK IoRequestsSpinLock; // 进程间同步处理 K

内核编程键盘过滤几种方法思路整理

第一种:绑定kbdcalss驱动对象 kbdclass类驱动对象是键盘的最上层的驱动对象,对它的分发函数进行处理,则不用考虑底层的兼容性问题. 思路:首先使用ObReferenceObjectByName函数打开kbdclass驱动对象,然后使用DeviceObject指针和NextDevice指针遍历kbdclass驱动对象下所有的设备对象,每遍历一个则创建一个设备对象附加在其上,这样我们编写的驱动对象的分发函数,即可处理kbdclass类驱动对象的IRP.如图所示 第二种:直接替换kbdcl

Windows驱动过滤--kdbclass过滤,寒江独钓加强版

寒江独钓键盘过滤,修改了下,过滤QQ密码写入buff,有 回车 或者buff满写入文件,因为irp完成,irp对应的内存快回收,所以用全局缓冲区.开启一个线程写入,开始打算用队例一个一个处理irp的,但是发现那样比较缓慢,只好这样了..创建进程回调代码加几行就行,这里没写,因为代码丢失了,算是个大概的代码吧.给初学的分享下.有错指出,谢谢. 前辈们不要见笑了. struct.h /************************************/ //author:DuanYueming

Keyboard Input WDF Filter Driver (Kbfiltr)源代码学习笔记

WDF版本键盘过滤驱动——Kbfiltr The Kbdfltr sample is an example of a keyboard input filter driver. This sample is WDF version of the original WDM filter driver sample. The WDM version of this sample has been deprecated. This is an upper device filter driver sa

寒江独钓:键盘的过滤 学习笔记

先来名词热身: 一.符号链接:其实就是一个别名.可以用一个不同的名字来代表一个设备对象 二.PDO:是物理设备对象,可以理解为是设备栈最下面的那个设备对象. 函数介绍: 内核中:ZwCreateFile是很重要的函数,不但可以打开文件,还可以打开设备对象.在应用程序中跟它对应的是CreateFile函数.. 接下来是记录Windows如何获得按键,然后传递给各个应用程序. csrss这个进程里有个线程叫win32k!RawInputThread,这个线程总是调用nt!ZwReadFile来读入数

通过键盘输入一串小写字母(a~z)组成的字符串。请编写一个字符串过滤程序,若字符串中出现多个相同的字符,将非首次出现的字符过滤掉。 比如字符串“abacacde”过滤结果为“abcde”。

这是华为2013的一个机试题,会好半天才想出来,用了三个for循环,可能有点繁琐,但只要慢慢看还是好理解的, 题目: 通过键盘输入一串小写字母(a~z)组成的字符串.请编写一个字符串过滤程序,若字符串中出现多个相同的字符,将非首次出现的字符过滤掉. 比如字符串"abacacde"过滤结果为"abcde". #include<stdio.h> #include<string.h> void main() { char a[]="aba