我们要学会查看技术细节点的文档化说明
Posted dvlinker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我们要学会查看技术细节点的文档化说明相关的知识,希望对你有一定的参考价值。
目录
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上的文档化的详细说明和描述,可能就能帮助我们解决遇到的问题。对于一些工具,则可以尝试看看官网的一些注释和说明。
以上是关于我们要学会查看技术细节点的文档化说明的主要内容,如果未能解决你的问题,请参考以下文章
scratch抽奖 电子学会图形化编程scratch等级考试四级真题和答案解析2019-12
关于localStorage和sessionStorage存储用法的一些细节说明----------localStorage和sessionStorage存储必须字符串化