[授权原创][IDD][本站难得一见的正经文] Chapter 1: 从 -1 开始做一个基于 IDD 驱动的虚拟显示设备

写在开坑前

其实算算上次写技术文已经差不多过去三四个春秋了, 其实本来觉得像我这种没啥文采还废话连篇的下等作者早该尽早自行了断免得误了看官, 不曾想为了捧个人场又来这厚颜无耻, 既然坑开了, 那七七八八总得唠两句以示诚意.

为什么写 IDD?

讲真的, 最近能写的玩意儿真不少, 但是 IDD 是我觉得最值得一写的, 主要原因在于只答应了 披头哥 短短三篇, 所以这次开的 IDD 坑定然是草草三篇收场, 当然这已经足以参透 IDD 了.

如果有条件的话, 建议参考 Github 上的 Microsoft/Windows-driver-sample. 微软近几年的文档无论从完善度还是清晰度来说, 都非常值得作为 产品级 的参考基础. 当然了, 配合本文肯定能大大的减轻阅读负担.

我想要代码!

很抱歉, 我不能.

由于 IDD 所开发的项目已经商用, 所以我并不能提供完整的源代码, 但是如果结合上述的开源范例, 我相信你还是可以很轻松的开发出一个切实可用的 IDD 驱动.

章节一眼看

我大致的将 IDD 的实现按照功能分为下面的三个章节

  1. [本文] 初始化一个没有功能的 IDD 壳子.
  2. 通过 IDD 来创建显示器.
  3. 获取显示器的输出内容.

你可以根据当前对 IDD 的了解情况来选择性的进行阅读.

课前预习

本着以人为本的原则, 也为了避免半途而废带来的挫败感, 在开始阅读前, 我准备了一份简单的条件自测, 如果您满足这些条件, 那么恭喜你, 踩过这个坑对你来说可能不是难事, 如果你并不具备这些条件但是自诩智慧过人, 那么我相信硬着头皮看完之后可能就能 Get 到这些能力了.

  1. 使用 VC++ 完成一些中等复杂程度的应用开发.
  2. 听说过 DirectX 并且知道他是干啥的.
  3. 打开过 Windows "设备管理器", 知道如何通过 "设备管理器" 安装/更换设备驱动.
  4. 了解过 WDK 甚至用过 WDK 写一个啥功能都没的无聊驱动.
  5. 把电脑搞蓝屏过, 知道如何通过安全模式修好驱动导致的蓝屏.
  6. 知道 Microsoft(微软) 别名 Hugehard(巨硬)
  7. 基础的英文阅读能力, 或者掌握百度翻译的入门级使用.

好戏开场, 什么是 IDD ?

IDD 全名 Indirect Display Driver 是一个诞生于 2016 年的新家伙, 比起十数年前就已经面世的 WDDM 来说, 无论是 代码可读性 还是 逻辑清晰度 简直是 Too Young Too Simple, 再加上有 WDF 的加持, 开发显卡过滤驱动这件事情从未如此简单.

通过 IDD 我们可以做到许多曾经需要通过 不正经 方法才能实现的功能需求, 如: 驱动级屏幕采集, 虚拟显示器.

IDD 还有三秒到达战场

严格意义上来说, IDD 并不是一个可以独立工作的系统组件, 它更像是一个开发框架, 巨硬 帮助你做完了一些复杂的底层工作, 而你负责享受 框架 完成的各种便利接口.

IDD 本身基于 IndirectKmd.sys 驱动, 你可以方便的在 *\SystemRoot\System32\drivers* 中找到他的本体, 我们一切使用 IDD 开发的"驱动"都会先行加载这个驱动, 如果用一个不太负责的例子来说明的话

我们的驱动程序需要依赖 IndirectKmd.sys 这个动态链接库.

与世上其他的妖艳贱货应用一样的是, IDD 也有一套内部的执行顺序, 系统通过回调的方式将其串联, 我简单将其分为四大块: 电源管理, 显卡设备功能, 显示设备功能, 显示功能.

实际 IDD 在执行时, 遵照的顺序也差不多如下:

graph LR
创建WDF驱动-->创建WDF设备-->电源初始化:上电-->初始化显示设备-->初始化SwapChain

0. 万事开头难....吗? 简单的创建 WDF 驱动

就像一个正常的驱动一样, IDD 驱动的入口函数也是 DriverEntry, 所有在 DriverEntry 中线性执行的工作就叫他: 第 0 阶段吧.

三行伪代码便可以清楚地描述这个工作:

DriverEntry(driverObject, registryPath) {
  attributes = WDF_OBJECT_ATTRIBUTES_INIT();
  config = WDF_DRIVER_CONFIG_INIT(Device::Initialize);
  WdfDriverCreate(driverObject, registryPath, attributes, config, WDF_NO_HANDLE);
}

如果足够的眼尖, 你肯定一眼就看出了下一步的回调方法 Device::Initialize. 系统会在完成 WDF 初始化后调用这个方法, 我们需要在这个函数当中提供设备功能以及电源管理的 回调函数表. 做完这些个之后, 一个合格的 WDF 驱动就算是完成了.

1. 先有鸡还是先有蛋? 创建 WDF 设备

我们通常认为: 先有设备再有驱动, 可是在 WDF 的体系中并不是这样的, 我们已经在上一步中创建了 WDF 驱动, 接下来需要一个 WDF 设备.

以下是一份 Device::Initialize 的伪代码:

Device::Initialize(driverObject, deviceInit) {
  pnpPowerCallbacks = WDF_PNPPOWER_EVENT_CALLBACKS_INIT();
  pnpPowerCallbacks.EvtDeviceD0Entry = Device::PowerOn;
  WdfDeviceInitSetPnpPowerEventCallbacks(deviceInit, pnpPowerCallbacks);
  
  iddFunctions = IDD_CX_CLIENT_CONFIG_INIT();
  iddFunctions.EvtIddCxAdapterInitFinished = Device::Ready;
    iddFunctions.EvtIddCxAdapterCommitModes = Device::CommitModes;
    iddFunctions.EvtIddCxParseMonitorDescription = Monitor::ParseDescription;
    iddFunctions.EvtIddCxMonitorGetDefaultDescriptionModes = Monitor::GetDefaultModes;
    iddFunctions.EvtIddCxMonitorQueryTargetModes = Monitor::QueryModes;
    iddFunctions.EvtIddCxMonitorAssignSwapChain = Monitor::AssignSwapChain;
    iddFunctions.EvtIddCxMonitorUnassignSwapChain = Monitor::UnassignSwapChain;
  
  IddCxDeviceInitConfig(deviceInit, iddFunctions);
  device = WdfDeviceCreate(deviceInit);
  IddCxDeviceInitialize(device)
}

显而易见的是, 我们在这个步骤做了两件事: 注册了电源回调 以及 注册了功能回调.

EvtDeviceD0Entry 事件是下一步的主角, D0 是设备上电状态, 其余事件我们可以暂且按下不表.

2. 来电了之后咋整? 起床干活!

既然有了电, 那是时候向系统展示我们真正的技术了, 这时候我们需要告知系统我们能做啥, 当然也得向系统汇报一下我们是谁.

Device::PowerOn(WDFDevice) {
  capability.MaxMonitorsSupported = GRAPHIC_CARD_MAX_MONITORS;
  capability.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE;
  capability.EndPointDiagnostics.TransmissionType = IDDCX_TRANSMISSION_TYPE_WIRED_OTHER;
  capability.EndPointDiagnostics.pEndPointFriendlyName = GRAPHIC_CARD_NAME;
  capability.EndPointDiagnostics.pEndPointManufacturerName = GRAPHIC_CARD_MANUFACTURER;
  capability.EndPointDiagnostics.pEndPointModelName = GRAPHIC_CARD_MODEL;

  firmwareVersion.MajorVer = 2;
  firmwareVersion.MinorVer = 0;
  capability.EndPointDiagnostics.pFirmwareVersion.MajorVer = firmwareVersion;
  capability.EndPointDiagnostics.pHardwareVersion.MinorVer = firmwareVersion;
  
  init.WdfDevice = _WDFDevice;
  init.pCaps = &capability;
  
  IddCxAdapterInitAsync(init);
}

具体字段的用途就不阐述了, 有兴趣的可以自行百度翻译, 巨硬 的字段名还是相对比较容易理解的.

重点在于 IddCxAdapterInitAsync, 明眼人一看就知道, 这是一个异步方法, 异步方法就一定有回调, 那么回调是哪个呢?

显然帮助你们踩坑的我一直提前知道了答案, 那就是 EvtIddCxAdapterInitFinished, 对应着我们的 Device::Ready.

其实也好理解, 来电了, 设备初始化了, 下一步就是设备就绪了.

3. 玩家准备就绪! 全军出击.

这一步是设备初始化完成的最后一步, 也是下一章节的序幕, 我们需要在这里创建具体的显示设备, 也就是显示器.

我们在此祭出一段非常简单的代码方便下一章的开篇:

Device::Ready(adapterObject, pInArgs) {
  while(MonitorsToCreate --) {
    Monitor::Initialize(MonitorsToCreate, adapterObject);
  }
}

上例中我们简单的创建固定数量的显示设备仅做范例, 在此先不展开具体细节只提一嘴: 你该准备一下要创建哪些显示器了.

大部分用于生产系统的应用程序都不可能将这些重要的参数 Hard-Coded 在二进制文件中, 所以您可以在文末思考一下从哪获取这些.

一个可行的方案是从配置文件读取, 只不过如何更新配置文件就留给你们自行发挥了.

末尾叨叨

作为闰更作者, 打算来个 Triple Kill 也是付出了老大的勇气的.

如果确实对文中的代码存疑或者确实需要帮助, 欢迎邮箱联系 nvmjs#soxos.me, 除了伸手要全套服务的, 我一定给到力所能及的帮助.

本文采用 CC BY-NC-SA 4.0 协议进行许可
原文地址: soxos.m2d.in/go/tzYAA. 首发于 nvmjs.com 且已经获得原作者许可.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350