基于IDD技术的虚拟显示器开发实现
IDD是Indirect Display Driver的缩写,它提供了一种快速开发出虚拟显示器的技术。所谓虚拟显示器,是指我们利用软件技术,在没有外接物理显示器的环境下,虚拟出显示器设备。每个虚拟的显示器都可以显示不同的图像内容,我们可以将虚拟显示器扩展,复制或者独立使用。
虚拟显示器的使用场景非常多,例如:
- 可以在没有足够外接接口的物理设备下,通过软件技术虚拟出显示设备。
- 远程虚拟多屏功能,例如如果我们被远程的服务器只有一个显示器,但是远程客户端需要双屏显示,那么我们就可以在服务器上面虚拟一个显示器来实现。
通过Indirect Display Driver我们可以创建一个虚拟的显示适配器,对该适配器我们模拟插入一个虚拟显示器设备,例如如下:
通过虚拟显示器,我们在虚拟机里面创建显示器,并扩展屏使用,如下:
本文我们分析一下如何使用Indirect Display Driver的来实现虚拟显示器的开发。
1. 技术架构
Indirect Display Driver模型提供了一种简单的用户模式驱动,用来虚拟化显示适配器,它的实现架构图如下:
在这个框架模型中:
- DxgKrnl.sys是微软图形显示子系统驱动,是微软WDDM框架驱动的实现基础组件。
- IndirectKMD.sys是专门IDD框架引入的一个驱动,它是一个Display Only驱动程序,是IDD的核心实现驱动。
- IddCx.dll是用户层接口提供动态库,主要给IDD用户态驱动提供相关接口(third-part部分)。
- third-part这个是是用户实现的IDD驱动程序,这是我们自己开发的用户态驱动模块。
对于IddCx的命名约定如下:
-
EVT_IDD_CX_XXX
:表示IDD回调函数。 -
IddCxXxx
:表示IddCx提供的扩展函数。 -
PFN_IDDCX_XXX
:指向IddCx函数的指针。
2. IddCx对象
在进行IDD驱动开发之前,需要掌握几个IddCx对象的概念,他们依次的创建顺序如下:
-
IDDCX_ADAPTER
表示逻辑显示适配器的对象。 -
IDDCX_MONITOR
表示连接的显示器的对象。 -
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;
}
这里主要有几个重要操作:
-
IddSampleDeviceD0Entry
设置的电源函数,表示D0状态进入。 -
IddCxDeviceInitConfig
设置IddCx的配置信息。 -
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;
};
在上述回调函数中,各自功能如下:
-
IddSampleDeviceD0Entry
表示设备进入工作状态,一般我们在此创建IDDCX_ADAPTER
适配器对象。 -
IddSampleAdapterInitFinished
表示IddCxAdapterInitAsync
函数创建适配器对象完成。 -
IddSampleParseMonitorDescription
表示解析显示器的EDID获取相关模式。 -
IddSampleMonitorGetDefaultModes
表示获取显示器的默认模式。 -
EvtIddCxMonitorQueryTargetModes
查询驱动支持的模式集合。 -
IddSampleAdapterCommitModes
提交模式的回调函数。 -
IddSampleMonitorAssignSwapChain
桌面图片交换链创建的回调函数。 -
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中,提供了如下设备对象:
- 单独设备对象,和用户层驱动通信(一般使用IOCONTROL)。
- 和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-Alertable
b3333848 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
需要校验签名信息:
这里有两个调用操作:
-
IddAdapter::IsSwDevice
这个判断是否是SW(SoftWare Device 也就是软件设备)设备(据说将IDD安装到自己的虚拟总线这个函数就会返回失败)。 -
DriverSigning::IsDriverWindowsSigned
判断是否签名(看下面抛出的异常应该是微软签名)。
因此这里有两种解决方案:文章来源:https://www.toymoban.com/news/detail-845590.html
- 将IDD挂载到自己的虚拟总线上面(有网友实现可以,本人没有做验证)。
- 安装测试证书,然后开启测试模式(这种方法比较简单)/ 正式签名。
8. 实现效果
通过上述技术分析,我们就可以利用IDD来实现一个虚拟显示器。例如下面的示例,我可以在虚拟机中创建一个虚拟显示器,并将其设置成扩展屏,然后可以和正常扩展屏一样使用虚拟显示器的扩展屏,如下:
文章来源地址https://www.toymoban.com/news/detail-845590.html
到了这里,关于基于IDD技术的虚拟显示器开发实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!