基于IDD技术的虚拟显示器开发实现

2024-03-06 06:50

本文主要是介绍基于IDD技术的虚拟显示器开发实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 基于IDD技术的虚拟显示器开发实现
    • 1. 技术架构
    • 2. IddCx对象
      • 2.1 IDDCX_ADAPTER结构
      • 2.2 IDDCX_MONITOR结构
      • 2.3 IDDCX_SWAPCHAIN结构
    • 3. 关于EDID
    • 4. IDD开发
    • 5. 关于IDD驱动的安装
    • 6. 关于设备的挂载和运行
    • 7. 其他问题
      • 7.1 关于IddCxAdapterInitAsync失败
    • 8. 实现效果

基于IDD技术的虚拟显示器开发实现

IDD是Indirect Display Driver的缩写,它提供了一种快速开发出虚拟显示器的技术。所谓虚拟显示器,是指我们利用软件技术,在没有外接物理显示器的环境下,虚拟出显示器设备。每个虚拟的显示器都可以显示不同的图像内容,我们可以将虚拟显示器扩展,复制或者独立使用。

虚拟显示器的使用场景非常多,例如:

  1. 可以在没有足够外接接口的物理设备下,通过软件技术虚拟出显示设备。
  2. 远程虚拟多屏功能,例如如果我们被远程的服务器只有一个显示器,但是远程客户端需要双屏显示,那么我们就可以在服务器上面虚拟一个显示器来实现。

通过Indirect Display Driver我们可以创建一个虚拟的显示适配器,对该适配器我们模拟插入一个虚拟显示器设备,例如如下:

在这里插入图片描述

通过虚拟显示器,我们在虚拟机里面创建显示器,并扩展屏使用,如下:

在这里插入图片描述

本文我们分析一下如何使用Indirect Display Driver的来实现虚拟显示器的开发。

1. 技术架构

Indirect Display Driver模型提供了一种简单的用户模式驱动,用来虚拟化显示适配器,它的实现架构图如下:
在这里插入图片描述

在这个框架模型中:

  1. DxgKrnl.sys是微软图形显示子系统驱动,是微软WDDM框架驱动的实现基础组件。
  2. IndirectKMD.sys是专门IDD框架引入的一个驱动,它是一个Display Only驱动程序,是IDD的核心实现驱动。
  3. IddCx.dll是用户层接口提供动态库,主要给IDD用户态驱动提供相关接口(third-part部分)。
  4. third-part这个是是用户实现的IDD驱动程序,这是我们自己开发的用户态驱动模块。

对于IddCx的命名约定如下:

  1. EVT_IDD_CX_XXX:表示IDD回调函数。
  2. IddCxXxx:表示IddCx提供的扩展函数。
  3. PFN_IDDCX_XXX:指向IddCx函数的指针。

2. IddCx对象

在进行IDD驱动开发之前,需要掌握几个IddCx对象的概念,他们依次的创建顺序如下:

  1. IDDCX_ADAPTER表示逻辑显示适配器的对象。
  2. IDDCX_MONITOR表示连接的显示器的对象。
  3. IDDCX_SWAPCHAIN表示桌面图像的交换链。

2.1 IDDCX_ADAPTER结构

IDDCX_ADAPTER表示一个单独的逻辑显示适配器,通过IddCxAdapterInitAsync接口来创建,这个接口声明如下:

NTSTATUS IddCxAdapterInitAsync(const IDARG_IN_ADAPTER_INIT *pInArgs,IDARG_OUT_ADAPTER_INIT      *pOutArgs
);struct IDARG_OUT_ADAPTER_INIT {IDDCX_ADAPTER AdapterObject;
};

这里通过IDARG_OUT_ADAPTER_INIT返回的就是IDDCX_ADAPTER,表示一个适配器对象。

正如函数名字一样,这个函数是异步创建的,IDD使用回调机制通知创建完成,例如如下:

EVT_IDD_CX_ADAPTER_INIT_FINISHED EvtIddCxAdapterInitFinished;NTSTATUS EvtIddCxAdapterInitFinished(IDDCX_ADAPTER AdapterObject,const IDARG_IN_ADAPTER_INIT_FINISHED *pInArgs
)
{...}struct IDARG_IN_ADAPTER_INIT_FINISHED {NTSTATUS AdapterInitStatus;
};

IDARG_IN_ADAPTER_INIT_FINISHED表示适配器的创建状态。

2.2 IDDCX_MONITOR结构

IDDCX_MONITOR表示的是一个可插拔显示器,这个设备通过IddCxMonitorCreate函数创建:

NTSTATUS IddCxMonitorCreate(IDDCX_ADAPTER                AdapterObject,const IDARG_IN_MONITORCREATE *pInArgs,IDARG_OUT_MONITORCREATE      *pOutArgs
);struct IDARG_IN_MONITORCREATE {PWDF_OBJECT_ATTRIBUTES ObjectAttributes;IDDCX_MONITOR_INFO     *pMonitorInfo;
};struct IDDCX_MONITOR_INFO {UINT                                  Size;DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY MonitorType;UINT                                  ConnectorIndex;IDDCX_MONITOR_DESCRIPTION             MonitorDescription;GUID                                  MonitorContainerId;
};//类似接口类型
typedef enum {DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1,DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0,DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5,
} DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;//一般来说是EDID的描述信息
struct IDDCX_MONITOR_DESCRIPTION {UINT                           Size;IDDCX_MONITOR_DESCRIPTION_TYPE Type;UINT                           DataSize;PVOID                          pData;
};

创建显示设备之后,通过IddCxMonitorArrival可以将显示器设备插入,这个函数声明如下:

NTSTATUS IddCxMonitorArrival(IDDCX_MONITOR            AdapterObject,IDARG_OUT_MONITORARRIVAL *pOutArgs
);//返回的显示器信息
struct IDARG_OUT_MONITORARRIVAL {LUID OsAdapterLuid;UINT OsTargetId;
};

IddCxMonitorDeparture这个函数表示显示器被拔出,该函数声明如下:

NTSTATUS IddCxMonitorDeparture(IDDCX_MONITOR MonitorObject
);

2.3 IDDCX_SWAPCHAIN结构

IDDCX_SWAPCHAIN代表着交换链(swapchain),他提供连接显示器的桌面显示图像,他是系统主动创建的,通过EVT_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN传递给IDD驱动,该回调函数如下:

EVT_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN EvtIddCxMonitorAssignSwapchain;NTSTATUS EvtIddCxMonitorAssignSwapchain(IDDCX_MONITOR MonitorObject,const IDARG_IN_SETSWAPCHAIN *pInArgs
)
{...}

当交换链被销毁的时候,就会调用EVT_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN回调函数,该回调函数如下:

EVT_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN EvtIddCxMonitorUnassignSwapchain;NTSTATUS EvtIddCxMonitorUnassignSwapchain(IDDCX_MONITOR MonitorObject
)
{...}

3. 关于EDID

EDID: Extended Display Identification Data(扩展显示标识数据)是一种VESA (Video Electronics Standards Association)标准数据格式,其中包含有关显示器及其性能的参数,包括供应商信息、最大图像大小、颜色设置、厂商预设置、频率范围的限制以及显示器名和序列号的字符串。

Host Device通过读取Display中的EDID数据来知道Display的一些属性。简而言之,EDID就是Display的一个描述信息。

对于虚拟显示器,我们需要在调用IddCxMonitorCreate的时候指定EDID,如下:

struct IDDCX_MONITOR_DESCRIPTION {UINT                           Size;IDDCX_MONITOR_DESCRIPTION_TYPE Type;UINT                           DataSize;PVOID                          pData;
};

并且需要我们在回调函数EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION中能够解析EDID并设置显示模式信息,该回调函数声明如下:

EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION EvtIddCxParseMonitorDescription;NTSTATUS EvtIddCxParseMonitorDescription(const IDARG_IN_PARSEMONITORDESCRIPTION *pInArgs,IDARG_OUT_PARSEMONITORDESCRIPTION *pOutArgs
)
{...}

4. IDD开发

IDD驱动是在IddSampleDeviceAdd完成初始化和创建的,主要是通过IddCxDeviceInitConfig设置框架调用的各种回调函数。IddSampleDeviceAdd这个函数实现代码如下:

NTSTATUS IddSampleDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{NTSTATUS Status = STATUS_SUCCESS;WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;//...WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);PnpPowerCallbacks.EvtDeviceD0Entry = IddSampleDeviceD0Entry;WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &PnpPowerCallbacks);IDD_CX_CLIENT_CONFIG IddConfig;IDD_CX_CLIENT_CONFIG_INIT(&IddConfig);//...IddConfig.EvtIddCxDeviceIoControl = IddSampleIoDeviceControl;IddConfig.EvtIddCxAdapterInitFinished = IddSampleAdapterInitFinished;IddConfig.EvtIddCxParseMonitorDescription = IddSampleParseMonitorDescription;IddConfig.EvtIddCxMonitorGetDefaultDescriptionModes = IddSampleMonitorGetDefaultModes;IddConfig.EvtIddCxMonitorQueryTargetModes = IddSampleMonitorQueryModes;IddConfig.EvtIddCxAdapterCommitModes = IddSampleAdapterCommitModes;IddConfig.EvtIddCxMonitorAssignSwapChain = IddSampleMonitorAssignSwapChain;IddConfig.EvtIddCxMonitorUnassignSwapChain = IddSampleMonitorUnassignSwapChain;Status = IddCxDeviceInitConfig(pDeviceInit, &IddConfig);if (!NT_SUCCESS(Status)){return Status;} //...Status = WdfDeviceCreate(&pDeviceInit, &Attr, &Device);if (!NT_SUCCESS(Status)){return Status;}Status = IddCxDeviceInitialize(Device);//...auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Device);pContext->pContext = new IndirectDeviceContext(Device);return Status;
}

这里主要有几个重要操作:

  1. IddSampleDeviceD0Entry设置的电源函数,表示D0状态进入。
  2. IddCxDeviceInitConfig设置IddCx的配置信息。
  3. IddCxDeviceInitialize初始化WDF设备(应该是告诉框架,回调函数有哪些)。

在这个函数中,主要的功能就是设置好IDD_CX_CLIENT_CONFIG的回调接口,系统框架通过回调接口在不同的时机创建不同的对象。

IDD_CX_CLIENT_CONFIG包含了所有显示驱动的回调函数,该结构声明如下:

struct IDD_CX_CLIENT_CONFIG {ULONG                                                       Size;PFN_IDD_CX_DEVICE_IO_CONTROL                                EvtIddCxDeviceIoControl;PFN_IDD_CX_PARSE_MONITOR_DESCRIPTION                        EvtIddCxParseMonitorDescription;PFN_IDD_CX_ADAPTER_INIT_FINISHED                            EvtIddCxAdapterInitFinished;PFN_IDD_CX_ADAPTER_COMMIT_MODES                             EvtIddCxAdapterCommitModes;PFN_IDD_CX_MONITOR_GET_DEFAULT_DESCRIPTION_MODES            EvtIddCxMonitorGetDefaultDescriptionModes;PFN_IDD_CX_MONITOR_QUERY_TARGET_MODES                       EvtIddCxMonitorQueryTargetModes;PFN_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN                         EvtIddCxMonitorAssignSwapChain;PFN_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN                       EvtIddCxMonitorUnassignSwapChain;PFN_IDD_CX_MONITOR_I2C_TRANSMIT                             EvtIddCxMonitorI2CTransmit;PFN_IDD_CX_MONITOR_I2C_RECEIVE                              EvtIddCxMonitorI2CReceive;PFN_IDD_CX_MONITOR_SET_GAMMA_RAMP                           EvtIddCxMonitorSetGammaRamp;PFN_IDD_CX_MONITOR_OPM_GET_CERTIFICATE_SIZE                 EvtIddCxMonitorOPMGetCertificateSize;PFN_IDD_CX_MONITOR_OPM_GET_CERTIFICATE                      EvtIddCxMonitorOPMGetCertificate;PFN_IDD_CX_MONITOR_OPM_CREATE_PROTECTED_OUTPUT              EvtIddCxMonitorOPMCreateProtectedOutput;PFN_IDD_CX_MONITOR_OPM_GET_RANDOM_NUMBER                    EvtIddCxMonitorOPMGetRandomNumber;PFN_IDD_CX_MONITOR_OPM_SET_SIGNING_KEY_AND_SEQUENCE_NUMBERS EvtIddCxMonitorOPMSetSigningKeyAndSequenceNumbers;PFN_IDD_CX_MONITOR_OPM_GET_INFOMATION                       EvtIddCxMonitorOPMGetInformation;PFN_IDD_CX_MONITOR_OPM_CONFIGURE_PROTECTED_OUTPUT           EvtIddCxMonitorOPMConfigureProtectedOutput;PFN_IDD_CX_MONITOR_OPM_DESTROY_PROTECTED_OUTPUT             EvtIddCxMonitorOPMDestroyProtectedOutput;PFN_IDD_CX_MONITOR_GET_PHYSICAL_SIZE                        EvtIddCxMonitorGetPhysicalSize;
};

在上述回调函数中,各自功能如下:

  1. IddSampleDeviceD0Entry表示设备进入工作状态,一般我们在此创建IDDCX_ADAPTER适配器对象。
  2. IddSampleAdapterInitFinished表示IddCxAdapterInitAsync函数创建适配器对象完成。
  3. IddSampleParseMonitorDescription表示解析显示器的EDID获取相关模式。
  4. IddSampleMonitorGetDefaultModes表示获取显示器的默认模式。
  5. EvtIddCxMonitorQueryTargetModes 查询驱动支持的模式集合。
  6. IddSampleAdapterCommitModes提交模式的回调函数。
  7. IddSampleMonitorAssignSwapChain桌面图片交换链创建的回调函数。
  8. IddSampleMonitorUnassignSwapChain桌面图片交换链销毁的回调函数。

虚拟显示器的创建通过IddCxMonitorCreate函数来完成,一般我们可以在IddSampleAdapterInitFinished回调中,显示器适配对象创建完成之后,调用,例如实例代码如下:

void IndirectDeviceContext::FinishInit(UINT ConnectorIndex)
{WDF_OBJECT_ATTRIBUTES Attr;WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectMonitorContextWrapper);IDDCX_MONITOR_INFO MonitorInfo = {};MonitorInfo.Size = sizeof(MonitorInfo);MonitorInfo.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;MonitorInfo.ConnectorIndex = ConnectorIndex;MonitorInfo.MonitorDescription.Size = sizeof(MonitorInfo.MonitorDescription);MonitorInfo.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;if (ConnectorIndex >= ARRAYSIZE(s_SampleMonitors)){MonitorInfo.MonitorDescription.DataSize = 0;MonitorInfo.MonitorDescription.pData = nullptr;}else{MonitorInfo.MonitorDescription.DataSize = IndirectSampleMonitor::szEdidBlock;MonitorInfo.MonitorDescription.pData = const_cast<BYTE*>(s_SampleMonitors[ConnectorIndex].pEdidBlock);}CoCreateGuid(&MonitorInfo.MonitorContainerId);IDARG_IN_MONITORCREATE MonitorCreate = {};MonitorCreate.ObjectAttributes = &Attr;MonitorCreate.pMonitorInfo = &MonitorInfo;IDARG_OUT_MONITORCREATE MonitorCreateOut;NTSTATUS Status = IddCxMonitorCreate(m_Adapter, &MonitorCreate, &MonitorCreateOut);if (NT_SUCCESS(Status)){auto* pMonitorContextWrapper = WdfObjectGet_IndirectMonitorContextWrapper(MonitorCreateOut.MonitorObject);pMonitorContextWrapper->pContext = new IndirectMonitorContext(MonitorCreateOut.MonitorObject);IDARG_OUT_MONITORARRIVAL ArrivalOut;Status = IddCxMonitorArrival(MonitorCreateOut.MonitorObject, &ArrivalOut);}
}

5. 关于IDD驱动的安装

首先,IDD是一个用户态驱动程序,这个驱动程序依赖WUDFRd.sys内核态驱动程序来完成功能,因此对于IDD安装的时候指定的服务是WUDFRd,安装INF如下:

[MyDevice_Install.NT.Services]
AddService=WUDFRd,0x000001fa,WUDFRD_ServiceInstall[WUDFRD_ServiceInstall]
DisplayName = %WudfRdDisplayName%
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\WUDFRd.sys

除此之外,还需要安装UMDF驱动

[MyDevice_Install.NT.Wdf]
UmdfService=IddSampleDriver,IddSampleDriver_Install
UmdfServiceOrder=IddSampleDriver
UmdfKernelModeClientPolicy = AllowKernelModeClients

因此我们启动的时候,启动的驱动是WUDFRd.sys,从这里看出来UMDF驱动框架是依赖内核层驱动,将内核层信息传递到用户层。

WUDFRd.sys只能做一个非常基础的WUDF驱动框架,真实的IDD框架是IndirectKmd来完成的,在安装过程中,我们通过UpperFilters来使得形成设备堆栈,达到IndirectKmd运行的目的,如下:

[MyDevice_HardwareDeviceSettings]
HKR,, "UpperFilters",  %REG_MULTI_SZ%, "IndirectKmd"

6. 关于设备的挂载和运行

安装完成之后,在内核驱动WUDFRd中,提供了如下设备对象:

  1. 单独设备对象,和用户层驱动通信(一般使用IOCONTROL)。
  2. 和IndirectKmd驱动形成设备挂载。

这个设备挂载含如下:

kd> !devobj 8f633640 
Device object (8f633640) is for:\Driver\WudfRd DriverObject a0bfab30
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00003050
SecurityDescriptor 87246d90 DevExt 8f6336f8 DevObjExt 8f633718 
ExtensionFlags (0x00000010)  DOE_START_PENDING
Characteristics (0x00000180)  FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) af0de020 \Driver\IndirectKmd
AttachedTo (Lower) 8f6338b0 \Driver\SoftwareDevice
Device queue is not busy.

也就是说\Driver\IndirectKmd驱动的消息(一般设备对象是Dxgkrnl中),通过设备栈传到\Driver\WudfRd,驱动\Driver\WudfRd再将消息传递到IDD。

例如我们可以看一下驱动的调用堆栈:

kd> !thread
THREAD af1b47c0  Cid 0004.00cc  Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
IRP List:adf50390: (0006,01d8) Flags: 00000000  Mdl: 00000000
Not impersonating
DeviceMap                 872042c0
Owning Process            86789280       Image:         System
Attached Process          N/A            Image:         N/A
Wait Start TickCount      551671         Ticks: 0
Context Switch Count      3249           IdealProcessor: 0  NoStackSwap
UserTime                  00:00:00.000
KernelTime                00:00:00.078
Win32 Start Address nt!ExpWorkerThread (0x81edd980)
Stack Init b3333ca0 Current b33336ec Base b3334000 Limit b3331000 Call 00000000
Priority 13 BasePriority 12 PriorityDecrement 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr      Args to Child              
b3333800 81e49958     8f633640 adf50390 00000000 WUDFRd!RdDriver::RdDispatch (FPO: [Non-Fpo]) (CONV: stdcall) 
b3333818 900ebf23     af0de0d8 00000000 adf5046c nt!IofCallDriver+0x48 (FPO: [Non-Fpo])
b3333868 900bf6b1     af0de020 adf50390 adf5046c dxgkrnl!DpiFdoDispatchPnp+0x983 (FPO: [Non-Fpo])
b3333930 81e49958     af0de020 adf50390 b33339c8 dxgkrnl!DpiDispatchPnp+0xa9 (FPO: [Non-Fpo])
b3333948 82146f3c     c00000bb 8f6338b0 b3333a20 nt!IofCallDriver+0x48 (FPO: [Non-Fpo])
b3333984 82238a9a     c00000bb 00000000 b3333a20 nt!IopSynchronousCall+0xba (FPO: [Non-Fpo])
b33339c8 822384a6     b7c2f7c4 00000000 9ea10e00 nt!PpIrpQueryResourceRequirements+0x34 (FPO: [Non-Fpo])
b3333a18 82238177     00000000 b3333a3c b7c2f7b0 nt!IopQueryDeviceResources+0xda (FPO: [Non-Fpo])
b3333a4c 82237e6b     b3333aa8 00000001 b7c2f7d8 nt!PnpGetResourceRequirementsForAssignTable+0x9b (FPO: [Non-Fpo])
b3333ab0 82237db5     00000000 b3333b43 b6d0dd10 nt!PnpAllocateResources+0x5d (FPO: [Non-Fpo])
b3333adc 82235372     b3333b43 87e1fd98 87e1fd98 nt!PnpAssignResourcesToDevices+0x47 (FPO: [Non-Fpo])
b3333b0c 82231a36     b3333b43 87e1fd98 b2ec0428 nt!PnpProcessAssignResources+0xc2 (FPO: [Non-Fpo])
b3333b4c 8223bfcb     b3333b78 00000001 00000000 nt!PipProcessDevNodeTree+0x60 (FPO: [Non-Fpo])
b3333b80 81f4003a     867a65a0 af1b47c0 820c5400 nt!PiProcessReenumeration+0x6d (FPO: [Non-Fpo])
b3333be8 81edda6a     00000000 00000000 af1b47c0 nt!PnpDeviceActionWorker+0x35a (FPO: [Non-Fpo])
b3333c38 81eac0f0     867a65a0 39dba586 00000000 nt!ExpWorkerThread+0xea (FPO: [Non-Fpo])
b3333c70 81f8818d     81edd980 867a65a0 00000000 nt!PspSystemThreadStartup+0x4a (FPO: [Non-Fpo])
b3333c7c 00000000     00000000 003b006f 00680063 nt!KiThreadStartup+0x15

这里我们可以找到两个设备对象:

kd> !devobj af0de020
Device object (af0de020) is for:\Driver\IndirectKmd DriverObject a0bfa130
Current Irp 00000000 RefCount 0 Type 00000023 Flags 00002004
SecurityDescriptor 87246d90 DevExt af0de0d8 DevObjExt af0defa8 
ExtensionFlags (0x00000010)  DOE_START_PENDING
Characteristics (0x00000100)  FILE_DEVICE_SECURE_OPEN
AttachedTo (Lower) 8f633640 \Driver\WudfRd
Device queue is not busy.kd> !devobj 8f633640 
Device object (8f633640) is for:\Driver\WudfRd DriverObject a0bfab30
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00003050
SecurityDescriptor 87246d90 DevExt 8f6336f8 DevObjExt 8f633718 
ExtensionFlags (0x00000010)  DOE_START_PENDING
Characteristics (0x00000180)  FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) af0de020 \Driver\IndirectKmd
AttachedTo (Lower) 8f6338b0 \Driver\SoftwareDevice
Device queue is not busy.

当把IRP发送给用户层处理的时候,此时的调用堆栈如下:

kd> !thread af1b47c0
THREAD af1b47c0  Cid 0004.00cc  Teb: 00000000 Win32Thread: 00000000 WAIT: (Suspended) KernelMode Non-Alertableb3333848  NotificationEvent
Not impersonating
DeviceMap                 872042c0
Owning Process            86789280       Image:         System
Attached Process          N/A            Image:         N/A
Wait Start TickCount      551671         Ticks: 0
Context Switch Count      3256           IdealProcessor: 0  NoStackSwap
UserTime                  00:00:00.000
KernelTime                00:00:00.078
Win32 Start Address nt!ExpWorkerThread (0x81edd980)
Stack Init b3333ca0 Current b3333664 Base b3334000 Limit b3331000 Call 00000000
Priority 13 BasePriority 12 PriorityDecrement 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr      Args to Child              
b333367c 81e518aa     867a6500 81271120 af1b47c0 nt!KiSwapContext+0x19 (FPO: [Uses EBP] [1,0,4])
b3333728 81e50f97     af1b48a0 af1b47c0 b3333848 nt!KiSwapThread+0x45a (FPO: [Non-Fpo])
b333377c 81e50972     00000000 8f633640 adf5041b nt!KiCommitThreadWait+0x127 (FPO: [Non-Fpo])
b3333824 81f2e0af     b3333848 00000005 00000000 nt!KeWaitForSingleObject+0x1d2 (FPO: [Non-Fpo])
b3333858 8221b44a     8f633640 adf50390 adf50390 nt!IoSynchronousCallDriver+0x65 (FPO: [2,4,0])
b3333870 900e3734     8f633640 adf50390 af0de0d8 nt!IoForwardIrpSynchronously+0x2a (FPO: [Non-Fpo])
b33338e0 900eb607     af0de020 adf50390 af0de0d8 dxgkrnl!DpiFdoHandleStartDevice+0x14e (FPO: [Non-Fpo])
b3333938 900bf6b1     af0de020 adf50390 af0de020 dxgkrnl!DpiFdoDispatchPnp+0x67 (FPO: [Non-Fpo])
b3333a00 81e49958     af0de020 adf50390 b3333ab4 dxgkrnl!DpiDispatchPnp+0xa9 (FPO: [Non-Fpo])
b3333a18 8223d340     00000000 8f6338b0 b3333a90 nt!IofCallDriver+0x48 (FPO: [Non-Fpo])
b3333a38 81eb6ee9     81f3ac10 b869b690 8f6338b0 nt!PnpAsynchronousCall+0x9e (FPO: [Non-Fpo])
b3333a6c 81f402ee     af1b47c0 81f3ac10 b869b690 nt!PnpSendIrp+0x6f (FPO: [Non-Fpo])
b3333ab4 8223c2cd     b869b690 00000000 9ea10e00 nt!PnpStartDevice+0x68 (FPO: [Non-Fpo])
b3333aec 8223c1d5     b869b690 87e1fd98 9ea10e00 nt!PnpStartDeviceNode+0xcd (FPO: [Non-Fpo])
b3333b0c 82231cf0     00000000 87e1fd98 b2ec0428 nt!PipProcessStartPhase1+0x53 (FPO: [Non-Fpo])
b3333b4c 8223bfcb     b3333b78 00000001 00000000 nt!PipProcessDevNodeTree+0x31a (FPO: [Non-Fpo])
b3333b80 81f4003a     867a65a0 af1b47c0 820c5400 nt!PiProcessReenumeration+0x6d (FPO: [Non-Fpo])
b3333be8 81edda6a     00000000 00000000 af1b47c0 nt!PnpDeviceActionWorker+0x35a (FPO: [Non-Fpo])
b3333c38 81eac0f0     867a65a0 39dba586 00000000 nt!ExpWorkerThread+0xea (FPO: [Non-Fpo])
b3333c70 81f8818d     81edd980 867a65a0 00000000 nt!PspSystemThreadStartup+0x4a (FPO: [Non-Fpo])
b3333c7c 00000000     00000000 003b006f 00680063 nt!KiThreadStartup+0x15

7. 其他问题

7.1 关于IddCxAdapterInitAsync失败

如果我们直接使用IDD的示例的话IddCxAdapterInitAsync这个函数会返回失败,这个函数返回STATUS_NOT_SUPPORTED

#define STATUS_NOT_SUPPORTED             ((NTSTATUS)0xC00000BBL)

为什么会导致这个问题的产生呢:因为在IddCxAdapterInitAsync中的初始化函数IddAdapter::Init需要校验签名信息:
在这里插入图片描述

这里有两个调用操作:

  1. IddAdapter::IsSwDevice这个判断是否是SW(SoftWare Device 也就是软件设备)设备(据说将IDD安装到自己的虚拟总线这个函数就会返回失败)。
  2. DriverSigning::IsDriverWindowsSigned判断是否签名(看下面抛出的异常应该是微软签名)。

因此这里有两种解决方案:

  1. 将IDD挂载到自己的虚拟总线上面(有网友实现可以,本人没有做验证)。
  2. 安装测试证书,然后开启测试模式(这种方法比较简单)/ 正式签名。

8. 实现效果

通过上述技术分析,我们就可以利用IDD来实现一个虚拟显示器。例如下面的示例,我可以在虚拟机中创建一个虚拟显示器,并将其设置成扩展屏,然后可以和正常扩展屏一样使用虚拟显示器的扩展屏,如下:
在这里插入图片描述

这篇关于基于IDD技术的虚拟显示器开发实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/779189

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time