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

这篇具有很好参考价值的文章主要介绍了基于IDD技术的虚拟显示器开发实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

indirect display driver,云桌面-VDI,远程桌面,Windows驱动开发,计算机外设,c++,c语言,驱动开发,windows,开发语言

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

indirect display driver,云桌面-VDI,远程桌面,Windows驱动开发,计算机外设,c++,c语言,驱动开发,windows,开发语言

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

1. 技术架构

Indirect Display Driver模型提供了一种简单的用户模式驱动,用来虚拟化显示适配器,它的实现架构图如下:
indirect display driver,云桌面-VDI,远程桌面,Windows驱动开发,计算机外设,c++,c语言,驱动开发,windows,开发语言

在这个框架模型中:

  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-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需要校验签名信息:
indirect display driver,云桌面-VDI,远程桌面,Windows驱动开发,计算机外设,c++,c语言,驱动开发,windows,开发语言

这里有两个调用操作:

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

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

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

8. 实现效果

通过上述技术分析,我们就可以利用IDD来实现一个虚拟显示器。例如下面的示例,我可以在虚拟机中创建一个虚拟显示器,并将其设置成扩展屏,然后可以和正常扩展屏一样使用虚拟显示器的扩展屏,如下:
indirect display driver,云桌面-VDI,远程桌面,Windows驱动开发,计算机外设,c++,c语言,驱动开发,windows,开发语言文章来源地址https://www.toymoban.com/news/detail-845590.html

到了这里,关于基于IDD技术的虚拟显示器开发实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 物理机ubuntu系统--远程控制-不接显示器-使用虚拟显示器-设置分辨率1920*1080

    liunx系统与intel显卡驱动不兼容的机制问题,导致有些机器无法在没有显示器的情况下,不能进行远程,向日葵和TeamViewer都不行。 因此使用虚拟显示器的软件 Xorg 。 反正网上很多资料。本文只是添加图片,更加详细的过程,以及遇到的情况,给予需要帮助的人,帮到你的话,

    2024年02月10日
    浏览(48)
  • ubuntu设置虚拟显示器且远程连接

    ubuntu 20.04 在 /usr/share/X11/xorg.conf.d/ 中添加 xorg.conf 文件(系统默认就会使用虚拟显示器): 虽然配置上面写了 “1920x1080”,但是实际上最大支持 “1360x768”,重启即可生效。 使用teamviewer、todesk或者向日葵等远程工具实现远程连接即可。 重启计算机后,系统会默认使用虚拟显

    2024年02月16日
    浏览(42)
  • 通过FPGA实现基于RS232串口的指令发送并控制显示器中目标位置

    目录 1.算法理论概述 串口通信模块 指令解析模块 位置控制模块 显示器驱动模块 2.部分核心程序 3.算法运行软件版本 4.算法运行效果图预览 5.算法完整程序工程         通过FPGA实现基于RS232串口的指令发送并控制显示器中目标位置是一种常见的应用场景,用于实现对显示器

    2024年02月16日
    浏览(44)
  • 详细介绍如何基于ESP32实现低功耗的电子纸天气显示器--附完整源码

    实现界面展示          这是一款天气显示器,由支持 wifi 的 ESP32 微控制器和 7.5 英寸电子纸(又名电子墨水)显示器供电。当前和预测的天气数据是从 OpenWeatherMap API 获取的。传感器为显示屏提供准确的室内温度和湿度。 该项目在睡眠时消耗约 14μA,在约 10 秒的清醒期

    2024年02月10日
    浏览(32)
  • 在VMware虚拟机中实现双显示器的使用

    在VMware虚拟机中,使用多个显示器可以提高工作效率和舒适度。本文将介绍如何在VMware虚拟机上配置双显示器,并提供相应的源代码示例。 步骤1:检查虚拟机设置 首先,确保你的虚拟机设置允许使用多个显示器。打开VMware虚拟机,选择“编辑”菜单,然后选择“虚拟机设置

    2024年02月05日
    浏览(43)
  • 远程服务器虚拟显示器(Ubuntu 20.04 LTS)

    安装 ssh sever 并开启,确保虚拟显示器配置失败后,无法正常显示,仍可以通过ssh连接至服务器 查看 ssh 服务已经开启,并可以远程 ssh 连接至服务器 测试远程连接 打开配置文件 编辑配置文件 重启服务器 sudo apt-get --purge remove xserver-xorg-core-hwe-18.04 sudo apt-get --purge remove xserve

    2024年02月11日
    浏览(42)
  • ubuntu开机自启vnc虚拟显示器并使用向日葵远程连接

    解锁后打开自动登录 依赖 初始化 在终端启动vnc vncserver 此时要先设置一个6位密码并确认一遍,如123456 修改文件 sudo gedit ~/.vnc/xstartup 替换为以下内容 新建脚本文件如fan.sh sudo gedit ~/fan.sh 脚本内容为 赋予执行权限 sudo chmod 777 fan.

    2024年02月09日
    浏览(47)
  • 单片机接口与技术 实验03 显示器与按键

    目录 前言: 1、实验目的: 2、实验内容: 第一版:lcd1602显示器: 代码 电路图 第二版(提高版): 代码: lcd1602.h lcd1602.c main.c 电路图: 电路图详解: 代码详解: lcd1602.h lcd1602.c lcd1602命令字 重点:         首先是初始化         显示字符串         设置光标位置    

    2024年02月08日
    浏览(28)
  • 外接竖屏显示器virtual box虚拟机屏幕分辨率不能自适应

    问题描述: 笔记本外接竖屏显示器后,virtual box 无适合分辨率 导致结果如图  解决方案: 1.确保虚拟机关闭 2.双击此处修改显卡设置为VBoxSVGA 3.重启虚拟机--- 找到视图界面,勾选“自动调整显示尺寸”    修改视图中的模式即可(依然失败可尝试将外接显示器设置为主屏幕

    2024年02月14日
    浏览(55)
  • 基于ESP32的RGB点阵显示器

            分享一个之前制作的RGB彩灯点阵控制器。     硬件介绍:     1.主控芯片ESP32;     2.RGB灯为内置控制IC类型的,IC型号为WS2812B;     3.点阵的大小为12X10,分别使用ESP32的10个IO来控制点阵的10个显示行;     4.设置有BH1750光照传感器,对环境光线强度进行检测

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包