网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 操作系统 > Windows >

使用VHF框架实现一个虚拟HID键盘

时间:2025-09-01 18:32

人气:

作者:admin

标签:

导读:前几天我通过改造微软的vhidmini2这个驱动示例,写了一个umdf的虚拟hid键盘,然后我发现,微软还提供了一个叫Virtual Hid Framework(VHF)的框架,专门用来实现虚拟hid设备,在kmdf和umdf上都...

前几天我通过改造微软的vhidmini2这个驱动示例,写了一个umdf的虚拟hid键盘,然后我发现,微软还提供了一个叫Virtual Hid Framework(VHF)的框架,专门用来实现虚拟hid设备,在kmdf和umdf上都支持(文档这么说的),所以就想着用VHF来重写一下上次的那个虚拟hid键盘。

使用VHF开发的驱动程序叫做源驱动程序,源驱动程序的作用是控制VHF设备对象的生命周期,以及为VHF设备对象提供数据。下面这张官方文档中的设备树显示了它们之间的层次关系。
img
绿色框的FDO指的是源驱动程序,就是我们要开发的部分,开发时需要引用Vhfkm.lib(在umdf中是Vhfum.lib)来使用vhf提供的api。PDO是物理设备对象,通常是上级设备的FDO枚举出来的设备,对于虚拟设备来说,一般是通过devgen等方式生成的设备。PDO一般会显示在设备管理器中,未安装驱动时显示为Unknown Device,源驱动程序就是安装在这个设备上的。Vhf.sys是VHF框架的核心,作为LowerFilter安装在源驱动程序上,过滤PDO到源驱动程序之间的请求,这些请求一般是设备生命周期相关的请求,例如PNP事件等,与HID功能无关。Vhf.sys会枚举出一个PDO来,这个是实现虚拟HID设备功能的PDO,系统会在它上面安装HidClass的驱动。

VHF之于虚拟hid设备的开发,就像WPF之于桌面应用开发,虽然实现的自由度会稍微受限,但确实方便很多。使用VHF,不需要自己去处理复杂的IRP请求,其缓冲策略也有默认的实现,只需要设置好几个回调函数就行了,给我的感觉真的是像开发WPF应用一样。

虽然VHF用起来很方便,但是官方文档较少,示例代码也不完整,所以用起来还是遇到不少困难。我最初还是想用umdf的驱动来实现虚拟hid键盘,但实在是调不通,文档和示例代码大多是基于kmdf的,搞不懂到底是哪里有问题,所以就先实现了一个kmdf的。

使用VHF的步骤非常简单,就分为两步:1.编写初始化代码;2.编写处理请求的回调函数。

下面先介绍一下可能会用到的主要函数和数据结构,然后再说明如何编写实例代码。

初始化需要依次使用VHF_CONFIG_INIT、VhfCreate、VhfStart三个函数,这三个函数是不是光看名称就很容易理解?

1.1 VHF_CONFIG_INIT

FORCEINLINE
VOID
VHF_CONFIG_INIT(
    _Out_
        PVHF_CONFIG     Config,
#ifdef _KERNEL_MODE
    _In_
        PDEVICE_OBJECT  DeviceObject,
#else
    _In_
        HANDLE          FileHandle,
#endif
    _In_
        USHORT          ReportDescriptorLength,
    _In_reads_bytes_(ReportDescriptorLength)
        PUCHAR          ReportDescriptor    
    )

VHF_CONFIG_INIT函数的作用是初始化一个VHF_CONFIG的结构体,VHF_CONFIG结构体用来指定VHF框架对象的一些属性,例如PID、VID、回调函数指针等。

DeviceObject指定一个WDM设备对象与VHF关联,通常就是当前的设备对象。在kdmf中,可以通过WdfDeviceWdmGetDeviceObject来获取与WDF设备对象关联的WDM设备对象。

1.2 VhfCreate

NTSTATUS VhfCreate(
  [in]  PVHF_CONFIG VhfConfig,
  [out] VHFHANDLE   *VhfHandle
);

VhfCreate函数的作用是使用刚刚初始化的VHF_CONFIG指定的配置,去创建一个VHF设备对象,调用成功的话,VhfHandle就是新创建的VHF设备对象的句柄。

1.3 VhfStart

NTSTATUS VhfStart(
  [in] VHFHANDLE VhfHandle
);

VhfStart函数的作用就是启动刚刚创建的VHF设备对象。

1.4 VhfDelete

VOID VhfDelete(
  [in] VHFHANDLE VhfHandle,
  [in] BOOLEAN   Wait
);

在设备或驱动卸载之前,需要调用VhfDelete方法删除掉VHF设备对象。未正常删除VHF设备对象的话,系统会提示设备已更改,需要重启系统。

2.1 EVT_VHF_ASYNC_OPERATION

源驱动程序可以支持这些异步请求:GetFeature、 SetFeature、 WriteReport、 GetInputReport。在VHF_CONFIG结构体中设置相应的回调函数:EvtVhfAsyncOperationGetFeature、EvtVhfAsyncOperationSetFeature、EvtVhfAsyncOperationWriteReport、EvtVhfAsyncOperationGetInputReport,然后在VHF处理这些请求时,就会调用这些回调。

这些回调的类型都是EVT_VHF_ASYNC_OPERATION,定义如下:

EVT_VHF_ASYNC_OPERATION EvtVhfAsyncOperation;

VOID EvtVhfAsyncOperation(
  [in]           PVOID VhfClientContext,
  [in]           VHFOPERATIONHANDLE VhfOperationHandle,
  [in, optional] PVOID VhfOperationContext,
  [in]           PHID_XFER_PACKET HidTransferPacket
)
{...}

VhfClientContext是回调的上下文参数,是在初始化时通过VHF_CONFIG结构体设置的。VhfOperationHandle是这次异步操作的句柄,通常用于设置异步操作的结果。HidTransferPacket是请求报告的数据包。

2.2 VhfAsyncOperationComplete

NTSTATUS VhfAsyncOperationComplete(
  [in] VHFOPERATIONHANDLE VhfOperationHandle,
  [in] NTSTATUS           CompletionStatus
);

当源驱动程序处理完异步请求之后,必须要用回调传入的VhfOperationHandle参数,调用VhfAsyncOperationComplete函数来设置此次异步请求的结果。

2.3 VhfReadReportSubmit

NTSTATUS VhfReadReportSubmit(
  [in] VHFHANDLE        VhfHandle,
  [in] PHID_XFER_PACKET HidTransferPacket
);

源驱动程序可以通过VhfReadReportSubmit函数向VHF提交一个输入报告,然后由VHF决定何时将该报告提交给系统。

2.4 EVT_VHF_READY_FOR_NEXT_READ_REPORT

源驱动程序也可以自己决定何时将输入报告提交给系统。可以通过VHF_CONFIG的EvtVhfReadyForNextReadReport字段来设置一个EVT_VHF_READY_FOR_NEXT_READ_REPORT类型的回调,它的定义如下:

EVT_VHF_READY_FOR_NEXT_READ_REPORT EvtVhfReadyForNextReadReport;

VOID EvtVhfReadyForNextReadReport(
  [in] PVOID VhfClientContext
)
{...}

如果设置了EvtVhfReadyForNextReadReport回调,则当VHF准备好将缓冲区提交给系统时调用这个回调,然后由源驱动程序决定何时向缓冲区中填充输入报告。

源驱动程序仍然通过调用VhfReadReportSubmit来填充输入报告,一旦调用VhfReadReportSubmit后,VHF会尽快提交缓冲区,然后,直到下一次VHF调用EvtVhfReadyForNextReadReport回调后,源驱动程序才可以再次提交输入报告。

如果实现的是键盘、鼠标、触摸这类输入设备的话,一般而言,在启动VHF设备对象后EvtVhfReadyForNextReadReport会立即被调用。

还是以虚拟HID键盘为例,下面会从项目创建开始,完整演示一下用VHF框架实现虚拟HID设备的过程。

3.1 项目创建

这里通过VS2022来创建项目,在创建项目前需要先完整地安装好WDK,WDK怎么安装官方有详细的文档,这里就不讲了。

项目模板就选择Kernel Mode Driver(KMDF)或者Kernel Mode Driver, Empty(KMDF),如果选择空模板的话,就要自己实现DriverEntry等函数,这里我选了Kernel Mode Driver(KMDF)模板。
img

项目创建后,右键项目,点击属性,在项目属性面板中,选择链接器-输入,在附加依赖项中添加vhfkm.lib,然后在头文件中包含vhf.h
img

3.2 修改INF文件

Vhf.sys需要安装为源驱动程序的Lower Filter驱动,这可以通过INF文件来指定(仅限通过INF文件安装的情况)。模板中包含默认的INF文件,我们需要在INF文件的DDInstall.HW部分中添加一个AddReg指令(如果没有DDInstall.HW部分则添加一个),再添加一个对应的AddReg部分。类似下面这样:

[vhfkeyboardkm_Device.NT.HW]
AddReg = vhfkeyboardkm_Device.NT.AddReg

[vhfkeyboardkm_Device.NT.AddReg]
HKR,,"LowerFilters",0x00010000,"vhf"

3.3 初始化VHF设备对象的代码

模板实现的是一个PNP样式的驱动程序,它在EvtDriverDeviceAdd事件中完成WDF设备对象的创建和初始化,我们在它创建WDF设备对象后初始化VHF设备对象。模板的代码如下:

NTSTATUS
vhfkeyboardkmEvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = vhfkeyboardkmCreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

NTSTATUS
vhfkeyboardkmCreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_OBJECT_ATTRIBUTES deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;
    VHF_CONFIG vhfConfig;
    PDEVICE_OBJECT pdo;

    PAGED_CODE();

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
    deviceAttributes.EvtCleanupCallback = EVT_CONTEXT_CLEANUP;

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {

        deviceContext = DeviceGetContext(device); 

        RtlZeroMemory(deviceContext, sizeof(DEVICE_CONTEXT));

        //status = WdfDeviceCreateDeviceInterface(
        //    device,
        //    &GUID_DEVINTERFACE_vhfkeyboardkm,
        //    NULL // ReferenceString
        //    );

        //if (NT_SUCCESS(status)) {
        //    //
        //    // Initialize the I/O Package and any Queues
        //    //
        //    status = vhfkeyboardkmQueueInitialize(device);
        / 
温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信