Windows虚拟显示器MttVDD源码分析 (4) 间接设备上下文 (IndirectDeviceContext)

在上一章 驱动回调与入口点 (WDF/IddCx Callbacks)中,我们建立了驱动程序与 Windows 操作系统之间的联系------回调函数。我们现在知道,当系统需要我们做事时,它会呼叫我们指定的"服务员"(回调函数)。

但是,这里有一个问题:我们的"服务员"团队虽然知道在什么时候该做什么,但他们彼此之间是独立的。AssignSwapChain 回调如何知道要为哪个显示器工作?CreateMonitor 回调又如何知道它应该属于哪个虚拟显卡?如果每个"服务员"都各自为政,没有一个统一的指挥中心,整个"餐厅"很快就会陷入混乱。

这就是本章要介绍的核心概念------IndirectDeviceContext,我们驱动程序的"总指挥部"。

为什么需要一个"总指挥"?

想象一个庞大的建筑项目。有负责打地基的团队,有负责砌墙的团队,还有负责安装水电的团队。如果他们之间没有任何沟通,也没有一个总项目经理来协调,会发生什么?墙可能砌在了错误的地基上,水电管道也可能穿过了不该穿的地方。

在驱动程序中,那些回调函数就像是这些独立的施工团队。IndirectDeviceContext 就是这个项目的总项目经理总指挥

它是一个 C++ 类(class),我们为每一个创建的虚拟显卡设备都实例化一个 IndirectDeviceContext 对象。这个对象就像是为该项目专门设立的"指挥部办公室",它包含了与这个虚拟显卡相关的所有状态、数据和核心操作。

  • 状态和数据 :它知道自己管理的虚拟显卡 (m_Adapter) 和虚拟显示器 (m_Monitor) 的句柄(Handle)。
  • 核心操作 :它定义了如何创建显示器 (CreateMonitor)、如何处理图像数据 (AssignSwapChain) 等具体方法。

通过这种方式,当一个回调函数被系统调用时,它的首要任务就是找到对应的"总指挥部"(IndirectDeviceContext 实例),然后由这个总指挥来统一调度和执行任务。这确保了所有操作都是围绕着同一个虚拟设备进行的,避免了混乱。

"总指挥部"的蓝图:IndirectDeviceContext

IndirectDeviceContext 的设计蓝图定义在 Driver.h 头文件中。让我们来看一个简化版的定义,了解一下这个"指挥部"里都有哪些关键部门和文件。

cpp 复制代码
// 文件: Driver.h

class IndirectDeviceContext
{
public:
    // 构造函数:当"指挥部"建立时调用
    IndirectDeviceContext(_In_ WDFDEVICE WdfDevice);

    // 初始化虚拟显卡
    void InitAdapter();
    // 创建一个虚拟显示器
    void CreateMonitor(unsigned int index);

    // 分配交换链,准备接收图像
    void AssignSwapChain(IDDCX_MONITOR& Monitor, IDDCX_SWAPCHAIN SwapChain, ...);
    // 释放交换链,停止接收图像
    void UnassignSwapChain();

protected:
    // 指向 WDF 设备对象的句柄,像是通往上级(WDF框架)的电话线
    WDFDEVICE m_WdfDevice;
    // 虚拟显卡的句柄,像是项目的总档案
    IDDCX_ADAPTER m_Adapter;
    // 虚拟显示器的句柄,像是具体显示器的分档案
    IDDCX_MONITOR m_Monitor;

    // 指向交换链处理器的指针,这是负责处理图像数据的"专家"
    std.unique_ptr<SwapChainProcessor> m_ProcessingThread;

public:
    // 存储准备好的 EDID 数据
    static std::vector<BYTE> s_KnownMonitorEdid;
};

这个类的结构非常清晰:

  • 成员变量 (m_...) :这些是"指挥部"里存放的核心资料。m_WdfDevicem_Adapterm_Monitor 都是句柄 (Handle) ,你可以把它们理解为访问系统资源的"钥匙"或"ID卡"。例如,当你想对虚拟显卡进行操作时,你就需要出示 m_Adapter 这张"ID卡"。m_ProcessingThread 则指向一个专门处理图像流的对象,我们将在下一章 交换链处理器 (SwapChainProcessor) 中详细探讨它。
  • 成员函数/方法 :这些是"总指挥"能下达的命令,比如 InitAdapter()("项目启动!")和 CreateMonitor()("开始建造一个新显示器!")。

"指挥部"的创建与关联

我们已经有了"总指挥部"的设计蓝-图,但它是在何时何地被建造起来的呢?又是如何与我们的虚拟设备关联起来的呢?

答案就在 VirtualDisplayDriverDeviceAdd 这个回调函数中。我们在上一章讲到,当系统"发现"我们的虚拟显卡设备时,会调用这个函数。这正是为这个新设备建立"指挥部"的最佳时机。

Windows 驱动框架 (WDF) 有一个非常巧妙的机制,叫做上下文空间 (Context Space) 。它允许我们把任何自定义的数据结构(比如我们的 IndirectDeviceContext 对象)"附加"到一个系统对象(比如 WDFDEVICE)上。就像是给一个标准的政府大楼(WDFDEVICE)旁边加盖一间我们自己的办公室。

1. 声明"办公室"类型

首先,我们需要告诉 WDF 框架,我们将要附加的"办公室"是什么样的。

cpp 复制代码
// 文件: Driver.cpp

// 一个简单的包装结构,用于存放指向我们上下文对象的指针
struct IndirectDeviceContextWrapper
{
    IndirectDeviceContext* pContext;
    // ... 清理函数 ...
};

// 使用宏来声明这个包装结构可以作为 WDF 对象的上下文
WDF_DECLARE_CONTEXT_TYPE(IndirectDeviceContextWrapper);

2. 建造并附加"办公室"

然后,在 VirtualDisplayDriverDeviceAdd 函数中,我们进行实际的建造和附加操作。

cpp 复制代码
// 文件: Driver.cpp - VirtualDisplayDriverDeviceAdd 函数内

NTSTATUS VirtualDisplayDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{
    // ... 其他初始化代码 ...

    WDF_OBJECT_ATTRIBUTES Attr;
    // 关键步骤:告诉 WDF 我们要为设备对象附加一个 IndirectDeviceContextWrapper 类型的上下文
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectDeviceContextWrapper);

    WDFDEVICE Device = nullptr;
    // 创建设备对象,并根据 Attr 的设置,为其预留上下文空间
    WdfDeviceCreate(&pDeviceInit, &Attr, &Device);

    // ... 其他初始化 ...
    
    // 从刚创建的设备中,获取指向我们上下文空间的指针
    auto* pContextWrapper = WdfObjectGet_IndirectDeviceContextWrapper(Device);
    // 在上下文中,真正创建我们的"总指挥"对象
    pContextWrapper->pContext = new IndirectDeviceContext(Device);

    return STATUS_SUCCESS;
}

这个过程可以分解为三步:

  1. 规划 :通过 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 告诉 WDF 我们需要一块地方来放 IndirectDeviceContextWrapper
  2. 建造WdfDeviceCreate 不仅创建了标准的设备对象,还在旁边为我们预留了那块"上下文空间"。
  3. 入驻 :通过 WdfObjectGet_IndirectDeviceContextWrapper 拿到这块空间的"钥匙",然后 new IndirectDeviceContext(Device),正式让我们的"总指挥"入驻。

回调函数如何找到"总指挥"?

现在,我们的"总指挥部"已经和设备对象牢牢地绑定在了一起。当任何一个与显示相关的回调函数被 IddCx 框架调用时,系统总会提供一个句柄(比如 IDDCX_ADAPTERIDDCX_MONITOR)作为参数。

这些句柄本身也是 WDF 对象,它们都与最初的设备对象相关联。这意味着,我们可以从任何一个这样的句柄出发,找到我们附加的上下文。

下面的流程图展示了一个回调函数(例如 VirtualDisplayDriverAdapterInitFinished)如何找到并命令"总指挥"工作的过程。

sequenceDiagram participant IddCx as IddCx 框架 participant Callback as 回调函数 participant Context as IndirectDeviceContext (总指挥) participant WDF_Object as WDF 对象 (设备/适配器) IddCx->>Callback: 调用回调,并传入 AdapterObject 句柄 Callback->>WDF_Object: 使用 AdapterObject 句柄请求上下文 Note right of Callback: 调用 WdfObjectGet_IndirectDeviceContextWrapper(AdapterObject) WDF_Object-->>Callback: 返回 pContext (指向"总指挥"的指针) Callback->>Context: 调用 pContext->FinishInit() 方法 Context->>Context: 执行 FinishInit() 内部逻辑
(例如,调用 CreateMonitor)

让我们看看实际的代码,这非常简单直接:

cpp 复制代码
// 文件: Driver.cpp

// 当适配器初始化完成时,IddCx 调用此回调
NTSTATUS VirtualDisplayDriverAdapterInitFinished(
    IDDCX_ADAPTER AdapterObject, 
    const IDARG_IN_ADAPTER_INIT_FINISHED* pInArgs
)
{
    // 1. 通过传入的 AdapterObject 句柄,找到我们的"总指挥"
    auto* pContextWrapper = WdfObjectGet_IndirectDeviceContextWrapper(AdapterObject);
    IndirectDeviceContext* pDeviceContext = pContextWrapper->pContext;

    if (NT_SUCCESS(pInArgs->AdapterInitStatus))
    {
        // 2. 命令"总指挥"完成剩下的初始化工作
        pDeviceContext->FinishInit();
    }
    
    return STATUS_SUCCESS;
}

看到这个模式了吗?几乎所有的回调函数都遵循这个模式

  1. 接收一个系统传入的句柄。
  2. 通过该句柄获取 IndirectDeviceContext 的实例。
  3. 调用该实例上的一个方法来执行实际的工作。

这种设计模式将无状态的 C 风格回调函数与面向对象的、有状态的 C++ 类完美地结合在了一起,使得驱动程序的逻辑清晰、易于管理。

总结

在本章中,我们认识了 MttVDD 驱动程序的核心大脑------IndirectDeviceContext 类。我们学到了:

  • 为什么需要它:为了集中管理一个虚拟显卡设备的所有状态和操作,避免在分散的回调函数中造成混乱。它就像一个项目的"总指挥"。
  • 它的构成 :它包含了指向 WDF 设备、虚拟显卡和显示器等核心资源的句柄(成员变量),以及操作这些资源的方法(成员函数)。
  • 它的创建时机 :它在 VirtualDisplayDriverDeviceAdd 回调函数中被创建,并通过 WDF 的上下文空间 机制,"附加"到代表我们虚拟设备的 WDFDEVICE 对象上。
  • 如何使用它 :任何回调函数在被调用时,都可以通过系统传入的句柄,方便地获取到与之关联的 IndirectDeviceContext 实例,然后调用其方法来执行具体任务。

IndirectDeviceContext 为我们提供了一个坚实的框架,所有的功能都可以在这个"指挥部"中有条不紊地进行。它就像是舞台的总导演,而它所指挥的最重要的一个演员,就是负责处理每一帧桌面图像的"交换链处理器"。

在下一章中,我们将深入了解这个负责接收和处理真实桌面图像数据的关键组件。

相关推荐
小xin过拟合27 分钟前
day20 二叉树part7
开发语言·数据结构·c++·笔记·算法
EstrangedZ37 分钟前
vscode(MSVC)进行c++开发的时,在debug时查看一个eigen数组内部的数值
c++·ide·vscode
乌萨奇也要立志学C++1 小时前
【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
c++·哈希算法·散列表
易我数据恢复大师2 小时前
怎么把iphone文件传输到windows电脑?分场景选方法
windows·iphone·iphone文件传输·iphone文件传输到电脑·iphone传输文件
Forward♞2 小时前
Qt——网络通信(UDP/TCP/HTTP)
开发语言·c++·qt
青草地溪水旁3 小时前
`lock()` 和 `unlock()` 线程同步函数
linux·c++·c
重启的码农3 小时前
Windows虚拟显示器MttVDD源码分析 (3) 驱动回调与入口点 (WDF/IddCx Callbacks)
c++·windows·操作系统
重启的码农3 小时前
Windows虚拟显示器MttVDD源码分析 (2) EDID与显示器模拟
c++·windows·操作系统
重启的码农3 小时前
Windows虚拟显示器MttVDD源码分析 (1) 配置与设置管理
c++·windows·操作系统