Windows虚拟显示器MttVDD源码分析 (1) 配置与设置管理

欢迎来到 MttVDD 的世界!这是一个强大的虚拟显示驱动程序项目,可以让你在 Windows 系统中创造出"看不见"的显示器。在本系列教程中,我们将一步步揭开它神秘的面纱。

作为我们的第一站,我们将探索 MttVDD 的"大脑记忆库"------配置与设置管理系统。

为什么需要配置管理?

想象一下,你买了一台新电脑,但它的所有设置,比如屏幕亮度、音量大小、桌面壁纸,全都被焊死在了主板上,永远无法更改。这听起来是不是很糟糕?

驱动程序也是一样。如果没有一个灵活的配置系统,MttVDD 创造的虚拟显示器数量、支持的分辨率、是否开启 HDR 等特性都将被"写死"在代码里。每次你想创建一个 4K 而不是 1080p 的显示器,都必须修改代码、重新编译、然后重新安装驱动。这显然太麻烦了!

配置与设置管理 系统就是为了解决这个问题而存在的。它允许驱动程序在启动时,从外部文件(主要是 vdd_settings.xml)和 Windows 注册表中读取所有配置,就像电脑每次开机都会加载你保存好的设置一样。这使得驱动程序的行为可以被用户轻松定制,实现了高度的灵活性。

核心概念:配置的来源

MttVDD 的配置主要有两个来源,它们之间存在一个优先级关系:

  1. vdd_settings.xml 文件(主要来源) :这是最常用、最推荐的配置方式。它是一个位于驱动安装目录(通常是 C:\VirtualDisplayDriver)下的 XML 文件,你可以用任何文本编辑器打开并修改它。它的结构清晰,易于理解。

  2. Windows 注册表(高优先级来源) :这是一个更高级的配置方式。存储在注册表中的设置项会覆盖 vdd_settings.xml 文件中的同名设置。这通常用于安装程序自动配置或由配套的控制软件进行临时修改,普通用户很少直接操作它。

这个"注册表优先"的设计非常巧妙。比如,vdd_settings.xml 里设置了创建 1 个显示器,但你可以通过配套软件下达一个临时指令,在注册表中写入"创建 3 个显示器",驱动重启后就会创建 3 个。当你撤销这个临时指令(删除注册表项)后,驱动又会恢复读取 XML 中的设置,变回 1 个显示器。

MttVDD 是如何加载设置的?

MttVDD 驱动程序被加载时,它会像一个勤劳的图书管理员,立即开始整理它的"记忆库"。这个过程主要发生在 Driver.cpp 文件的 DriverEntry 函数中,这是驱动程序的第一个入口点。

1. 将设置载入"内存"

驱动会调用一系列查询函数,比如 EnabledQueryGetIntegerSetting 等,从配置文件和注册表中读取每一项设置,并把它们存放在全局变量中。这些全局变量就像是大脑中的短期记忆,在驱动运行期间随时可以被访问。

来看看 Driver.cpp 中定义这些"短期记忆"的地方:

cpp 复制代码
// Driver.cpp

// ... (省略其他代码)

// 全局变量,用于存储加载的设置
UINT numVirtualDisplays; // 要创建的虚拟显示器数量
bool logsEnabled = false; // 是否启用日志
bool debugLogs = false; // 是否启用调试日志
bool HDRPlus = false; // 是否开启 HDR+
// ... 还有很多其他的设置项

// ... (省略其他代码)

这些变量在被赋予从文件中读取的值之前,都有一个默认值(例如 false0)。

2. DriverEntry:一切的起点

现在,让我们看看 DriverEntry 函数是如何启动这个加载过程的。这是一个简化版的代码片段:

cpp 复制代码
// Driver.cpp - DriverEntry 函数

extern "C" NTSTATUS DriverEntry(
	PDRIVER_OBJECT  pDriverObject,
	PUNICODE_STRING pRegistryPath
)
{
	// ... (省略了WDF驱动初始化代码)

	// 从配置文件和注册表加载各种开关设置
	logsEnabled = EnabledQuery(L"LoggingEnabled");
	debugLogs = EnabledQuery(L"DebugLoggingEnabled");
	customEdid = EnabledQuery(L"CustomEdidEnabled");

	// 加载颜色相关的设置
	HDRPlus = EnabledQuery(L"HDRPlusEnabled");
	SDR10 = EnabledQuery(L"SDR10Enabled");
	
	// ... (加载更多设置)

	// ... (省略了创建驱动对象的代码)

	return Status;
}

正如你所见,DriverEntry 函数在执行任何重要操作之前,就通过 EnabledQuery 等函数把配置项一个个地加载到对应的全局变量中。这样,驱动程序在其生命周期内的任何时刻,都可以通过访问 logsEnabledHDRPlus 等变量来决定自己的行为。

深入探索:设置是如何被查询的

我们已经知道驱动通过 EnabledQuery 这样的函数来获取设置,但这些函数内部又是如何工作的呢?它们如何知道要去哪里查找,以及如何处理优先级?

答案藏在一个名为 SettingsQueryMap 的"字典"里。

SettingsQueryMap:配置的翻译官

SettingsQueryMap 是一个 C++ std::map 结构,它定义在 Driver.cpp 的顶部。它就像一个翻译官,告诉查询函数,一个内部的设置名(比如 LoggingEnabled)对应到注册表和 XML 文件里分别叫什么名字。

cpp 复制代码
// Driver.cpp

std::map<std::wstring, std::pair<std::wstring, std::wstring>> SettingsQueryMap = {
	// 内部名称              {注册表名称,       XML标签名}
	{L"LoggingEnabled",    {L"LOGS",           L"logging"}},
	{L"DebugLoggingEnabled", {L"DEBUGLOGS",      L"debuglogging"}},
	{L"HDRPlusEnabled",    {L"HDRPLUS",        L"HDRPlus"}},
	// ... 更多设置项
};

有了这张"翻译表",EnabledQuery(L"LoggingEnabled") 就知道:

  • 在注册表里,我应该找一个叫 LOGS 的值。
  • 在 XML 文件里,我应该找一个名叫 <logging> 的标签。

查询流程揭秘

现在,让我们用一个流程图来展示 EnabledQuery 函数的完整工作流程。假设我们要查询 LoggingEnabled 这个设置。

sequenceDiagram participant Driver as 驱动代码 participant Query as EnabledQuery函数 participant Reg as Windows注册表 participant XML as vdd_settings.xml Driver->>Query: 查询 "LoggingEnabled" Query->>Query: 在 SettingsQueryMap 中查找 "LoggingEnabled" Note right of Query: 找到注册表名 "LOGS"
找到 XML 名 "logging" Query->>Reg: 尝试读取 "LOGS" 的值 alt 注册表中存在 "LOGS" Reg-->>Query: 返回 "LOGS" 的值 (例如 1) Query-->>Driver: 返回 true else 注册表中不存在 Reg-->>Query: 未找到 Query->>XML: 尝试读取 标签的值 XML-->>Query: 返回 "true" Query-->>Driver: 返回 true end

这个流程清晰地展示了"注册表优先"的原则。接下来,我们看看简化后的代码实现。

cpp 复制代码
// Driver.cpp - EnabledQuery 函数的简化逻辑

bool EnabledQuery(const std::wstring& settingKey) {
    // 1. 从 SettingsQueryMap 中查找注册表和 XML 的名字
	auto it = SettingsQueryMap.find(settingKey);
	std::wstring regName = it->second.first;
	std::wstring xmlName = it->second.second;

    // 2. 尝试从注册表读取
	HKEY hKey;
	LONG lResult = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"...", 0, KEY_READ, &hKey);
	if (lResult == ERROR_SUCCESS) {
		// ... 读取注册表值的代码 ...
		if (成功读取) {
			RegCloseKey(hKey);
			return true; // 如果读取成功,直接返回
		}
		RegCloseKey(hKey);
	}

    // 3. 如果注册表没有,则从 XML 文件读取 (这是备用方案)
    // ... 使用 IXmlReader 打开和解析 vdd_settings.xml 的代码 ...
    // ... 寻找到名为 xmlName 的标签并读取其内容 ...
	
	return xmlLoggingValue; // 返回从 XML 中读取的值
}

这个函数完美地实现了我们之前描述的逻辑。GetIntegerSettingGetStringSetting 等其他查询函数也遵循着类似的逻辑,只是它们返回的数据类型不同。

loadSettings:加载显示模式

除了简单的开关和数值设置,驱动还需要知道要创建哪些显示模式(即分辨率和刷新率的组合)。这些复杂的列表式信息由 loadSettings 函数专门负责处理。

这个函数会在驱动设备被创建时(VirtualDisplayDriverDeviceAdd 内部)调用。它会解析 vdd_settings.xml 中关于显示器数量和分辨率的部分。

下面是一个 vdd_settings.xml 的例子:

xml 复制代码
<!-- C:\VirtualDisplayDriver\vdd_settings.xml -->
<vdd_settings>
    <displays>
        <count>2</count> <!-- 创建2个显示器 -->
    </displays>
    <resolutions>
        <resolution>
            <width>1920</width>
            <height>1080</height>
            <refresh_rate>60</refresh_rate>
        </resolution>
        <resolution>
            <width>2560</width>
            <height>1440</height>
            <refresh_rate>120</refresh_rate>
        </resolution>
    </resolutions>
    <!-- ... 其他设置 ... -->
</vdd_settings>

loadSettings 函数会读取这个文件,然后:

  1. numVirtualDisplays 全局变量设置为 2
  2. 将两个分辨率和刷新率组合存入 monitorModes 这个全局 vector 向量中。

之后,在创建显示器的阶段,驱动就会读取 numVirtualDisplays 来决定创建几个显示器,并使用 monitorModes 中的数据来告诉 Windows 每个显示器支持哪些模式。这部分内容将在后续的 EDID与显示器模拟 章节中详细介绍。

总结

在本章中,我们学习了 MttVDD 的配置与设置管理系统。我们了解到:

  • 为什么需要配置:为了使驱动程序灵活可定制,避免硬编码。
  • 配置的来源 :主要是 vdd_settings.xml 文件,同时支持使用 Windows 注册表进行高优先级覆盖。
  • 加载时机 :所有配置在驱动启动时(DriverEntry)一次性加载到全局变量中,作为驱动运行期间的"记忆"。
  • 核心机制 :通过 SettingsQueryMap 这个"翻译字典",查询函数(如 EnabledQuery)能够统一地从注册表和 XML 中读取设置。
  • 专门的模式加载loadSettings 函数负责从 XML 中读取复杂的显示模式列表。

这个强大的配置系统是 MttVDD 所有高级功能的基础。没有它,我们将无法自由地定制虚拟显示器的行为。

现在,我们已经知道了驱动是如何"记住"我们想要创建什么样的显示器的。在下一章中,我们将学习驱动是如何将这些愿望变成现实,并让 Windows 系统真正识别和相信我们创造出的虚拟显示器。

相关推荐
乌萨奇也要立志学C++1 小时前
【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
c++·哈希算法·散列表
易我数据恢复大师1 小时前
怎么把iphone文件传输到windows电脑?分场景选方法
windows·iphone·iphone文件传输·iphone文件传输到电脑·iphone传输文件
Forward♞2 小时前
Qt——网络通信(UDP/TCP/HTTP)
开发语言·c++·qt
青草地溪水旁2 小时前
`lock()` 和 `unlock()` 线程同步函数
linux·c++·c
重启的码农2 小时前
Windows虚拟显示器MttVDD源码分析 (3) 驱动回调与入口点 (WDF/IddCx Callbacks)
c++·windows·操作系统
重启的码农2 小时前
Windows虚拟显示器MttVDD源码分析 (4) 间接设备上下文 (IndirectDeviceContext)
c++·windows·操作系统
重启的码农3 小时前
Windows虚拟显示器MttVDD源码分析 (2) EDID与显示器模拟
c++·windows·操作系统
Warren983 小时前
Appium学习笔记
android·windows·spring boot·笔记·后端·学习·appium
秦jh_3 小时前
【MySQL】基本查询
linux·数据库·c++·mysql