用通过下面的两个示例,来说明官方给出的文档化说明的重要性。一个是MSDN上给出的说明,一个是GDIView GDI泄漏检测工具官方给出的说明。以这些官方说明为线索,可以找到我们程序出问题的地方,进而将问题解决掉。
1、聊天服务器时间的本地时区和夏时令的问题
(1)TL的聊天时间使用服务器时间,TL底层的XMPP客户端需要和服务器时间进行同步,进而获取服务器时间,用于聊天信息的时间戳。服务器传递过来的是UTC+0的时间,是字符串格式的年月日时间,不是整数时间。XMPP客户端要使用时差bias、mktime和localtime等将字符串年月日时间转换成UTC+0的整数时间,在发送消息时,将整数时间转成字符串年月日时间,发送给对端。接收端接收到消息时,将字符串年月日时间转换成整数时间,填充到消息结构体中供上层使用。
(2)收到服务器时间请求的回应时,将收到的字符串年月日时间转换成整数时间,如下所示:
后来确认是转换过程中多出了1个小时。当时的测试环境是:发送端设置UTC+10的带夏时令的时区,如下:
发送端:发送端发出消息后,收到终端层的消息发送的rsp,rsp中携带服务器时间,显示框中显示就是这个返回的服务器时间,比本地时间多了一个小时,
接收端:接收端接收到消息后,消息中携带了发送时的服务器时间,接收端转换出来的本地时间也多了一个小时,按讲接收端收到消息后也用了mktime,应该是多了两个小时才对啊?哈哈,接收端当时设置的是UTC+8的北京时区,没有夏时令调整的,所以接收端只多了一个小时。
(3)发送端发送消息时,构建UTC+0字符串年月日时间,如下:
接收端收到消息时,将UTC+0字符串年月日时间转成整数时间,如下:
(4)为定位问题,查看mktime函数的实现:
那么dstbias是如何实时get来的呢?因为用户可能中途改变了时区,涉及到有夏时令的,dstbias就不是默认的-3600了,相关代码如下:
由上述代码可知,在__tzset内部调用GetTimeZoneInfomation,实时的计算了dstbias的值,但是程序启动后,再改变时区,是不会实时更新dstbias等值的,即只在启动后第一次调用mktime时才会调用GetTimeZoneInfomation,实时获取dstbias等值。
(5)在asctime函数说明找到对struct tm的说明,如下:
(6)多一个小时问题的定位:
经调试发现,在设置下面的时区后,dstbias为-3600,正好一个小时
是下面代码的问题,是memset的问题:
即tTime.tm_isdst为0,进入不了上面的分支,多余的一个小时没有减掉,所以多了一个小时。
(7)VS2008的MSDN中对tm_isdst字段的说明不太清楚,如下:
但是最新的网页上的MSDN中说明的很清楚,如下:(所以再一次说明,遇到VS2008MSDN上讲的不清楚或者不明白的问题时,不妨到MSDN网站上查看一下最新的说明,说不定会有我们需要的帮助说明)
通过查看mktime的内部实现,里面调用了_localtime64_s和_gmtime64_s函数,在这两个函数中,都有struct tm结构体的初始化,都初始化0xFF,
即将tm的结构体的tm_isdst设置为0xFF,即小于0,由运行时库内部决定是否将夏时令时间dstbias,这样初始化XMPP侧的时间处理就没问题了。对于CTime类而言,也有参考的,传入的年月日时间参数时的构造函数,如下,最后的是否考虑夏时令也是传递的负值:
(8)时间处理函数mktime、localtime、gmtime函数和CTime类的说明:
mktime:将tm结构体时间转换成整数时间,会减去对UTC+0的时差值bias绝对值;
localtime:将整数时间转换成tm结构体时间,会加上对UTC+0的时差值bias绝对值;
gmtime:将整数时间转换成tm结构体时间,不进行时差的处理;
CTime类:
传入的整数值是UTC+0的整数时间。
由(7)中的代码得知,如果传入本地时区的年月日时间,在内部要经过mktime,转换成UTC+0整数时间的。
CTime的多个方法都是mktime、localtime的封装,参照这些函数的转换即可。
2、无从下手的GDI资源泄漏问题
(1)新版TL中打开部分窗口后关闭,GDI句柄数每次都上升6个,即有GDI资源泄漏。按讲打开的窗口销毁后,GDI相关资源都会销毁,这就比较奇怪了。由于部分窗口在打开关闭后没有泄漏,对比了一下,暂时可以考虑directui库没有泄漏问题。通过GDIView观察,GDI Total列没有增长,All GDI列每次都有增长。这就比较奇怪了,一般我们的资源泄漏主要是pen、bitmap等,结果这次这些常规GDI对象没有泄漏,感觉无从查起了。
(2)于是尝试着到GDIView的官网上查看一下,结果找到了对应的说明,如下:
官网:http://www.nirsoft.net/utils/gdi_handles.html
由上述可知,出现GDI Total列没有增长,All GDI列有增长的原因可能是图标资源或者光标资源在创建后没有释放引起的。于是乎想到,部分窗口在关闭后没有GDI句柄上升的情况,仔细想了一下,这些窗口都没有任务栏窗口,都没有设置icon图标。
(3)于是找到设置图标的代码,如下:
这个函数中只用到了LoadImage,到msdn上查看LoadImage的说明,找到了可能存在icon资源泄漏的情况,如下:
也就是说,使用LoadImage时如果没有设置LR_SHARED标记,则需要手动去Destroy掉这些资源,否则会导致泄漏。于是添加了LR_SHARED标记,修改后的代码如下:
最后再用GDIView查看,就没有GDI资源泄漏了。