• Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发


    在开发或者办公中,越大的屏幕看起来就显示越舒服了,通常我们的做法是有两块屏幕,这样显示的内容就变多了,可以很容易提高办公的效率。

    在这里插入图片描述
    在设置中显示中,如果我们有两块屏幕,在显示器中自然的会出现两个,在其中可以对两块屏幕进行相应的设置。

    在这个驱动中,我们要解决的问题是,我们没有物理的第二块屏幕,我们通过驱动的方式,虚拟出第二屏幕出来,只要我们得到第二屏幕的数据,我们很容易可以把屏幕数据流投影到想投的地方。

    关于虚拟屏幕,微软也有相应的demo。关于 Indirect Display的微软demo,可以去相应的地方找到文档,这里几个比较重要的github项目如下:

    https://github.com/superxh/VirtualDisplay

    https://github.com/RileyWen/ScreenExpander

    ScreenExpander为例,安装完驱动后,打开ConsoleDriverApplication,按n开启一个虚拟显示器。

    在这里插入图片描述
    WpfTestingClient 程序来接收视频流。

    在这里插入图片描述
    这时候我们就有一个第二屏的显示器,可以把应用拖到第二屏上去显示出来。

    在这里插入图片描述
    这样就实现了虚拟出第二显示器。

    原理都在代码中,我们稍作分析。

    还是原来的驱动入口 DriverEntry

    extern "C" NTSTATUS DriverEntry(
        PDRIVER_OBJECT  pDriverObject,
        PUNICODE_STRING pRegistryPath
    )
    {
        WDF_DRIVER_CONFIG Config;
        NTSTATUS Status;
    
        PrintfDebugString("DriverEntry \n");
        KdPrint(( " indirect" "DriverEntry\n"));
    
        WDF_OBJECT_ATTRIBUTES Attributes;
        WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
    
        WDF_DRIVER_CONFIG_INIT(&Config,
            Evt_IddDeviceAdd
        );
    
        Status = WdfDriverCreate(pDriverObject, pRegistryPath, &Attributes, &Config, WDF_NO_HANDLE);
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }
    
        return Status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    重要的函数看 Evt_IddDeviceAdd

    _Use_decl_annotations_
    NTSTATUS Evt_IddDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
    {
        NTSTATUS Status = STATUS_SUCCESS;
        WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
    
        UNREFERENCED_PARAMETER(Driver);
    
        PrintfDebugString("Evt_IddDeviceAdd\n");
    
        IDARG_OUT_GETVERSION IddCxVersion;
        IddCxGetVersion(&IddCxVersion);
        if (!NT_SUCCESS(Status)) {
            PrintfDebugString("IddCxVersion() Failed: 0x%x\n", Status);
        }
    
        PrintfDebugString("IddCx Version: 0x%lx\n", IddCxVersion.IddCxVersion);
    
        if (IDDCX_VERSION_LATEST > IddCxVersion.IddCxVersion) {
            PrintfDebugString("Error: Driver's IddCx Version 0x%lx is greater than System's 0x%lx\n",
                IDDCX_VERSION_LATEST, IddCxVersion.IddCxVersion);
        }
    
        // Register for power callbacks - in this sample only power-on is needed
        //
        WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
        PnpPowerCallbacks.EvtDeviceD0Entry = Evt_IddDeviceD0Entry;
        WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &PnpPowerCallbacks);
    
        IDD_CX_CLIENT_CONFIG IddConfig;
        IDD_CX_CLIENT_CONFIG_INIT(&IddConfig);
    
        PrintfDebugString("IddConfig.Size After Init: %lu\n", IddConfig.Size);
    
        // If the driver wishes to handle custom IoDeviceControl requests, it's necessary to use this callback since IddCx
        // redirects IoDeviceControl requests to an internal queue. This sample does not need this.
        IddConfig.EvtIddCxDeviceIoControl = Evt_IddIoDeviceControl;
    
        IddConfig.EvtIddCxParseMonitorDescription = Evt_IddParseMonitorDescription;
    
        IddConfig.EvtIddCxAdapterInitFinished = Evt_IddAdapterInitFinished;
        IddConfig.EvtIddCxAdapterCommitModes = Evt_IddAdapterCommitModes;
    
        IddConfig.EvtIddCxMonitorGetDefaultDescriptionModes = Evt_IddMonitorGetDefaultModes;
        IddConfig.EvtIddCxMonitorQueryTargetModes = Evt_IddMonitorQueryModes;
    
        IddConfig.EvtIddCxMonitorAssignSwapChain = Evt_IddMonitorAssignSwapChain;
        IddConfig.EvtIddCxMonitorUnassignSwapChain = Evt_IddMonitorUnassignSwapChain;
    
    #if IDDCX_VERSION_MINOR >= 4
        IddConfig.EvtIddCxMonitorGetPhysicalSize = Evt_IddMonitorGetPhysicalSize;
    #endif
    
        Status = IddCxDeviceInitConfig(pDeviceInit, &IddConfig);
        if (!NT_SUCCESS(Status))
        {
            PrintfDebugString("IddCxDeviceInitConfig Failed: 0x%x\n", Status);
            return Status;
        }
    
        WDF_OBJECT_ATTRIBUTES Attr;
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, AdapterWdfContext);
        Attr.EvtCleanupCallback = [](WDFOBJECT Object)
        {
            // Automatically cleanup the context when the WDF object is about to be deleted
            auto* pContext = WdfObjectGet_AdapterWdfContext(Object);
            if (pContext)
            {
                pContext->Cleanup();
            }
        };
    
        WDFDEVICE Device = nullptr;
        Status = WdfDeviceCreate(&pDeviceInit, &Attr, &Device);
        if (!NT_SUCCESS(Status))
        {
            PrintfDebugString("WdfDeviceCreate Failed!\n");
            return Status;
        }
    
        Status = WdfDeviceCreateDeviceInterface(
            Device,
            (LPGUID)&GUID_DEVINTERFACE_INDIRECT_DEVICE,
            NULL // ReferenceString
        );
        if (!NT_SUCCESS(Status)) {
            PrintfDebugString("WdfDeviceCreateDeviceInterface failed.\n");
            return Status;
        }
    
        Status = IddCxDeviceInitialize(Device);
        if (!NT_SUCCESS(Status)) {
            PrintfDebugString("IddCxDeviceInitialize Failed.\n");
            return Status;
        }
    
        PrintfDebugString("Exit Evt_IddDeviceAdd.\n");
    
        return Status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    接着看Evt_IddMonitorAssignSwapChain -> AssignSwapChain -> SwapChainProcessor->RunCore

    AcquiredBuffer.attach(Buffer.MetaData.pSurface);
    
    
                    if (!AcquiredBuffer.try_as(AcquiredTexture)) {
                        PrintfDebugString("[SwapChainProcessor::RunCore] Cannot Convert Acquired Buffer to Texture.\n");
                        goto EndOneSurfaceProcessing;
                    }
    
                    D3D11_TEXTURE2D_DESC TextureDesc;
                    AcquiredTexture->GetDesc(&TextureDesc);
    
                    //PrintfDebugString("Current Surface Width: Format: %u, Width: %u, Height: %u\n",
                    //    SurDesc.Format, SurDesc.Width, SurDesc.Height);
    
                    D3D11_MAPPED_SUBRESOURCE MappedSubResc;
                    hr = m_Device->DeviceContext->Map(AcquiredTexture.get(), 0, D3D11_MAP_READ, 0, &MappedSubResc);
                    if (FAILED(hr)) {
                        if (hr == E_INVALIDARG) {
                            D3D11_TEXTURE2D_DESC StagingTextureDesc;
                            StagingTextureDesc = TextureDesc;
                            StagingTextureDesc.Usage = D3D11_USAGE_STAGING;
                            StagingTextureDesc.BindFlags = 0;
                            StagingTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
                            StagingTextureDesc.MiscFlags = 0;
    
                            hr = m_Device->Device->CreateTexture2D(&StagingTextureDesc, nullptr, CopiedTexture.put());
                            if (FAILED(hr)) {
                                PrintfDebugString("Create Staging Texture failed: 0x%x\n", hr);
                                goto EndOneSurfaceProcessing;
                            }
    
                            m_Device->DeviceContext->CopyResource(CopiedTexture.get(), AcquiredTexture.get());
    
                            hr = m_Device->DeviceContext->Map(CopiedTexture.get(), 0, D3D11_MAP_READ, 0, &MappedSubResc);
                            if (FAILED(hr)) {
                                PrintfDebugString("Mapping Staging Texture failed: 0x%x\n", hr);
                                goto EndOneSurfaceProcessing;
                            }
    
                            AcquiredTexture = std::move(CopiedTexture);
                        }
                        else {
                            PrintfDebugString("Mapping GPU Texture failed: 0x%x\n", hr);
                            goto EndOneSurfaceProcessing;
                        }
    
                    }
    
                    // The image format is always DXGI_FORMAT_B8G8R8A8_UNORM,
                    // so the size of a frame is Height*Width*32/8 bytes.
                    DWORD dwImageSizeBytes = TextureDesc.Width * TextureDesc.Height * 32 / 8;
    
                    m_pImageBuf->dwWidth = TextureDesc.Width;
                    m_pImageBuf->dwHeight = TextureDesc.Height;
                    m_pImageBuf->dwMonitorIndex = m_pMonitorContext->MonitorIndex;
                    CopyMemory(m_pImageBuf->pData, MappedSubResc.pData, dwImageSizeBytes);
    
                    m_pMonitorContext->pAdapterContext->pAdaterClass->m_PipeServer.WriteBytes(
                        m_pImageBuf.get(),
                        sizeof(DWORD) * 2 + dwImageSizeBytes);
    
                    m_Device->DeviceContext->Unmap(AcquiredTexture.get(), 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    m_PipeServer.WriteBytes中,把数据发走。

    ScreenExpander还涉及到相关的开启和关闭,两个驱动的写法,可以参考这源码来理解。

  • 相关阅读:
    flask---》response
    第十二章 pytorch中使用tensorboard进行可视化(工具)
    用java写点题-lc104. 二叉树的最大深度
    放到WEB-INF中的文件,不能直接通过浏览器地址栏访问了
    Cadence23学习笔记(三)
    TCP详解
    国内可以免费使用的GPT
    软设上午题错题知识点4
    攻防世界WEB练习-easyupload
    九方面解读国家数据局成立,可交易数据的五大特性探讨
  • 原文地址:https://blog.csdn.net/weixin_40425640/article/details/125538801