Development and implementation of virtual display based on IDD technology

Article directory

  • Development and implementation of virtual display based on IDD technology
    • 1. Technical architecture
    • 2. IddCx object
      • 2.1 IDDCX_ADAPTER structure
      • 2.2 IDDCX_MONITOR structure
      • 2.3 IDDCX_SWAPCHAIN structure
    • 3. About EDID
    • 4. IDD development
    • 5. About the installation of IDD driver
    • 6. About mounting and running the device
    • 7. Other questions
      • 7.1 About IddCxAdapterInitAsync failure
    • 8. Achieve results

Development and implementation of virtual display based on IDD technology

IDD is the abbreviation of Indirect Display Driver, which provides a technology for quickly developing virtual displays. The so-called virtual display means that we use software technology to virtualize a display device in an environment without an external physical display. Each virtual display can display different image content. We can extend, copy or use the virtual display independently.

There are many usage scenarios for virtual displays, such as:

  1. Display devices can be virtualized through software technology when physical devices do not have sufficient external interfaces.
  2. Remote virtual multi-screen function, for example, if the remote server we are remote from has only one monitor, but the remote client needs dual-screen display, then we can virtualize a monitor on the server to achieve it.

Through the Indirect Display Driver we can create a virtual display adapter, to which we simulate inserting a virtual display device, for example as follows:

Through the virtual display, we create the display in the virtual machine and use the extended screen, as follows:

In this article, we analyze how to use the Indirect Display Driver to implement the development of virtual displays.

1. Technical architecture

The Indirect Display Driver model provides a simple user-mode driver for virtualizing the display adapter. Its implementation architecture is as follows:

In this framework model:

  1. DxgKrnl.sys is the Microsoft graphics display subsystem driver and the basic component for implementing the Microsoft WDDM framework driver.
  2. IndirectKMD.sys is a driver introduced by the IDD framework. It is a Display Only driver and is the core implementation driver of IDD.
  3. IddCx.dll is a dynamic library that provides user-level interfaces. It mainly provides related interfaces (third-part) for IDD user-mode drivers.
  4. The third-part is a user-implemented IDD driver, which is a user-mode driver module developed by ourselves.

The naming convention for IddCx is as follows:

  1. EVT_IDD_CX_XXX: Represents the IDD callback function.
  2. IddCxXxx: Indicates the extension function provided by IddCx.
  3. PFN_IDDCX_XXX: Pointer to the IddCx function.

2. IddCx object

Before proceeding with IDD driver development, you need to master the concepts of several IddCx objects. Their creation order is as follows:

  1. IDDCX_ADAPTER is an object representing a logical display adapter.
  2. IDDCX_MONITOR represents the object of the connected monitor.
  3. IDDCX_SWAPCHAIN represents the swap chain of desktop images.

2.1 IDDCX_ADAPTER structure

IDDCX_ADAPTER represents a separate logical display adapter, created through the IddCxAdapterInitAsync interface. This interface is declared as follows:

NTSTATUS IddCxAdapterInitAsync(
  const IDARG_IN_ADAPTER_INIT *pInArgs,
  IDARG_OUT_ADAPTER_INIT *pOutArgs
);

struct IDARG_OUT_ADAPTER_INIT {<!-- -->
  IDDCX_ADAPTER AdapterObject;
};

What is returned here through IDARG_OUT_ADAPTER_INIT is IDDCX_ADAPTER, which represents an adapter object.

As the function name suggests, this function is created asynchronously, and IDD uses a callback mechanism to notify the completion of creation, for example as follows:

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 indicates the creation status of the adapter.

2.2 IDDCX_MONITOR structure

IDDCX_MONITOR represents a pluggable monitor. This device is created through the IddCxMonitorCreate function:

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;
};

//Similar interface type
typedef enum {<!-- -->
  DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1,
  DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0,
  DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5,
} DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;

//Generally speaking, it is the description information of EDID
struct IDDCX_MONITOR_DESCRIPTION {<!-- -->
  UINT Size;
  IDDCX_MONITOR_DESCRIPTION_TYPE Type;
  UINT DataSize;
  PVOID pData;
};

After creating the display device, the display device can be inserted through IddCxMonitorArrival. This function is declared as follows:

NTSTATUS IddCxMonitorArrival(
  IDDCX_MONITOR AdapterObject,
  IDARG_OUT_MONITORARRIVAL *pOutArgs
);

//Returned display information
struct IDARG_OUT_MONITORARRIVAL {<!-- -->
  LUID OsAdapterLuid;
  UINT OsTargetId;
};

IddCxMonitorDepartureThis function indicates that the monitor is unplugged. The function is declared as follows:

NTSTATUS IddCxMonitorDeparture(
  IDDCX_MONITOR MonitorObject
);

2.3 IDDCX_SWAPCHAIN structure

IDDCX_SWAPCHAIN represents the swap chain, which provides the desktop display image connected to the monitor. It is actively created by the system and passed to the IDD driver through EVT_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN. The callback function is as follows :

EVT_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN EvtIddCxMonitorAssignSwapchain;

NTSTATUS EvtIddCxMonitorAssignSwapchain(
  IDDCX_MONITOR MonitorObject,
  const IDARG_IN_SETSWAPCHAIN *pInArgs
)
{<!-- -->...}

When the swap chain is destroyed, the EVT_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN callback function will be called. The callback function is as follows:

EVT_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN EvtIddCxMonitorUnassignSwapchain;

NTSTATUS EvtIddCxMonitorUnassignSwapchain(
  IDDCX_MONITOR MonitorObject
)
{<!-- -->...}

3. About EDID

EDID: Extended Display Identification Data (Extended Display Identification Data) is a VESA (Video Electronics Standards Association) standard data format that contains parameters about the display and its performance, including vendor information, maximum image size, color settings, and manufacturer presets. settings, limits of the frequency range, and strings for the display name and serial number.

Host Device knows some properties of Display by reading the EDID data in Display. In short, EDID is a description of Display.

For virtual displays, we need to specify the EDID when calling IddCxMonitorCreate, as follows:

struct IDDCX_MONITOR_DESCRIPTION {<!-- -->
  UINT Size;
  IDDCX_MONITOR_DESCRIPTION_TYPE Type;
  UINT DataSize;
  PVOID pData;
};

And we need to be able to parse the EDID and set the display mode information in the callback function EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION. The callback function is declared as follows:

EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION EvtIddCxParseMonitorDescription;

NTSTATUS EvtIddCxParseMonitorDescription(
  const IDARG_IN_PARSEMONITORDESCRIPTION *pInArgs,
  IDARG_OUT_PARSEMONITORDESCRIPTION *pOutArgs
)
{<!-- -->...}

4. IDD development

The IDD driver is initialized and created in IddSampleDeviceAdd, mainly by setting various callback functions called by the IddCxDeviceInitConfig framework. IddSampleDeviceAddThe implementation code of this function is as follows:

NTSTATUS IddSampleDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{<!-- -->
    NTSTATUS Status = STATUS_SUCCESS;
    WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
    //...
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT( & amp;PnpPowerCallbacks);
    PnpPowerCallbacks.EvtDeviceD0Entry = IddSampleDeviceD0Entry;
    WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, & amp;PnpPowerCallbacks);

    IDD_CX_CLIENT_CONFIG IddConfig;
    IDD_CX_CLIENT_CONFIG_INIT( & amp;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, & amp;IddConfig);
    if (!NT_SUCCESS(Status))
    {<!-- -->
        return Status;
    }
    //...
    Status = WdfDeviceCreate( & amp;pDeviceInit, & amp;Attr, & amp;Device);
    if (!NT_SUCCESS(Status))
    {<!-- -->
        return Status;
    }
    Status = IddCxDeviceInitialize(Device);
    //...
    auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Device);
    pContext->pContext = new IndirectDeviceContext(Device);
    return Status;
}

There are several important operations here:

  1. The power function set by IddSampleDeviceD0Entry indicates that the D0 state has entered.
  2. IddCxDeviceInitConfigSet the configuration information of IddCx.
  3. IddCxDeviceInitialize initializes the WDF device (should tell the framework what callback functions are).

In this function, the main function is to set the callback interface of IDD_CX_CLIENT_CONFIG. The system framework creates different objects at different times through the callback interface.

IDD_CX_CLIENT_CONFIG contains all display driver callback functions. The structure is declared as follows:

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;
};

In the above callback functions, their respective functions are as follows:

  1. IddSampleDeviceD0Entry indicates that the device has entered the working state. Generally, we create the IDDCX_ADAPTER adapter object here.
  2. IddSampleAdapterInitFinished means that the IddCxAdapterInitAsync function has completed creating the adapter object.
  3. IddSampleParseMonitorDescription means parsing the EDID of the display to obtain related modes.
  4. IddSampleMonitorGetDefaultModes means to get the default mode of the monitor.
  5. EvtIddCxMonitorQueryTargetModes A collection of modes supported by the query driver.
  6. IddSampleAdapterCommitModesThe callback function of commit mode.
  7. IddSampleMonitorAssignSwapChainCallback function created by desktop image swap chain.
  8. IddSampleMonitorUnassignSwapChainCallback function for desktop image swap chain destruction.

The creation of the virtual display is completed through the IddCxMonitorCreate function. Generally, we can call it in the IddSampleAdapterInitFinished callback after the display adaptation object is created. For example, the example code is as follows:

void IndirectDeviceContext::FinishInit(UINT ConnectorIndex)
{<!-- -->
    WDF_OBJECT_ATTRIBUTES Attr;
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( & amp;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( & amp;MonitorInfo.MonitorContainerId);

    IDARG_IN_MONITORCREATE MonitorCreate = {<!-- -->};
    MonitorCreate.ObjectAttributes = &Attr;
    MonitorCreate.pMonitorInfo = & amp;MonitorInfo;
    IDARG_OUT_MONITORCREATE MonitorCreateOut;
    NTSTATUS Status = IddCxMonitorCreate(m_Adapter, & amp;MonitorCreate, & amp;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, & amp;ArrivalOut);
    }
}

5. About the installation of IDD driver

First of all, IDD is a user-mode driver. This driver relies on the WUDFRd.sys kernel-mode driver to complete its functions. Therefore, the service specified when installing IDD is WUDFRd. The installation INF is as follows:

[MyDevice_Install.NT.Services]
AddService=WUDFRd,0x000001fa,WUDFRD_ServiceInstall

[WUDFRD_ServiceInstall]
DisplayName = %WudfRdDisplayName%
ServiceType = 1
StartType=3
ErrorControl=1
ServiceBinary = %\WUDFRd.sys

In addition, you also need to install the UMDF driver

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

Therefore, when we start, the driver that is started is WUDFRd.sys. It can be seen from this that the UMDF driver framework relies on the kernel layer driver to pass the kernel layer information to the user layer.

WUDFRd.sys can only be used as a very basic WUDF driver framework. The real IDD framework is completed by IndirectKmd. During the installation process, we pass UpperFilters to form a device stack to achieve the purpose of IndirectKmd operation, as follows:

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

6. About device mounting and operation

After the installation is complete, the following device objects are provided in the kernel driver WUDFRd:

  1. A separate device object communicates with the user layer driver (generally using IOCONTROL).
  2. Form device mounting with IndirectKmd driver.

This device mount includes the following:

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.

That is to say, the message of the \Driver\IndirectKmd driver (generally the device object is in Dxgkrnl) is passed to \Driver\WudfRd through the device stack, and the driver \Driver\ WudfRd then passes the message to IDD.

For example we can look at the driver’s call stack:

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

Here we can find two device objects:

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.

When sending the IRP to the user layer for processing, the call stack at this time is as follows:

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. Other questions

7.1 About IddCxAdapterInitAsync failure

If we use the IDD example directly, IddCxAdapterInitAsync this function will return failure, this function returns STATUS_NOT_SUPPORTED

#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL)

Why does this problem occur? Because the initialization function IddAdapter::Init in IddCxAdapterInitAsync needs to verify the signature information:

There are two call operations here:

  1. IddAdapter::IsSwDevice This determines whether it is a SW (SoftWare Device) device (it is said that if you install IDD to your own virtual bus, this function will return failure).
  2. DriverSigning::IsDriverWindowsSigned Determines whether to sign (see the exception thrown below which should be Microsoft signature).

So here are two solutions:

  1. Mount IDD to your own virtual bus (some netizens can implement it, but I have not verified it).
  2. Install the test certificate, then enable test mode (this method is relatively simple)/formally sign.

8. Realize the effect

Through the above technical analysis, we can use IDD to implement a virtual display. For example, in the following example, I can create a virtual display in the virtual machine and set it as an extended screen. Then I can use the extended screen of the virtual display like a normal extended screen, as follows: