MFC底层探索:GDI对象的生存期

MFC类库所有类型的基类是CObject,而跟GUI有关的MFC类分别分为 CDC图形设备接口类与CGdiObject类二大类别:下面我们谈一谈GDI对象的底层句柄,GDI对象,以及作图时要选入到dc的动作 这些对象,句柄以及动作的依赖关系,所谓依赖关系就是GDI对象的生存期

CObject类的源码就不展示了。同样CDC子类与CGdiObject子类的源码也不详细展示了;因为我们谈论的核心主题其实都在CDC和CGdiObject这二个类中

从源码可以看出 GDI对象底层维护了一个HGDIOBJ句柄,默认的构造函数是无意义的;一个GDI对象真正有意义有效果是要创建或者获取了一个有效的底层GDI句柄后然后 attach到对象中;从CPen类的源码我们也可以看出 attach与Create是一个组合动作这是让GDI对象真正有效的开始
attach动作解析:GDI对象获取底层句柄,并且把这个句柄和对象的this指针保存到一个关联map容器中
detach动作解析:从map容器删除这个关联的元素,并且把底层句柄清空
DeleteObject调用了全局的DeleteObject
在我们搞清楚全局的DeleteObject做了什么之前;我们先弄明白底层句柄是个什么东西,即调用系统Create函数创建出来的HGDIObject是什么含义
了解windows内核的人肯定十分清楚所谓的内核对象,内核对象的句柄不过是一个句柄表中的索引,表中存放的是内核对象的虚拟地址;而对于GDI底层句柄是不是如内核对象句柄那样的底层实现我不清楚;但是从逻辑上来说实现方式应该是大同小异;我们可以用理解内核对象句柄的方式从逻辑上理解GDI底层句柄!

https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject

https://docs.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-deleteobject?redirectedfrom=MSDN
从msdn文档中我们可以看出 选入GDI底层句柄到dc的行为应该只是建立一个引用关系:即dc获取了GDI底层的句柄其实是一个reference性质,引用了真正的底层对象;故而有绘图对象被选入dc时不要删除它;那么如果我们就要在此时删除会怎么样呢

我们发现原来选入的画笔对象确实失效了;但是此时dc在知道选入的画笔对象失效后,自动选入系统定义的默认的画笔;从上面的源码我们可以得知全局的DeleteObject函数确实会删除底层的绘图对象使得底层GDI句柄失效;这个和windows内核对象稍有不同,windows内核对象的closehandle函数只是对内核对象的引用计数减一直到最后一个引用被移除才真正删除;

谈谈CGdiObject::FromHandle
我们注意到selectobject会返回旧的GDI对象,而这个工作其实是用CGdiObject::FromHandle函数完成的

看源码我们知道实际上这个实现其实就是去哪个GDI对象的全局map表中根据原来的底层GDI句柄去找到与其关联的GDI对象的this指针;那么当我们选入我们自己定义的CPen对象去到dc的时候返回的旧的Cpen对象由是何时被定义的呢?从源码可看出attach函数就是附加底层句柄并且建立一个map表关联this与这个底层句柄的;直到detach把他们分开;所以我们可以认为是系统肯定帮我们创建了一些默认的GDI对象;设置断点调式可以验证我们的推论:

从中我们可以看到在CrtStartup的启动码中对全局对象AFX_GLOBAL_DATA afxGlobalData的初始化行为里面,确实初始化了一些默认的字体;其中有一个是用很特别的成员函数CreateStockObject创建的,跟踪进去看发现里面只是调用系统的GetStockObject函数得到底层GDI句柄后赋值给GDI对象,并没有调用Attach关联this到map!【因为系统以及帮我们做了】并且在这行CFont* pDefaultGUIFont = CFont::FromHandle((HFONT) GetStockObject(DEFAULT_GUI_FONT));我们观测的pDefaultGUIFont的地址与&font地址不一样!

这说明这些系统Stock预定义东西是:系统底层不光帮我们创建了一个所谓的"内核绘图"对象,和代码这个内核绘图对象的句柄;还帮我们创建了一个隐式的C++GDI对象并关联到这个全局的map中;这就是为什么CreateStockObject实现无需在attach的原因;而我们无法对这种Stock对象进行解map关联,以及调用全局的DeleteObject删除底层句柄代表的"内核绘图"对象

总结:1.现在我们终于了解了我们自己定义GDI对象时,其有效性的生存期特性 attach后DeleteObject前 底层句柄是有效的;attach后Detach前GDI对象是有效的【也就是说如果把一个有效的GDI对象选入dc后在dc绘图前删除这个GDI对象会导致选入的底层句柄失效,系统会自动选择其他默认的底层句柄对象;但是如果选入有效GDI对象后,在dc绘图前先detach这个GDI对象,然后删除此GDI对象是可以的;此时DeleteObject函数不会有任何效果;同理一个无效的GDI对象一开始就选入dc也是不会有任何效果,系统依然用默认的绘图对象句柄】
2.当被selectobject到dc的底层句柄对象变得无效时或者一开始就选入的是无效的句柄时;系统会帮我们选择一个默认的有效的句柄
3.无需为有着Stock底层句柄的GDI对象调用DeleteObject或者Detach分离;即便你这样做了,也无任何效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值