Windows桌面实现之七(DirectX HOOK 方式截取特殊的全屏程序之一)

                                                                            by fanxiushu 2019-04-16 转载或引用请注明原始作者。
因为间隔的时间较长,为了方便查阅,下面是以前的六篇文章地址链接:
https://blog.csdn.net/fanxiushu/article/details/73269286 Windows远程桌面实现之一 (抓屏技术总览 MirrorDriver,DXGI,GDI)
https://blog.csdn.net/fanxiushu/article/details/76039801 Windows远程桌面实现之二(抓屏技术之MirrorDriver镜像驱动开发)
https://blog.csdn.net/fanxiushu/article/details/77013158 Windows远程桌面实现之三(电脑内部声音采集,录音采集,摄像头视频采集)
https://blog.csdn.net/fanxiushu/article/details/78869719 Windows远程桌面实现之四(在现代浏览器中通过普通页面访问远程桌面)
https://blog.csdn.net/fanxiushu/article/details/80996391 Windows远程桌面实现之五(FFMPEG实现桌面屏幕RTSP,RTMP推流及本地保存)
https://blog.csdn.net/fanxiushu/article/details/81905680 Windows远程桌面实现之六(新版本框架更新,以及网页HTML5音频采集通讯

这里还有一篇文章,是以HOOK 显卡的WDDM驱动的各类回调函数的办法来采集数据:
https://blog.csdn.net/fanxiushu/article/details/82731673  
WIN7以上系统WDDM虚拟显卡开发(WDDM Filter/Hook Driver 显卡过滤驱动开发之一)
这并不是一个值得推荐的办法。

对应的程序请到GITHUB下载最新版本:
https://github.com/fanxiushu/xdisp_virt


前三篇都是介绍如何采集各种数据,尤其是windows的桌面图像数据,其中GDI最通用,也最简单,其次是mirror和DXGI,
但 DXGI(其实是 DXGI Desktop Duplication API,为了简单就叫DXGI )只适合win8以上系统。

但有类特殊的程序,就是独占方式的全屏3D程序,这里不单是全屏游戏,还可能是其他独占方式的全屏程序,比如某些视频播放软件。
但是各种全屏独占程序中,以游戏最多。因此下面介绍中多以全屏游戏作为测试主要对象。
全屏独占模式,就是程序代替windows的桌面管理器,接管整个显示器的显示,
程序产生的图像数据不再经过桌面管理器的混合,而是程序直接朝显示器输出。
很早前的DDRAW图形组件就是做这种事的,从DirectX8开始取消了DDraw,但是因为使用DDRAW的老程序很多,
因此ddraw一直都存在,并且也兼容各种windows系统,包括最新的WIN10,当然越新的系统对DDRAW的功能大部分都是软件模拟。
DirectX从8 ,到9, 到10 ,再到11,各个版本中,对全屏独占方式都不曾改变。
不过最新的WIN10中,不知道是从1803还是1809开始,对全屏独占方式似乎做了很大的弱化,DirectX12似乎对全屏独占模式只是模拟。
不管怎样,在WIN7系统中,因为全屏独占模式的存在,给截取windows桌面图像带来很大麻烦。
比如最典型的,WIN7中的 Windows Media Center 程序,我对这个win7自带的程序其实并不了解,因为平时不使用它,
不过用它来测试全屏抓屏效果,倒也不错。

其实熟悉DirectX的,通过分析MediaCenter加载的DLL库,很容易看出来,
它实际上使用 Direct3D9, 不光是MediaCenter,很多程序都是使用D3D9开发的,比如迅雷影音等视频播放软件,
因为DX9可以支持WINXP到WIN7到WIN10. 也就是兼容性好,当WINXP彻底淘汰之后,DX9的接替者就是DX11 。
当Media Center程序进入全屏之后,便开启了全屏独占,它接替了windows桌面管理器,
这个时候windows桌面管理器是失效的。
我们再用  GetDC(GetDeskTopWIndow())  ( 或者GetDC(NULL)  )
获取到就只是黑屏或者就是程序进入全屏前那一刹那windows桌面的内容。
也就是GDI抓屏在这种情况下是无法工作的,
再来看看mirror驱动方式抓屏,在第二篇文章中就曾讲过mirror的原理,它是诞生于XP时代,主要是2D图像DDI绘图指令。
而WIN7以上是3D绘图指令,全屏DirectX程序更是清一色的Direct3D加速指令,
mirror是适用于XP时代的XPDM显卡驱动模型,WIN7以上是WDDM模型,mirror之所以还能运行在WIN7,WIN8,WIN10 ,
主要是WIN7以后的系统为了保持兼容性而模拟的。
WDDM驱动中有个 DxgkddiPresent回调函数,这个函数就是把后台缓存的图像数据呈现到显示终端,
类似于2D图像指令中的 BitBlt 的功能。
Present这个函数不管是DirectX图形库,还是应用层的WDDM显示驱动,还是WDDM内核驱动,都会多次出现。
我们这篇文章讲的HOOK方式截屏,也是对这个Present函数入手,下面会详细讲解。
可以这么推测,WIN7在模拟mirror驱动的时候,并没有从Present方面去模拟,还是遵循WINXP原来的工作方式,
因此WIN7中的mirror无法解决全屏独占程序抓屏问题。
但这种情况在WIN10有所改变,因为我使用 ”极品飞车17“ 等全屏独占游戏在WIN10(1809版本)平台下,
使用DXGI和mirror都能正确截屏全屏游戏(当然GDI还是无法全屏截屏),因此可以推测,WIN10中的mirror模拟得更彻底,
基本上是从Present中模拟mirror的DrvBitBlt的2D绘图指令。

再来看看windows桌面管理器,这究竟是个什么东西。
他是windows中的一个桌面管理集合,应该包括多个系统组件,其中我们能看得到的就是一个叫 dwm.exe 程序。
这个dwm.exe程序在WIN7似乎可有可无,但是开启AERO效果必须有dwm.exe。
win7中不存在dwm.exe并不意味着桌面管理器就不运行了,只是切换到了XP以前那种桌面管理模式而已。
而到WIN8以上系统,dwm.exe必须存在,没它整个windows桌面就挂了。
这也是windows开发中的一进步,同时更加稳固了他们对桌面管理的概念。
WIN7有几种桌面样式:
1,经典样式,就是窗口呈现3维效果的样子,这是我最常用的,因为看起来简洁清爽。
2,WIN7 Basic,
3,AERO,半透明毛玻璃效果,这是我感觉最花里胡哨的界面,不大常用,而且很占资源。
其中前两种不需要dmw.exe程序都能正常使用,AERO必须要开启dwm.exe。
我们再来简单看看桌面管理器都做些什么核心工作,
windows中一切都是窗口化的,各种窗口,什么顶层窗口,子窗口,隐藏窗口等等,他们的祖宗都是桌面窗口。
窗口包括外框(frame)和客户区(client area),窗口外框由桌面管理器负责绘制,客户区则是程序负责绘制。
我们在程序中使用 GDI, DirectX,OpenGL图形库(还有目前被吹嘘的新vulkan图形库)在客户区绘制出各种图形,
然后桌面管理器负责混合我们各种程序绘制的图像,判定哪些被遮挡不能显示,哪些可以显示。
然后混合了之后最后输出到显示终端。
这也是我们在普通截屏的时候,使用GetDC(NULL)就能截取整个电脑屏幕,因为所有程序都是在窗口管理器中进行混合的,
使用GetDC自然就能截屏。
当然,这里边也有些另类的,比如某个使用DDRAW窗口程序,而且恰巧显卡也支持DDRAW硬件加速,
于是DDRAW画图绕过了窗口管理器混合,直接朝显示器输出。
(这是我使用VLC播放DirectDraw方式输出的视频的时候,非全屏,在Intel集成显卡和一块N卡测试得出的不同结果:
使用GDI抓屏,WIN7系统,N卡抓不到VLC播放的图像,VLC播放窗口是黑的,在Intel集成显卡上却能看到,可见N卡支持DDRAW硬件加速)
这里似乎有些矛盾,既然不是全屏独占模式的窗口程序,桌面管理器还是有效的,
那他还是应该老老实实的混合图像,GetDC就应该抓取到所有图像,可是DDRAW有些特别。
总感觉那个时候搞得有些混乱的。毕竟那个时候显卡性能不强,得想各种办法来提升显示性能。

在WINXP以及以前的系统,在WIN7没有开启AERO效果的情况下,窗口混合都是实时混合的,什么意思呢?
就是应用程序在窗口客户区画图,绘制图形交给桌面管理器,
桌面管理器立马根据这个窗口所处的位置,决定哪些区域该显示,哪些不该显示。
然后立马显示到显示终端上。不会保留图像备份,这在当时显卡性能不强,显存很小的情况下,这么做是明智的。
不过这样带来一个问题,比如我们想单独截取某个窗口的图像,
如果这个窗口被遮挡了,就截取不到被遮挡的部分,因为这部分图像数据都不存在了。

而到了WIN7开启AERO特效,WIN8,WIN10以后的系统,系统内核为每个窗口客户区,在显存里专门开辟了一块显存地址,
也叫离屏表面(off-screen),微软也把它称为重定向表面。
程序在窗口客户区不管是使用GDI,DirectX,OpenGL等画图,都会画到这块离屏表面上。
然后窗口管理器,从这些离屏表面混合图像,然后统一朝显示终端输出。
这是dwm.exe主要做的事情,也是不能强行关闭dwm.exe原因。
其中WIN7的AERO特性的做法有些特别,它混合图像之后,可能是做些透明方面的处理,
然后使用DirectX朝显示终端输出,也就是它把整个桌面混合图像再使用DirectX朝显示器输出。
这很浪费资源。

既然WIN7的AERO特性是如此做的,
我们显然在这种情况下,直接挂钩WDM.exe程序,拦截它的DirectX函数,就能截取整个桌面图像了。
这也是比GDI更高效的截图办法,当然只适合WIN7,而且是开启AERO特效的情况下。

而WIN8,WIN10中的dwm.exe输出最终桌面图像的时候,并没有使用DirectX,可能是更底层的驱动方式输出或交给其他系统组件完成的。
并且从WIN8以上系统,CreateWindowEx创建窗口的时候,提供了一个叫 WS_EX_NOREDIRECTIONBITMAP 的 扩展Style。
前面说过了,每个窗口客户区域,在显存都会对应一个重定向表面,所有绘图都会画到这个表面上。这是WIN8以上普通窗口的做法。
可是需求总是很多,有些程序不想这么做,不想要重定向表面。
因为它想自己创建和管理这个表面,让桌面管理器从这个程序自己新创建的表面取图像来混合,
于是CreateWindowEx的时候,设置WS_EX_NOREDIRECTIONBITMAP 告诉系统,我不需要重定向表面。
最典型的就是WIN10上的UWP程序,它的窗口风格都是清一色的 WS_EX_NOREDIRECTIONBITMAP。
它通常配合DirectX一起使用,比如IDXGIFactory2 中的 CreateSwapChainForComposition,就是创建一个复合交换链,
再配合DirectComposition API或者XAML一起在 UWP 窗口某个区域绘图,因为不是画到重定向表面,而是保存到DirectX自己的内部缓存。
桌面管理器直接从DirectX中取图像。
这也引出一个问题,就是这种窗口,我们是无法通过GetDC(HWND)截取到窗口内容的,
所以UWP程序,无法通过传统的GDI截取窗口图像的。

介绍了上面的知识,我们再来看看如何HOOK DirectX等图形库并且截图,既然提到是HOOK,
自然免不了要把我们的dll注入到其他进程里,不注入如何能HOOK,不HOOK如何能截取到程序的绘图操作。
最常用也最正规的无非就两种:
1,调用SetWindowHookEx,设置消息钩子,比如WH_GETMESSAGE,
只要对方的程序是调用了user32.dll建立了消息循环
都会自动被注入。
有窗口的程序都遵循这个规律的,图形库没窗口是画不出来的,
因此使用WH_GETMESSAGE钩子,几乎都能注入所有调用图形库的程序。当然UWP是个例外。
2,CreateRemoteThread,在指定进程中创建一个线程,在此线程中调用LoadLibrary加载dll,这个方法同样适用于UWP程序。

具体如何操作,这里也就不再赘述了,可查阅其他相关资料。

之后是如何HOOK DirectX,我们需要熟悉DirectX图形库,
未完待续。。。
下图是采用HOOK DirectX办法,截取全屏MediaCenter的效果图,请关注

https://github.com/fanxiushu/xdisp_virt 上的xdisp_virt,稍后会把这部分功能发布上去。
Windows桌面实现之七(DirectX HOOK 方式截取特殊的全屏程序之一)_第1张图片

你可能感兴趣的:(C++,windows,多媒体,音视频)