当前位置:网站首页>我们要学会查看技术细节点的文档化说明
我们要学会查看技术细节点的文档化说明
2022-07-27 12:52:00 【dvlinker】
目录
1、概述
当我们在使用Windows API遇到问题时,要想到使用微软的MSDN(在线MSDN或者本地安装的MSDN),到MSDN上查看目标API的详细说明和注解(Remark部分),比如下图中的系统API函数GetWIndowText:

也许就能找到问题的解决办法。
此外,在使用一些辅助软件工具遇到问题时,如果工具有官网的话,可以尝试到官网上查看一下该工具的详细说明,也可能找到问题的解决思路或突破口。
本文提及技术细节点的文档化说明,是指微软MSDN或软件工具官网上给出的公开的说明文字和注解。本文结合解决的几个实际问题,来说明文档化说明的重要性。
2、GDI绘图遇到的问题
2.1、创建兼容bitmap应该使用哪个DC
在Windows GDI绘图中,有个双缓冲绘图的概念,即现在内存DC上绘图,待绘制成功后再将内存DC中的内容绘制到窗口上。
在使用双缓冲绘图时,我们一般先创建一个与窗口DC兼容的内存DC,然后再创建一个兼容的bitmap,然后将bitmap选中内存DC中,bitmap是画板,将要绘制的内容先绘制到bitmap上。在创建兼容bitmap时传入的dc参数是有讲究的。如果传入的是刚创建的兼容dc,代码如下:
HDC hdc = ::GetDC( this->m_hWnd ); // 窗口DC
HDC hMemDC = ::CreateCompatibleDC( hdc ); // 兼容内存DC
HBITMAP hBitmap = ::CreateCompatibleBitmap( hMemDC, 800, 600 ); // 传入的是刚创建的兼容DC感觉上是没问题的,hMemDC是与窗口的hdc是兼容的,想到可能存在的传递性,创建的bitmap应该也是与hdc兼容的,运行应该是没问题的。
但实际运行后,绘制到窗口的图片都变成了单一的灰白色。这是怎么回事呢?到MSDN中查看CreateCompatibleBitmap API的函数说明,在Remarks部分找到了如下的说明:

所以,根据上述微软的说明,如果使用我们上面给出的代码,创建出来的兼容位图是单色的位图,绘制出来的效果是单一的灰白色。MSDN上明确指出,传入到CreateCompatibleBitmap中的dc应该和CreateCompatibleDC传入的一致,都使用窗口dc,代码如下:
HDC hdc = ::GetDC( this->m_hWnd );
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hBitmap = ::CreateCompatibleBitmap( hdc, 800, 600 ); // 传入的是刚创建的兼容DC,这样处理就没问题了。2.2、一个bitmap位图不能同时选进多个dc中
假设位图m_hBitmap是UI类的成员变量,并且已经将要绘制到窗口上的图片加载到该位图中了,在窗口的OnPaint函数中会使用到该位图进行窗口的绘制,相关的代码片如下所示:
HDC hdc = ::GetDC( this->m_hWnd );
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hOldBitmap = (HBITMAP)::SelectObject( hdc, m_hBitmap )
...... // 此处使用BitBlt或者其他的函数进行绘制,代码省略调试代码时,因为急着看到界面效果(看看代码有没有生效),没有讲究代码的规范性,没有将创建的兼容dc(hMemDC)给delete掉。运行程序后发现,只有第一次绘制是有效的,窗口上显示时正常的,接下来的每次窗口刷新时的绘制都是无效的。
绘制无效时,查看绘制函数BitBlt的返回值,是调用成功的返回值。这又是怎么回事呢?明明添加了代码,怎么没有起到应有的效果呢?事后,在SelectObject API函数的MSDN说明中,有下面的一句话:

即不能将一个位图同时选进多个dc中,看到这句话我们似乎知道出问题的原因了。
在OnPaint函数中的绘制代码,在每次窗口刷新时都会执行OnPaint中,所以上述代码会被多次执行,因为创建的兼容dc(hMemDc)没有delete掉,下次再执行到上述代码片,又会创建新的兼容dc,接着将位图m_hBitmap选进兼容dc,这样就导致位图m_hBitmap选进了多个dc中了,所以出现了上述问题。
这个问题也提醒我们,平时在写代码要注重规范性,不用的资源要及时释放掉(这也是新手在写代码容易犯得的错误,这个问题就是在帮新人排查问题代码时遇到的)。就上述代码片,正确的做法是将创建的兼容dc在用完后delete掉,这样就不会出现同一个位图同事被选进两个dc中的问题了,正确的代码如下:
HDC hdc = ::GetDC( this->m_hWnd );
HDC hMemDC = ::CreateCompatibleDC( hdc );
HBITMAP hOldBitmap = (HBITMAP)::SelectObject( hMemDC, m_hBitmap )
...... //此处使用BitBlt或者其他的函数进行绘制,代码省略
::SelectObject( hMemDC, hOldBitmap );
::DeleteDC( hMemDC ); // 将创建的兼容dc释放掉3、无从下手的GDI资源泄漏问题
经测试同事反馈,我们的软件在打开部分窗口后关闭,GDI句柄数在每次操作后都会上升6个,多次操作后GDI对象会有明显的较大的飙升现象。可以通过Windows资源管理器来实时监测目标进程的GDI个数:(在任务管理器的详细信息标签页中)

这说明软件中是有GDI资源泄漏的。如果有GDI对象泄漏,在程序长时间运行时,如果程序的GDI对象个数达到上万个,就会导致程序异常甚至崩溃。所以,这个问题必须要重视,是必须要解决的。
在默认情况下,打开Windows资源管理器,默认是看不到GDI对象这一列的,需要右键点击进程列表的标题栏,弹出如下的右键菜单:
然后点击“选择列”菜单项,在弹出的窗口中勾选“GDI对象”:
然后在进程列表中就可以看到GDI对象列了。
在测试过程中,还发现部分窗口在打开关闭后是没有泄漏的。下面该GDI泄漏排查利器GDIView上场了:

通过使用GDI资源泄漏排查工具GDIView,当GDI资源泄漏时,观察到GDI Total列没有增长,All GDI列每次都有增长(上升6个)。一般我们的资源泄漏主要是pen、bitmap、font、brush、region、dc等常见的GDI对象,结果这次这些常规GDI对象一个都没有泄漏,这就比较奇怪了,感觉无从查起了。
于是尝试着到GDIView的官网(http://www.nirsoft.net/utils/gdi_handles.html)上看一下,看看有没有相关的说明。结果找到了对应的说明,如下:

即如果出现其他GDI数量没有增长,只有All GDI列出现增长,有可能是创建的图标或光标资源没有释放引起的。
于是排查了处理图标和光标资源的代码,但并没有找到可能存在泄漏的地方。无意中发现,当有的窗口在任务栏有图标时,就会有泄漏,如果窗口没有任务栏窗口(比如程序的关于窗口等),就不会有泄漏。那是不是设置任务栏窗口图标的代码有问题,于是找到对应的代码:
void CWindowWnd::SetIcon( UINT nRes )
{
HICON hIcon = (HICON)::LoadImage( CPaintManagerUI::GetInstance()
, MAKEINTRESOURCE(nRes)
, IMAGE_ICON
, ::GetSystemMetrics(SM_CXICON)
, ::GetSystemMetrics(SM_CYICON)
, LR_DEFAULTCOLOR );
ASSERT( hIcon );
::SendMessage( m_hWnd, WM_SETICON, (WPARAM)TRUE, (LPARAM)hIcon );
hIcon = (HICON)::LoadImage( CPaintManagerUI::GetInstance()
, MAKEINTRESOURCE(nRes)
, IMAGE_ICON
, ::GetSystemMetrics(SM_CXSMICON)
, ::GetSystemMetrics(SM_CYSMICON)
, LR_DEFAULTCOLOR );
ASSERT( hIcon );
::SendMessage( m_hWnd, WM_SETICON, (WPARAM)FALSE, (LPARAM)hIcon );
}难道是LoadImage API函数使用的有问题?于是到MSDN上详细查看了该函数的说明,找到了出问题的地方:

即如果没有使用LR_SHARED标记位,则需要调用DestroyXXX接口去手动释放这些资源。
上面的代码中没有使用这个标记位,也没有释放加载的图标资源,所以出现了图标资源的泄漏。解决办法是,在加载图标时,添加上LR_SHARED标记位。修改后的代码如下:
void CWindowWnd::SetIcon( UINT nRes )
{
HICON hIcon = (HICON)::LoadImage( CPaintManagerUI::GetInstance()
, MAKEINTRESOURCE(nRes)
, IMAGE_ICON
, ::GetSystemMetrics(SM_CXICON)
, ::GetSystemMetrics(SM_CYICON)
, LR_DEFAULTCOLOR|LR_SHARED ); // 添加LR_SHARED标记位
ASSERT( hIcon );
::SendMessage( m_hWnd, WM_SETICON, (WPARAM)TRUE, (LPARAM)hIcon );
hIcon = (HICON)::LoadImage( CPaintManagerUI::GetInstance()
, MAKEINTRESOURCE(nRes)
, IMAGE_ICON
, ::GetSystemMetrics(SM_CXSMICON)
, ::GetSystemMetrics(SM_CYSMICON)
, LR_DEFAULTCOLOR|LR_SHARED );
ASSERT( hIcon );
::SendMessage( m_hWnd, WM_SETICON, (WPARAM)FALSE, (LPARAM)hIcon );
}此外,为了更深入的理解问题,还可以看一下LR_SHARED标记位的说明:

从上面说明得知,如果使用了LR_SHARED标记位,则系统在图标资源不再使用时去负责销毁图标资源。
4、总结
作为一个合格的Windows开发人员,要养成遇到问题就查MSDN的好习惯。MSDN上的文档化的详细说明和描述,可能就能帮助我们解决遇到的问题。对于一些工具,则可以尝试看看官网的一些注释和说明。
边栏推荐
- eBPF/Ftrace
- 3D laser slam:aloam---ceres optimization part and code analysis
- OPPO 自研大规模知识图谱及其在数智工程中的应用
- [introduction to C language] zzulioj 1021-1025
- Getting started for beginners: build your own blog with WordPress
- Go language series: how to build a go language development environment?
- Verilog的系统任务----$fopen、$fclose和$fdisplay, $fwrite,$fstrobe,$fmonitor
- Install the wireless network card driver
- 51: Chapter 5: develop admin management services: 4: develop [add admin account, interface]; (only [user name + password, method]; [@t...] annotation controls transactions; when setting cookies, do yo
- 字节跳动 AI Lab 总监李航:语言模型的过去、现在和未来
猜你喜欢

OPPO 自研大规模知识图谱及其在数智工程中的应用

Fiddler bag capturing Tool + night God simulator

图像特征及提取

Go language series: how to build a go language development environment?

Product manager experience 100 (XI) - Strategic Product Manager: model and methodology

How to pass parameters in JNI program

产品经理经验谈100篇(十一)-策略产品经理:模型与方法论

使用putty设置基于 SSH 密钥的身份验证

SNMP (Simple Network Management Protocol)

基于frp实现内网穿透——借助公网服务器实现ssh远程连接内网服务器
随机推荐
How can electric complete set enterprises do well in cost and profit management with the help of ERP system?
Design of network abnormal traffic analysis system
Absolute positioning
SCI thesis writing
SQL group by statement
JS divides the array into two-dimensional arrays according to the specified attribute values
Feign client automatic assembly of three clients
纵横靶场-图片的奥秘
【2023复旦微电子提前批笔试题】~ 题目及参考答案
QT clipboard qclipboard copy paste custom data
Classification and application of slip rings
uniapp防止连续点击出错
How to fix the slip ring
Pat class B 1109 good at C (detailed)
eBPF/Ftrace
Musk was exposed to be the founder of Google: he broke up his best friend's second marriage and knelt down to beg for forgiveness
附加:【URLEncoder.encode(待编码字符串, “编码方式“);】(是什么?;我们向cookie中设置值的时候,为什么要使用这个去编码?)(待完善……)
echart折线图默认显示最后一个点以及纵向虚线
QT excellent open source project 13: qscintilla
2022年7月24日 暑假第二周训练

