WDF
+ -

创建PDO

2026-06-17 1 0

代码来源于HidInjectorSample

NTSTATUS
HIDINJECTOR_CreateRawPdo(
    WDFDEVICE       Device
    )
/*++

Routine Description:

    This routine creates and initialize a PDO.

Arguments:

Return Value:

    NT Status code.

--*/
{   
    NTSTATUS                    status;
    PWDFDEVICE_INIT             pDeviceInit = NULL;
    PRAWPDO_DEVICE_CONTEXT           pdoData = NULL;
    WDFDEVICE                   hChild = NULL;
    WDF_OBJECT_ATTRIBUTES       pdoAttributes;
    WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
    WDF_IO_QUEUE_CONFIG         ioQueueConfig;
    WDFQUEUE                    queue;
    WDF_DEVICE_STATE            deviceState;
    PHID_DEVICE_CONTEXT             devExt;
    WDF_PNPPOWER_EVENT_CALLBACKS  pnpPowerCallbacks;

    DECLARE_CONST_UNICODE_STRING(deviceId,HIDINJECTOR_DEVICE_ID );
    DECLARE_CONST_UNICODE_STRING(deviceLocation,L"HID Injector Sample\0" );
    DECLARE_CONST_UNICODE_STRING(SDDL_MY_PERMISSIONS, L"D:P(A;; GA;;; SY)(A;; GA;;; BA)(A;; GA;;; WD)");
    DECLARE_UNICODE_STRING_SIZE(buffer, MAX_ID_LEN);

    KdPrint(("Entered HIDINJECTOR_CreateRawPdo\n"));

    //
    // Allocate a WDFDEVICE_INIT structure and set the properties
    // so that we can create a device object for the child.
    //
    pDeviceInit = WdfPdoInitAllocate(Device);

    if (pDeviceInit == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

    //
    // Register for power callbacks so we can register the symbolic link
    //
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = RAWPDO_EvtDeviceSelfManagedIoInit;
    WdfDeviceInitSetPnpPowerEventCallbacks(
        pDeviceInit,
        &pnpPowerCallbacks
        );

    //
    // Mark the device RAW so that the child device can be started
    // and accessed without requiring a function driver. Since we are
    // creating a RAW PDO, we must provide a class guid.
    //
    status = WdfPdoInitAssignRawDevice(pDeviceInit, &GUID_DEVCLASS_HIDINJECTOR);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    //
    // Since keyboard is secure device, we must protect ourselves from random
    // users sending ioctls and creating trouble.
    //
    status = WdfDeviceInitAssignSDDLString(pDeviceInit,
                                           &SDDL_MY_PERMISSIONS);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    //
    // Assign DeviceID - This will be reported to IRP_MN_QUERY_ID/BusQueryDeviceID
    //
    status = WdfPdoInitAssignDeviceID(pDeviceInit, &deviceId);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    //
    // We could be enumerating more than one children if the filter attaches
    // to multiple instances of keyboard, so we must provide a
    // BusQueryInstanceID. If we don't, system will throw CA bugcheck.
    //
    status =  RtlUnicodeStringPrintf(&buffer, L"%02d", InstanceNo);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    status = WdfPdoInitAssignInstanceID(pDeviceInit, &buffer);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    //
    // Provide a description about the device. This text is usually read from
    // the device. In the case of USB device, this text comes from the string
    // descriptor. This text is displayed momentarily by the PnP manager while
    // it's looking for a matching INF. If it finds one, it uses the Device
    // Description from the INF file to display in the device manager.
    // Since our device is raw device and we don't provide any hardware ID
    // to match with an INF, this text will be displayed in the device manager.
    //
    status = RtlUnicodeStringPrintf(&buffer,L"HID_Injector_Sample_%02d", InstanceNo );
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }
    InstanceNo++;

    //
    // You can call WdfPdoInitAddDeviceText multiple times, adding device
    // text for multiple locales. When the system displays the text, it
    // chooses the text that matches the current locale, if available.
    // Otherwise it will use the string for the default locale.
    // The driver can specify the driver's default locale by calling
    // WdfPdoInitSetDefaultLocale.
    //
    status = WdfPdoInitAddDeviceText(pDeviceInit,
                                        &buffer,
                                        &deviceLocation,
                                        0x409
                                        );
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    WdfPdoInitSetDefaultLocale(pDeviceInit, 0x409);

    //
    // Initialize the attributes to specify the size of PDO device extension.
    // All the state information private to the PDO will be tracked here.
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, RAWPDO_DEVICE_CONTEXT);

    //
    // Set up our queue to allow forwarding of requests to the parent
    // This is done so that the cached Keyboard Attributes can be retrieved
    //
    WdfPdoInitAllowForwardingRequestToParent(pDeviceInit);

    status = WdfDeviceCreate(&pDeviceInit, &pdoAttributes, &hChild);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }

    //
    // Get the device context.
    //
    pdoData = GetRawPdoDeviceContext(hChild);

    pdoData->InstanceNo = InstanceNo;

    //
    // Get the parent queue we will be forwarding to
    //
    devExt = GetHidDeviceContext(Device);
    pdoData->ParentQueue = devExt->RawPdoQueue;

    //
    // Configure the default queue associated with the control device object
    // to be Serial so that request passed to EvtIoDeviceControl are serialized.
    // A default queue gets all the requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching.
    //

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,
                                    WdfIoQueueDispatchSequential);

    ioQueueConfig.EvtIoWrite = HIDINJECTOR_EvtIoWriteForRawPdo;


    status = WdfIoQueueCreate(hChild,
                                        &ioQueueConfig,
                                        WDF_NO_OBJECT_ATTRIBUTES,
                                        &queue // pointer to default queue
                                        );
    if (!NT_SUCCESS(status)) {
        KdPrint( ("WdfIoQueueCreate failed 0x%x\n", status));
        goto Cleanup;
    }

    //
    // Set some properties for the child device.
    //
    WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);

    pnpCaps.Removable         = WdfTrue;
    pnpCaps.SurpriseRemovalOK = WdfTrue;
    pnpCaps.NoDisplayInUI     = WdfTrue;

    pnpCaps.Address  = InstanceNo;
    pnpCaps.UINumber = InstanceNo;

    WdfDeviceSetPnpCapabilities(hChild, &pnpCaps);

    //
    // TODO: In addition to setting NoDisplayInUI in DeviceCaps, we
    // have to do the following to hide the device. Following call
    // tells the framework to report the device state in
    // IRP_MN_QUERY_DEVICE_STATE request.
    //
    WDF_DEVICE_STATE_INIT(&deviceState);
    deviceState.DontDisplayInUI = WdfTrue;
    WdfDeviceSetDeviceState(hChild, &deviceState);

    //
    // Tell the Framework that this device will need an interface so that
    // application can find our device and talk to it.
    //
    status = WdfDeviceCreateDeviceInterface(
                 hChild,
                 &GUID_DEVINTERFACE_HIDINJECTOR,
                 NULL
             );

    if (!NT_SUCCESS (status)) {
        KdPrint( ("WdfDeviceCreateDeviceInterface failed 0x%x\n", status));
        goto Cleanup;
    }

    //
    // Add this device to the FDO's collection of children.
    // After the child device is added to the static collection successfully,
    // driver must call WdfPdoMarkMissing to get the device deleted. It
    // shouldn't delete the child device directly by calling WdfObjectDelete.
    //
    status = WdfFdoAddStaticChild(Device, hChild);
    if (!NT_SUCCESS(status)) {
        goto Cleanup;
    }
    devExt->RawPdo = hChild;


    //
    // pDeviceInit will be freed by WDF.
    //
    return STATUS_SUCCESS;

Cleanup:

    KdPrint(("HIDINJECTOR_CreateRawPdo failed %x\n", status));

    //
    // Call WdfDeviceInitFree if you encounter an error while initializing
    // a new framework device object. If you call WdfDeviceInitFree,
    // do not call WdfDeviceCreate.
    //
    if (pDeviceInit != NULL) {
        WdfDeviceInitFree(pDeviceInit);
    }

    if(hChild) {
        WdfObjectDelete(hChild);
    }

    return status;
}

这段代码是一个非常标准且完整的总线驱动创建子设备(PDO)的示例。它完美地演示了我们之前讨论的所有关键概念。

1. 分配并初始化 PDO 结构 (核心步骤)

// 1. 为子设备分配一个 WDFDEVICE_INIT 结构
pDeviceInit = WdfPdoInitAllocate(Device); 

// 2. 注册电源回调 (自托管 I/O)
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = RAWPDO_EvtDeviceSelfManagedIoInit;
WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &pnpPowerCallbacks);

// 3. 设置为 RAW 设备 (无功能驱动)
status = WdfPdoInitAssignRawDevice(pDeviceInit, &GUID_DEVCLASS_HIDINJECTOR);
  • WdfPdoInitAllocate:就是我们刚聊的,为子设备 PDO 分配“户口本”。
  • 注册电源回调这正是你之前问的 EvtDeviceSelfManagedIoInit 这里为子设备也注册了自己的电源管理回调。这意味着子设备(虚拟 HID 设备)在被系统启动时,也能执行自己的初始化逻辑(比如创建定时器)。
  • WdfPdoInitAssignRawDevice这是一个关键点。它将这个 PDO 标记为“RAW”设备,告诉系统:“这个设备不需要加载任何功能驱动(Function Driver),我自己就能处理所有事情。” 这也是为什么它不需要匹配任何 INF 文件。

2. 设置设备身份标识 (PnP 的核心)

// 分配设备 ID
status = WdfPdoInitAssignDeviceID(pDeviceInit, &deviceId);

// 分配实例 ID (区分多个同型号设备)
status =  RtlUnicodeStringPrintf(&buffer, L"%02d", InstanceNo);
status = WdfPdoInitAssignInstanceID(pDeviceInit, &buffer);

// 添加设备描述文本 (在设备管理器里显示)
status = WdfPdoInitAddDeviceText(pDeviceInit, &buffer, &deviceLocation, 0x409);
  • 这些标识符是 PnP 子系统的核心。系统通过这些 ID 来决定如何匹配驱动程序,以及在设备管理器中显示什么名字。因为没有 INF 文件,Raw 设备会直接使用你在代码中提供的描述。

3. 创建实际的设备对象 (FDO → PDO)

// 设置设备上下文的内存大小
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, RAWPDO_DEVICE_CONTEXT);

// 关键!允许将未处理的请求转发给父设备 (FDO)
WdfPdoInitAllowForwardingRequestToParent(pDeviceInit);

// 正式创建 PDO
status = WdfDeviceCreate(&pDeviceInit, &pdoAttributes, &hChild);
  • 调用 WdfDeviceCreate 后,一个真正的 PDO 对象就诞生了。
  • WdfPdoInitAllowForwardingRequestToParent 很有用:当子设备收到它无法处理的 I/O 请求时,可以转交给父总线设备处理。

4. 设置 PDO 的属性与功能

// 创建 I/O 队列 (处理应用发来的读写请求)
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchSequential);
ioQueueConfig.EvtIoWrite = HIDINJECTOR_EvtIoWriteForRawPdo;
status = WdfIoQueueCreate(hChild, ...);

// 设置 PnP 能力 (可移除、支持意外移除)
WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
pnpCaps.Removable = WdfTrue;
pnpCaps.SurpriseRemovalOK = WdfTrue;
WdfDeviceSetPnpCapabilities(hChild, &pnpCaps);

// 创建设备接口 (让应用程序可以找到并打开这个设备)
status = WdfDeviceCreateDeviceInterface(hChild, &GUID_DEVINTERFACE_HIDINJECTOR, NULL);
  • 这步完成了 PDO 的“软件配置”。它告诉系统:这个设备支持哪些操作(I/O 队列)、物理属性如何(是否可热插拔),以及应用程序如何找到它(设备接口 GUID)。

5. 将子设备挂载到父设备下 (建立从属关系)

// 将 PDO 添加到父设备的静态子设备列表中
status = WdfFdoAddStaticChild(Device, hChild);
  • WdfFdoAddStaticChild建立父子关系的关键。它将刚刚创建的 PDO (hChild) 正式注册到父 FDO (Device) 的子设备列表中。
  • 从此,父设备(总线)的生命周期(启动、睡眠、移除)会与子设备(虚拟 HID)的生命周期联动。这与你之前理解的电源状态管理是紧密配合的。

错误处理与资源释放

Cleanup:
    if (pDeviceInit != NULL) {
        WdfDeviceInitFree(pDeviceInit); // 释放未使用的初始化结构
    }
    if(hChild) {
        WdfObjectDelete(hChild); // 删除已创建的设备对象
    }
    return status;

这是标准的错误处理逻辑。如果在创建过程中失败,必须手动释放已分配的资源,避免内存泄漏。


0 篇笔记 写笔记

作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!