目录
1、前言
2、回退代码后,ToolTip窗口不显示了
3、使用历史版本比对法找到ToolTip窗口何时开始不显示的
4、为了给字体设置ClearType属性,_WIN32_WINNT宏的值从0x500修改成0x501
5、将_WIN32_WINNT宏值由从0x500修改成0x501,导致系统的ToolTip窗口组件出问题
6、解决办法
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html
1、前言
最近测试同事发现,在Slider滑块控件显示百分比的ToolTip时有问题,在拖动滑块时ToolTip窗口没有显示实时的百分比,显示的还是之前的百分比,如下所示:
于是在排查这个问题的过程中遇到了一系列问题,在此做个总结,以供大家借鉴和参考。
2、回退代码后,ToolTip窗口不显示了
这个问题是必现的。用老版本对比了一下,老版本中这个滑块控件的ToolTip显示是没有问题的。那说明这个问题肯定是修改代码,改出来的。于是,到svn上查看代码的修改记录,果然前段时间有人修改过相关代码。于是尝试将修改的代码还原,看看还没有问题,验证一下是不是这次修改引起的。
将代码还原之后,重新编译执行,发现问题更严重了,ToolTip提示窗口直接不显示了。这就有点奇怪了,老版本中是没问题的,估计更早的时候还有人修改过代码,导致ToolTip提示窗口不显示了。
在这里,给大家重点推荐一下我的几个热门畅销专栏:
专栏1:(该专栏订阅量已达到400个,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
专栏中的文章均是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!
专栏3:
开源组件及数据库技术https://blog.csdn.net/chenlycly/category_12458859.html
以多年的开发实战为基础,分享一些开源组件及数据库技术!
3、使用历史版本比对法找到ToolTip窗口何时开始不显示的
我们之前讲过可以使用历史版本比对法去排查一些问题,即安装不同时间点的版本,看看问题是从哪个时间点开始出现的。找到这个时间点后,大概就是前一天提交的代码或者底层发布过来的新dll库导致的。
于是使用二分法安装不同时间点的版本,发现之前的版本长时间都有ToolTip不显示的问题,一直没有人反馈,所有界面的ToolTip都有问题。一直找到一年前的某个版本没有问题。于是继续使用二分法缩小范围,最终找到ToolTip窗口从那一天开始不显示的。
于是到svn上查看前一天的代码修改记录,在directui库的Stdafx.h头文件中定义了_WIN32_WINNT宏,用该宏指定当前程序支持的最低操作系统版本,将该_WIN32_WINNT宏的值由0x500改成了0x501,事后证明正是这个修改导致的ToolTip窗口不显示的。
4、为了给字体设置ClearType属性,_WIN32_WINNT宏的值从0x500修改成0x501
为什么要将_WIN32_WINNT宏的值从0x500修改成0x501呢?当时我们的软件在设置高DPI的电脑上运行,系统会对软件界面进行放大,放大后字体会比较模糊,为了将文字显示的更清晰一点,给绘制文字的字体设置ClearType属性,这样绘制出来的文字会更有棱角一点,会清晰一些。操作系统也支持设置ClearType选项,如下所示:
如何给字体设置ClearType属性呢?我们在使用LOGFONT结构体创建字体时,直接给LOGFONT结构体中的lfQuality字段设置CLEARTYPE_QUALITY值即可,如下所示:
跳转到CLEARTYPE_QUALITY宏的定义处:
可以看到只有当定义的NT版本达到0x501,才定义这个CLEARTYPE_QUALITY宏,但我们的directui工程中将_WIN32_WINNT宏值定义为0x500,于是为了支持CLEARTYPE_QUALITY宏,直接_WIN32_WINNT的值由0x500改成0x501。
5、将_WIN32_WINNT宏值由从0x500修改成0x501,导致系统的ToolTip窗口组件出问题
开始出问题的时间点之前值只对这个_WIN32_WINNT宏值做了修改,难道是这个宏值对系统的ToolTip窗口也有影响?于是将_WIN32_WINNT宏的值重新改成0x500,将使用CLEARTYPE_QUALITY宏的代码注释掉,重新编译运行,ToolTip窗口就能正常显示了。果然与_WIN32_WINNT宏的值有关系!
那将_WIN32_WINNT宏的值设置成为0x501,为啥会导致ToolTip窗口组件出问题呢?这个要看ToolTip窗口对应的结构体TOOLINFO,可以跳转到这个结构体的定义处,如下所示:
TOOLINFO最后一个字段是void *lpReserved,这个字段有点特殊,当NTDDI_VERSION大于NTDDI_WINXP时才会定义。
我们可以go到这两个宏的定义处,NTDDI_WINXP宏的值为0x05010000:
而NTDDI_VERSION是直接与_WIN32_WINNT相关的:
本例中我们将_WIN32_WINNT宏改成0x501,所以#if (NTDDI_VERSION >= NTDDI_WINXP)条件为真,所以当前场景下,TOOLINFO最后一个字段void *lpReserved是有效的!
我们在定义TOOLINFO结构体对象时,给该结构体的cbSize字段赋值的是sizeof(TOOLINFO):
即当前TOOLINFO结构体的大小,包含void *lpReserved字段的。而Windows系统默认加载的是comctl 5.82版本组件,这个版本中的ToolTip组件对应的TOOLINFO结构体中的size不包含void *lpReserved字段,而我们当前给cbSize字段设置的大小是包含void *lpReserved的,所以cbSize字段设大了,所以ToolTip组件内部出错了,导致ToolTip窗口显示不出来了。
在Win32编程中使用ToolTip窗口组件时,先是调用CreateWindowEx创建一个TOOLTIPS_CLASS类窗口,然后给这个窗口发送TTM_ADDTOOL消息将定义的TOOLINFO结构体对象值(TOOLINFO结构体中存放的是给ToolTip窗口设置属性),如下所示:
应该是这一步设置进去后就出问题了。
6、解决办法
在给TOOLINFO结构体对象的cbSize字段赋值时,不再赋sizeof(TOOLINFO),而是赋TTTOOLINFOA_V2_SIZE宏值:
::ZeroMemory(&m_ToolTip, sizeof(TOOLINFO));
// 赋值为TTTOOLINFOA_V2_SIZE,不用sizeof(TOOLINFO)
m_ToolTip.cbSize = TTTOOLINFOA_V2_SIZE;
这个宏值就是将void *lpReserved字段去除后的TOOLINFO结构体大小。可以查看TTTOOLINFOA_V2_SIZE宏的定义看出来:
#define TTTOOLINFOA_V1_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lpszText)
#define TTTOOLINFOW_V1_SIZE CCSIZEOF_STRUCT(TTTOOLINFOW, lpszText)
// 1、TTTOOLINFOW_V2_SIZE宏的定义
#define TTTOOLINFOA_V2_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lParam)
#define TTTOOLINFOW_V2_SIZE CCSIZEOF_STRUCT(TTTOOLINFOW, lParam)
#define TTTOOLINFOA_V3_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lpReserved)
#define TTTOOLINFOW_V3_SIZE CCSIZEOF_STRUCT(TTTOOLINFOW, lpReserved)
// 2、CCSIZEOF_STRUCT宏的定义
#ifndef CCSIZEOF_STRUCT
#define CCSIZEOF_STRUCT(structname, member) (((int)((LPBYTE)(&((structname*)0)->member) - ((LPBYTE)((structname*)0)))) + sizeof(((structname*)0)->member))
#endif
// 3、TTTOOLINFO宏的定义
typedef struct tagTOOLINFOW {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT_PTR uId;
RECT rect;
HINSTANCE hinst;
LPWSTR lpszText;
LPARAM lParam;
#if (NTDDI_VERSION >= NTDDI_WINXP)
void *lpReserved;
#endif
} TTTOOLINFOW, NEAR *PTOOLINFOW, *LPTTTOOLINFOW;
根据TTTOOLINFOA_V2_SIZE的定义,TTTOOLINFOA_V2_SIZE就是只计算到TOOLINFO倒数第二个参数lParam的长度,包含lParam成员长度,正好将最后一个字段void *lpReserved给跳过去了,这样ToolTip就能正常显示了。
也可以使用#pragma comment预编译指令,将comctrl指定为使用6.0版本的:
#pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df' language='*'\"")
对应范例如下:
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "comctl32.Lib")
#pragma comment(lib, "gdi32.Lib")
#pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df' language='*'\"")