翻译《The Old New Thing》- What does the CS_OWNDC class style do?


What does the CS_OWNDC class style do? - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20060601-06/?p=31003


Raymond Chen 2006年06月01日


 简要

本文讨论了CS_OWNDC窗口类样式的影响,它让窗口管理器为窗口创建一个永久的设备上下文(DC),并始终返回同一个DC。这会导致代码中假设每次调用GetDC会得到不同DC的逻辑出现问题,因为实际上多次调用可能返回相同的DC,从而破坏了依赖于此假设的绘图代码。

复制再试一次分享

正文

        回想一下,窗口DC(设备上下文)通常只是临时使用。如果你需要在窗口中绘图,你可以调用BeginPaint,或者在绘制周期之外调用GetDC,尽管通常应避免在绘制周期之外进行绘制。窗口管理器为窗口生成一个DC并返回它。你使用这个DC,然后将其恢复到原始状态,并用EndPaint(或ReleaseDC)将其返回给窗口管理器。在内部,窗口管理器保持一个小的DC缓存,当有人请求窗口DC时,它会从这个缓存中提取,当DC被返回时,它会重新进入缓存。由于窗口DC只是临时使用,通常未处理的DC数量不会超过几个,一个小的缓存就足以满足正常运行系统中DC的需求。

        如果你注册一个窗口类并在类样式中包含CS_OWNDC标志,那么窗口管理器会为窗口创建一个DC,并将其放入DC缓存中,并打上一个特殊标记,意味着“不要从DC缓存中清除这个DC,因为它是这个窗口的CS_OWNDC”。如果你调用BeginPaintGetDC来获取一个CS_OWNDC窗口的DC,那么那个DC将总是被找到并返回(因为它被标记为“永不清除”)。这样做的后果有好有坏,还有更坏的。

        好的方面是,由于DC是专门为窗口创建的,并且永远不会被清除,所以你不必担心在将其返回到缓存之前“清理DC”。无论你何时为一个CS_OWNDC窗口调用BeginPaintGetDC,你总是能得到那个特殊的DC回来。实际上,这就是CS_OWNDC窗口的全部意义所在:你可以创建一个CS_OWNDC窗口,获取它的DC,按照你喜欢的方式设置它(选择字体、设置颜色等),即使你释放了DC并在以后再次获取它,你将得到同一个DC回来,它将和你离开时一样。

        不好的方面是,你正在使用一个本应只临时使用的东西(一个窗口DC),并永久地使用它。早期版本的Windows对DC的数量有非常低的限制(大约八个左右),因此当DC不再需要时,尽快释放它们至关重要。尽管这个限制自那以后已经显著提高,但背后的原理仍然存在:不应轻率地分配DC。你可能已经注意到CS_OWNDC的实现仍然使用DC缓存;只是这些DC得到了一个特殊的标记,以便DC管理器知道要特别对待它们。这意味着大量的CS_OWNDC DC最终会“污染”DC缓存,减缓未来调用函数如BeginPaintReleaseDC的速度,这些函数需要搜索DC缓存。

        为什么DC管理器没有被优化以处理大量CS_OWNDC DC的情况?首先,正如我已经指出的,原始的DC管理器不需要担心大量DC的情况,因为系统根本就不可能首先创建那么多DC。其次,即使DC的数量限制提高后,重写DC管理器以优化CS_OWNDC DC的处理也没有太多意义,因为程序员已经被告知要节制使用CS_OWNDC。这是软件工程的一个实际问题:你能做的只有这么多。你决定做的每件事都是以牺牲其他事情为代价的。很难证明优化程序员被告知要避免并且事实上已经在避免的场景是合理的。你不会为某人滥用你的系统的情况优化。这就像花时间设计汽车发动机,以便在汽车没有油的情况下保持良好的燃油效率。

        更糟糕的是,大多数窗口框架库和几乎所有示例代码都假定你的窗口不是CS_OWNDC窗口。考虑以下代码,它使用两种字体绘制文本,使用第一种字体引导第二种字体中字符的位置。它看起来完全没问题,不是吗?

void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2) {HDC hdc1 = GetDC(hwnd);HFONT hfPrev1 = SelectFont(hdc1, hf1);UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);MoveToEx(hdc1, 0, 0, NULL);HDC hdc2 = GetDC(hwnd);HFONT hfPrev2 = SelectFont(hdc2, hf2);for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {POINT pt;GetCurrentPositionEx(hdc1, &pt);TextOut(hdc2, pt.x, pt.y + 30, psz, 1);TextOut(hdc1, 0, 0, psz, 1);}SelectFont(hdc1, hfPrev1);SelectFont(hdc2, hfPrev2);SetTextAlign(hdc1, taPrev1);ReleaseDC(hwnd, hdc1);ReleaseDC(hwnd, hdc2);
}

        我们为窗口获取了两个DC。在第一个中我们选择了我们的第一个字体;在第二个中,我们选择了第二个。在第一个DC中,我们还设置了文本对齐为TA_UPDATECP,这意味着传递给TextOut函数的坐标将被忽略。相反,文本将从“当前位置”开始绘制,并且“当前位置”将更新为字符串的末尾,这样下一次调用TextOut将从上一次结束的地方继续。 一旦两个DC设置好,我们就逐个字符绘制我们的字符串。我们查询第一个DC以获取当前位置,并在第二个字体中绘制相同x坐标(但稍低一些)的字符,然后我们用第一个字体绘制字符(这也推进了当前位置)。 完成文本绘制循环后,我们作为标准记账的一部分恢复两个DC的状态。

        函数的意图是绘制类似这样的东西,其中第一个字体比第二个大。

        如果窗口不是CS_OWNDC,那就是你得到的。你可以通过从我们的临时程序中调用它来尝试它:

HFONT g_hfBig;
BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) {LOGFONT lf;GetObject(GetStockFont(ANSI_VAR_FONT),sizeof(lf),&lf);lf.lfHeight *= 2;g_hfBig = CreateFontIndirect(&lf);return g_hfBig != NULL;
}
void
OnDestroy(HWND hwnd) {if (g_hfBig) DeleteObject(g_hfBig);PostQuitMessage(0);
}
void
PaintContent(HWND hwnd, PAINTSTRUCT *pps) {FunnyDraw(hwnd, g_hfBig,GetStockFont(ANSI_VAR_FONT));
}

        但如果窗口是CS_OWNDC,那么坏事就发生了。你自己试试,将这一行wc.style = 0;改为wc.style = CS_OWNDC;你会得到以下意想不到的输出:

        当然,如果你了解CS_OWNDC的工作原理,这一点也不意外。理解的关键是要记住,当窗口是CS_OWNDC时,无论调用多少次GetDCGetDC都只是返回同一个DC。现在你只需要记住FunnyDraw函数,hdc1hdc2实际上是同一件事

void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2) {HDC hdc1 = GetDC(hwnd);HFONT hfPrev1 = SelectFont(hdc1, hf1);UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);MoveToEx(hdc1, 0, 0, NULL);

        到目前为止,函数的执行相当正常。

    HDC hdc2 = GetDC(hwnd);

        由于窗口是一个CS_OWNDC窗口,返回给hdc2的DC与hdc1返回的是同一个。换句话说,hdc1 == hdc2!现在事情变得有趣了。

    HFONT hfPrev2 = SelectFont(hdc2, hf2);

        由于hdc1 == hdc2,这实际上做的是从DC中取消选择字体hf1,而选择字体hf2

    for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {POINT pt;GetCurrentPositionEx(hdc1, &pt);TextOut(hdc2, pt.x, pt.y + 30, psz, 1);TextOut(hdc1, 0, 0, psz, 1);}

        现在这个循环完全崩溃了。在第一次迭代中,我们从DC获取当前位置,返回(0, 0),因为我们还没有移动它。然后我们在第二个DC中的位置(0, 30)绘制字母“H”。但由于第二个DC与第一个相同,实际上我们是在TA_UPDATECP模式的DC中调用TextOut。因此,坐标被忽略,字母“H”被显示出来(在第二种字体中),当前位置被更新为“H”之后。最后,我们绘制第一个DC中的“H”(它与第二个相同)。我们以为我们用第一个字体绘制,但实际上我们用第二个字体绘制。我们以为我们在(0, 0)绘制,但实际上我们在(x, 0)绘制,其中_x_是字母“H”的宽度,因为对TextOut(hdc2, ...)的调用更新了当前位置。

        因此,每次循环,字符串中的下一个字符都会以第二种字体显示两次。

        但等等,灾难还没有结束。看看我们的清理代码: 

    SelectFont(hdc1, hfPrev1);

        这将DC中的原始字体恢复。

    SelectFont(hdc1, hfPrev2);

        这将重新选择第一个字体!我们未能将 DC 恢复到其原始状态,结果把一个 "损坏的 "DC 放进了缓存。

        这就是我将 CS_OWNDC 描述为 "更糟糕 "的原因。它采用了曾经有效的代码,并违反了大多数人(通常没有意识到)对 DC 的假设,从而破坏了代码。

        你可能以为 CS_OWNDC 很糟糕。下次我将谈谈 CS_CLASSDC ,那更是场灾难。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/704307.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高通QCS6490开发(三):点亮板卡

QCS6490是高通公司针对高端物联网终端而优化的SoC,在性能和功耗上有最优的平衡。《高通QCS6490 AIoT应用开发》是介绍如何基于QCS6490平台完成AIIoT的应用开发的系列文章。 本期主要介绍介绍如何点亮FV01开发板。 以下步骤介绍如何点亮FV01开发板步骤 1、将12V/5A…

人物介绍模板 PSD 源文件免费获取

免费获取 下载链接在最后! 下载链接在最后! 下载链接在最后! 下载链接在最后! 下载链接在最后! 链接:https://pan.baidu.com/s/1sq3e6djMdZt76Sh_uqVxWg 提取码:naun

Gini Impurity(基尼不纯度)

基尼不纯度:衡量集合的无序程度; 基尼不纯度 基尼不纯度:将来自集合的某种结果随机应用于某一数据项的预期误差率。 1、显然基尼不纯度越小,纯度越高,集合的有序程度越高,分类的效果越好; 2、…

广告小白必看|谷歌Google Ads被封禁原因是什么,如何防范?

跨境出海业务少不了需要做Google Ads推广业务;其中让投手们闻风丧胆的消息就是帐户被暂停。当 Google 检测到任何违反其政策且可能损害用户在线体验的行为时,就会发生这种情况。那么如何在做广告推广的同时,保证账号不被封禁呢?看…

独立静态ISP:互联网连接的新选择

在数字化时代,互联网连接的质量直接影响着我们的工作与生活。随着技术的发展,独立静态ISP(Internet Service Provider,互联网服务提供商)逐渐成为企业和个人用户关注的焦点。本文将从五个方面探讨独立静态ISP的优势、应…

异步I/O库-libuv介绍

1.简介 libuv是一个跨平台的支持事件驱动的异步I/O的库,使开发者可以以非阻塞的方式执行文件I/O操作、网络通信、子进程管理等。 libuv的主要特点包括: 事件循环:libuv有一个基于事件循环的模型,它不断地轮询事件,并…

【2024年电工杯数学建模竞赛】选题分析+A题B题完整思路+代码分享

.2024年电工杯数学建模AB题选题思路 比赛开始第一时间在下面的资料裙分享: 点击链接加入群聊【2024数维杯数学建模ABC题资料汇总】:http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kBwulH5tSN2X7iLXzZHAJqRk9sYnegd0y&authKey2TSsuOgqXZQ%2FvTX4R59…

Java面试八股之Java中有哪些原子类,原理是什么

Java中有哪些原子类,原理是什么 AtomicInteger 和 AtomicLong: 用于对整数(int)和长整数(long)进行原子操作。 原理:它们内部封装了一个整型或长整型变量,并通过使用Unsafe类提供…

手机如何下载短视频到本地:成都鼎茂宏升文化传媒公

手机如何下载短视频到本地 ​随着移动互联网的迅猛发展,短视频已经成为人们生活中不可或缺的一部分。从娱乐、学习到社交,短视频以其短小精悍、内容丰富的特点,吸引了大量用户的关注。然而,有时我们可能希望将喜欢的短视频保存到…

jspXMl标记语言基础

1.打开命令框进入数据库 打开eclipse创建需要连接的项目 粘贴驱动程序 查看驱动器 使用sql的包 int代表个 conlm代表列名 <%page import"java.sql.ResultSet"%> <%page import"java.sql.Statement"%> <%page import"java.sql.Connect…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt 驾校管理系统 设计与实现

一.项目介绍 系统角色&#xff1a;管理员、驾校教练、学员 管理员&#xff1a; 个人中心&#xff1a;修改密码以及个人信息修改 学员管理&#xff1a;维护学员信息&#xff0c;维护学员成绩信息 驾校教练管理&#xff1a;驾校教练信息的维护 驾校车辆管理&…

如何找到MySQL中存储引擎所对应的表空间并且打开?

在上节课我们学习了数据库&#xff08;MySQL&#xff09;进阶&#xff1a;存储引擎&#xff0c;有不少同学产生疑惑&#xff0c;到底要怎么找到表空间并且打开啊&#xff1f;这节课我们就来探讨。 首先&#xff0c;根据这个路径&#xff1a;C:\ProgramData\MySQL\MySQL Server…