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





