使用GDI对象绘制UI时需要注意的若干细节问题总结

news2024/12/27 10:04:03

目录

1、一个bitmap不能同时被选进两个dc中

2、CreateCompatibleDC和CreateCompatibleBitmap要使用同一个dc作为参数

3、不能删除已经被选入DC中的GDI对象

4、使用完的GDI对象,要将之释放掉,否则会导致GDI对象泄漏

5、CreateCompatibleBitmap返回错误码8的原因及解决办法(用CreateDIBSection替换CreateCompatibleBitmap)

6、Windows开发人员必须要学会到微软MSDN上查看函数的详细说明

7、最后


C++软件异常排查从入门到精通系列教程(核心精品专栏,欢迎订阅,持续更新...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新480多篇,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/category_2276111.html       我们在自绘窗口、给控件贴图、绘制文字时会使用到Pen画笔、Brush画刷、Bitmap位图、Font字体、Region区域、DC设备上下文等GDI对象,在使用这些GDI对象要注意一些细节问题,否则会出现一些绘制异常。本文结合多年的项目实践,给大家分享几个使用GDI对象的细节,以供大家借鉴或参考。

1、一个bitmap不能同时被选进两个dc中

       我们在使用Pen画笔、Brush画刷、Bitmap位图、Font字体、Region区域等GDI对象绘制之前,需要将它们选进DC设备上下文中,然后通过DC去操作使用。

       对于bitmap对象,使用示例如下:

// 先创建位图,然后将位图选进DC中,然后操作DC去完成绘图
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hMemBitmap = ::CreateCompatibleBitmap( hdc, rect.GetWidth(), rect.GetHeight() );
::SelectObject( hMemDC, hMemBitmap );

使用bitmap对象时,不能将一个bitmap同时选进两个dc中

       系统API函数SelectObject在MSDN上有明确的说明:

​即一个bitmap不能同时被选进两个dc。这个问题我们之前遇到过,因为新手在编写GDI绘制的代码时代码不规范,创建的GDI资源没有释放,导致一个bitmap被选进了两个dc中,导致绘制有问题,始终达不到预期的效果。

2、CreateCompatibleDC和CreateCompatibleBitmap要使用同一个dc作为参数

       我们在使用双缓冲绘图时会先创建一个内存DC和内存bitmap,如下:

// CreateCompatibleDC和CreateCompatibleBitmap使用的是同一个参数hdc
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hMemBitmap = ::CreateCompatibleBitmap( hdc, rect.GetWidth(), rect.GetHeight() );

但要注意,CreateCompatibleDC和CreateCompatibleBitmap两函数传入的dc对象必须是同一个

       有些人可能会将CreateCompatibleDC返回的dc对象,直接放到CreateCompatibleBitmap参数中,如下:

// 错误:CreateCompatibleBitmap使用CreateCompatibleDC返回的dc句柄
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hMemBitmap = ::CreateCompatibleBitmap( hMemDC, rect.GetWidth(), rect.GetHeight() );

这样会导致调用CreateCompatibleBitmap创建的位图是单色位图,只能绘制黑白颜色的效果。

       MSDN上CreateCompatibleBitmap API函数说明页面,有下面一段说明:

​大体意思是,当一个内存dc被创建时,初始拥有一个尺寸为1x1的单色的位图被选进去。如果这个新创建的内存dc(CreateCompatibleDC函数返回的dc),被用到CreateCompatibleBitmap的参数中用来创建bitmap,那么这个位图将是单色的位图,只能绘制黑白颜色的效果。

之前在开发截图功能时就遇到过,当时截图的背景始终是灰白色的,后来看到上述MSDN上说明才知道原因。


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到570多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达220多个,专栏文章已经更新到480多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。

专栏3:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


3、不能删除已经被选入DC中的GDI对象

       DeleteObject API函数中,有如下的说明:

​即不能删除还在被选入dc中的GDI对象,需要选出后再删除,否则会导致绘制异常。

4、使用完的GDI对象,要将之释放掉,否则会导致GDI对象泄漏

       在GDI对象使用完成后要将之删除或释放掉,如果不删除或释放,则会导致GDI泄漏。如果有GDI对象泄漏的代码被频繁地执行,会导致GDI对象在持续的泄漏,当GDI对象总数接近或达到1万个时,就会导致GDI绘图函数调用出现异常,出现窗口绘制不出来等问题,紧接着程序就可能会出现崩溃。很多Windows老程序员可能都遇到过类似的问题。

       关于Windows进程的GDI对象数目上限的详细说明,可以查看我的文章:

从注册表看Windows系统进程GDI句柄及进程句柄数上限icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/139565038       创建GDI对象的接口比较多,要用对应的接口去释放,比如使用CreateXXXXXX创建的GDI对象,使用完后,要用DeleteObject释放;调用LoadXXXXXX函数去加载图片资源,使用完后,也要用DeleteObject释放;调用CreateXXXDC创建的DC对象,使用完后,要用DeleteDC去释放;调用GetDC获取到的DC对象,使用完后,要用ReleaseDC释放。

       调用不同的接口去创建或获取GDI对象,释放时也要调用对应的释放接口,不能混淆!在这里给大家大概地罗列一下:

创建或获取GDI对象删除或释放GDI对象
CreatePen/CreatePenIndirect(pen画笔对象)、CreateSolidBrush/CreateBrushIndirect(brush画刷对象)、CreateFont/CreateFontIndirect(Font字体对象)、CreateCompatibleBitmap(BItmap位图对象)对于Create出来的对象,要调用DeleteObject释放
CreateDC/CreateCompatibleDC(创建DC对象)调用DeleteDC释放
GetDC(获取DC对象)调用ReleaseDC释放
LoadBitmap(加载Bitmap位图)调用DeleteObject释放
LoadImage(加载图片资源)

如果加载的是Bitmap位图,则调用DeleteObject释放;

如果加载的是Cursor光标,则调用DestroyCursor释放;

如果加载的是Icon图标,则调用DestroyIcon释放。

      对于上面提到的创建GDI对象的API函数,在释放时该调用哪个接口,可以直接到MSDN上查看API接口的Remarks部分就会找到对应的说明。比如创建兼容位图的API函数CreateCompatibleBItmap,在Remaks部分的说明如下:

​再比如加载图片的API函数LoadImage,其在Remarks部分的说明如下:

在调用Windows系统API函数遇到问题时,需要到微软MSDN帮助页面中查看API函数的详细说明(可能会给出调用函数时的注意事项,或者调用函数的示例代码等),在说明中可能会找到相关的原因!会使用MSDN,是一个Windows开发人员最基本的要求!

       对于GDI对象泄漏问题的排查,首先到Windows任务管理器中看目标程序进程的GDI总数是否有异常(GDI总数特别大,好几千甚至接近10000个上限),然后再使用GDIView工具查看到底是哪一类GDI对象数量特别大

​基本可以确定该类对象有泄漏。然后去查看代码,检查每个操作该类GDI对象的地方,看看是否存在GDI对象使用完后没有释放。

使用GDIView工具查看具体是哪个GDI对象发生了泄漏。确定泄漏对象之后,需要去查看代码去排查GDI对象的泄漏点。但软件的模块多,代码量大,不好漫无目标地排查。可以尝试去找到复现问题的办法,根据复现问题的操作步骤和场景,猜测问题可能出在哪些代码块中,即缩小代码排查的范围。 

       关于如何使用GDIView去排查GDI对象以及项目问题排查实例,可以查看我之前写的文章:
深入探究 C++ 程序中的资源泄漏问题icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/133631728使用GDIView工具排查GDI对象泄漏问题(常用分析工具)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/125399896使用GDIView工具排查GDI对象泄漏导致C++程序UI界面绘制异常的问题icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/140731065使用GDIView工具排查GDI对象泄漏案例的若干细节总结icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/141526436

5、CreateCompatibleBitmap返回错误码8的原因及解决办法(用CreateDIBSection替换CreateCompatibleBitmap)

       之前我们在加载图片去绘制窗口时(比如将图片加载起来显示到窗口中),如果图片尺寸过大,会出现CreateCompatibleBitmap返回失败,即创建位图失败的问题。调用GetLastError获取调用CreateCompatibleBitmap失败的LastError值为8,查看该错误码的含义为:

​即创建位图时需要对应的内存,而内存资源不足,导致申请内存失败,所以CreateCompatibleBitmap调用失败了。创建位图的示例代码如下:

HDC hDC = ::GetDC( NULL );
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hMemBitmap = ::CreateCompatibleBitmap( hdc, nBmpWidth, nBmpHeight );

比如我们要加载的图片尺寸为3000*3000,则需要的内存大小为:

图片的宽高相乘,得到图片的像素个数,每个像素占4个字节(R、G、B、Alpha各占一个字节),则创建这样尺寸的位图需要的内存为:

3000*3000*4 = 34MB

34MB的内存很大。

       调用CreateCompatibleBitmap创建是DDB(Device​-dependent bitmap)设备相关位图,创建该位图使用的是系统内核的分页内存,这是稀有资源,可能会出现内存资源不够用的情况。可以使用API函数CreateDIBSection去创建DIB(Device​-independent bitmap)设备无关位图,这种方式下使用的是虚拟内存,基本就会出现内存不足的问题了。关于CreateCompatibleBitmap和CreateDIBSection的详细说明,可以查看我之前写的文章:
CreateCompatibleBitmap返回错误码8的原因及解决方案(使用CreateDIBSection替换CreateCompatibleBitmap)icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/139586621

之前开发的截图功能,在部分机器上会频繁出现CreateCompatibleBitmap创建失败的问题,导致截图失败。后来就是用CreateDIBSection替换CreateCompatibleBitmap解决的。

      使用CreateDIBSection创建位图的示例代码如下

   BITMAPINFOHEADER bmih;
    memset(&bmih, 0, sizeof(BITMAPINFOHEADER));
    bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biBitCount = 24;
    bmih.biCompression = BI_RGB;
    bmih.biPlanes = 1;
    bmih.biWidth = nImageWidth; // 位图的宽度
    bmih.biHeight = nImageHeight; // 位图的高度
    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(BITMAPINFO));
    bmi.bmiHeader = bmih;
    void* p;
    hBitmap = ::CreateDIBSection(cdc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, &p, NULL, 0);

6、Windows开发人员必须要学会到微软MSDN上查看函数的详细说明

       在调用Windows系统API函数遇到问题时,需要到微软MSDN帮助页面中查看API函数的详细说明。MSDN上可能会给出调用函数时的若干注意事项或限制条件(约束条件),或者给出调用API函数的示例代码可供参考,在函数说明中可能会找到引发问题的原因!会使用MSDN,是一个Windows开发人员基本要求!

       除了去查看MSDN上的说明,在使用工具时遇到问题,可以尝试到工具的官网上查找官方的说明,比如我们有次在使用GDIView时排查GDI对象泄漏时始终无法定位问题时,就是到GDIView工具的官网上找到了问题的答案,相关问题说明可以去查看我之前写的文章:

到GDIView等工具官网上或者微软MSDN上查看文档化说明去解决问题icon-default.png?t=O83Ahttps://blog.csdn.net/chenlycly/article/details/126004453

7、最后

      使用GDI对象去编写绘图的代码相对简单,但在编码过程中要注意上面讲到的若干细节问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石

我的个人主页 我的专栏:Java-数据结构,希望能帮助到大家!!!点赞❤ 收藏❤ 一、引言 1. 栈与队列在编程中的角色定位 栈和队列作为两种基本的数据结构,在众多编程场景中都有着独特的地位。它们为数据的有序…

洛谷P2670扫雷游戏(Java)

三.P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n 行 m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩…

vue2:Cascader 级联选择器中加载两种不同的数据结构

前言 因UI调整,需要将el-tree控件更换为级联选择器,而在原树形控件中,加载了两种不同的数据结构,(参见vue2:树形控件el-tree中加载两种不同结构的数据_vue2 树形插件-CSDN博客)所以现在级联选择器中也需要加载这两种不同的数据结构。 问题 原本以为处理方式差不多,在…

【10】MySQL中的加密功能:如何使用MD5加密算法进行数据加密

文章目录 1. MySQL加密功能概述2. MD5加密算法3. 在MySQL中使用MD5加密4. 使用更安全的加密方法总结 在现代的数据库应用中,数据的安全性和隐私性变得尤为重要。无论是存储用户的个人信息,还是保护敏感的业务数据,确保这些数据不会被未授权访…

【SARL】单智能体强化学习(Single-Agent Reinforcement Learning)《纲要》

📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…

前端上传后端接收参数为null

记录一下工作中的问题 前端明明把文件传到后台了,但是后台接收参数为null 原因: 前端上传文件的name和后端接收参数名称不匹配 前端 后端 把前端上传的name由upfile改为file即可 本来是很基本的小问题,但因为自己钻了牛角尖一直没搞定&…

Clickhouse MergeTree存储引擎

文章目录 MergeTree特点MergeTree核心参数- ORDER BY- PARTITION BY- PRIMARY KEY- SAMPLE BY- TTL- SETTINGS- index_granularity- index_granularity_bytes- min_index_granularity_bytes- enable_mixed_granularity_parts- use_minimalistic_part_header_in_zookeeper- min_…

【机器学习】—Transformers的扩展应用:从NLP到多领域突破

好久不见!喜欢就关注吧~ 云边有个稻草人-CSDN博客 目录 引言 一、Transformer架构解析 (一)、核心组件 (二)、架构图 二、领域扩展:从NLP到更多场景 1. 自然语言处理(NLP) 2…

【算法】【优选算法】位运算(下)

目录 一、:⾯试题 01.01.判定字符是否唯⼀1.1 位图1.2 hash思路1.3 暴力枚举 二、268.丢失的数字2.1 位运算,异或2.2 数学求和 三、371.两整数之和四、137.只出现⼀次的数字 II五、⾯试题 17.19.消失的两个数字 一、:⾯试题 01.01.判定字符是…

深度学习中注意力机制介绍及seq2seq案例

一. 注意力机制介绍 普通机器翻译 图中表示的是一个中文到英文的翻译:欢迎 来 北京 → welcome to BeiJing。编码器首先处理中文输入"欢迎 来 北京",通过GRU模型获得每个时间步的输出张量,最后将它们拼接(按位相加)成一个中间语义张…

工业—使用Flink处理Kafka中的数据_ChangeRecord2

使用 Flink 消费 Kafka 中 ChangeRecord 主题的数据,每隔 1 分钟输出最近 3 分钟的预警次数最多的 设备,将结果存入Redis 中, key 值为 “warning_last3min_everymin_out” , value 值为 “ 窗口结束时间,设备id” &am…

Android 消息队列之MQTT的使用:物联网通讯,HTTP太重了,使用MQTT;断网重连、注册、订阅、发送数据和接受数据,实现双向通讯。

目录: 问题MQTT是什么以及为什么使用如何使用:第一阶段、基础功能如何使用:第二阶段、增加断网重连如何使用:第三阶段、封装 一、问题 在开发的时候,我们一般都使用Http和后台进行通讯,比如我们是开发物联…

项目-02-数学学院后台项目开发过程中的问题总结

目录 一、后台(pc端,vue2)1. dialog对话框被黑色蒙层盖住2. 将前端表格导出为word文档3. 在线查看、下载 .docx、.doc、.pdf文档 一、后台(pc端,vue2) 1. dialog对话框被黑色蒙层盖住 问题: d…

大数据实验E5HBase:安装配置,shell 命令和Java API使用

实验目的 熟悉HBase操作常用的shell 命令和Java API使用; 实验要求 掌握HBase的基本操作命令和函数接口的使用; 实验平台 操作系统:Linux(建议Ubuntu16.04或者CentOS 7 以上);Hadoop版本:3…

使用Tomcat搭建简易文件服务器

创建服务器 1. 复制一个tomcat服务器,并命名为file-service(好区分即可) 2.在webapp里面新建一个文件夹 uploadfiles ,用于存储上传的文件 3. 修改conf/service.xml,配置文件服务器的端口与上传文件夹的访问 在Host标签之间加入一个Context标签 docBase"uploa…

【算法】位运算合集

阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 零:位运算基础公式 零:五道基础题 1:位1的个数 2:比…

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势?

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势? 重要性:⭐⭐⭐ 💯 NLP Github 项目: NLP 项目实践:fasterai/nlp-project-practice 介绍:该仓库围绕着 NLP 任务模型的设计、训练…

《Vue零基础教程》(5)计算属性和侦听器好讲解

1 计算属性 1) 什么是计算属性 计算属性就是基于现有属性计算后的属性 2) 计算属性的作用 计算属性用于对原始数据的再次加工 3) 案例 需求 实现如下效果 使用表达式实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF…

Narya.ai正在寻找iOS工程师!#Mixlab内推

如果你对AI技术和iOS开发充满热情&#xff0c;这里有一个绝佳的机会加入一家专注于AI应用创新的初创公司。Narya.ai正在招聘iOS工程师&#xff0c;帮助他们开发下一代效率工具&#xff0c;旨在提升用户的日常生活效率与幸福感。 关于Narya.ai&#xff1a; 专注于AI应用层创新&a…

【开源免费】基于SpringBoot+Vue.JS课程答疑系统(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 070 &#xff0c;文末自助获取源码 \color{red}{T070&#xff0c;文末自助获取源码} T070&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…