由AFX_IDW_PANE_FIRST宏的含义分析界面库XTP的一个bug
来源:互联网 发布:软件销售招聘 编辑:程序博客网 时间:2024/06/04 19:10
Codejock的界面库Xtreme Toolkit Pro(XTP)是当前MFC开发中发展得比较成熟,应用也颇为广泛的几个界面库之一,其强大的界面美化功能以及简便的使用步骤深受不少MFC程序员的青睐。应用XTP进行MFC程序的开发能够在极大地减少开发周期的前提下,编写出专业化的windows程序界面。
笔者在实际使用XTP的过程中,发现了一个隐藏得比较深的、一般的应用不会遇到的bug。概括地说,这个bug是与视图的静态拆分 (CXTSplitterWnd)以及标签式视图(CXTTabView)相关的,当混合使用这两个特性时,就有可能遇到这个潜在的bug。
首先让我来描述一下触发这个bug的过程:在一个基于MFC的单文档程序中使用CXTSplitterWnd将主视图拆分为左右两个视图,再在右边的视图(直接从CXTTabView继承)中创建Tab View,如果创建了多个Tab View,当激活第2个(从0开始)以上的Tab View后,此时若调用CXTSplitterWnd的HideColumn将左边的视图(非Tab View所在的主视图)隐藏,就会发生断言失败的错误。断言失败对话框会显示在MFC的源代码winsplit.cpp中的第361行出错,即在这个函数中:
当出现断言失败时,我们通常的调试方法就是看调用堆栈,看到底是哪行代码出的问题。通过查看调用堆栈,我们可以清楚的看到在原因出在CXTSplitterWnd的HideColumn中的GetActivePane那一行。
跟踪GetActivePane的调用,看到如下代码:
CWnd* CSplitterWnd::GetActivePane(int* pRow, int* pCol)// return active view, NULL when no active view{ASSERT_VALID(this);// attempt to use active view of frame windowCWnd* pView = NULL;CFrameWnd* pFrameWnd = EnsureParentFrame();pView = pFrameWnd->GetActiveView();// failing that, use the current focusif (pView == NULL)pView = GetFocus();// make sure the pane is a child pane of the splitterif (pView != NULL && !IsChildPane(pView, pRow, pCol)) // 行BpView = NULL;return pView;}
从上面的堆栈跟踪可以很清楚地看到程序的执行路径和逻辑:调用HideColumn后,程序会检查当前将要隐藏的pane中是否存在当前的活动视图,通过调用GetActivePane(行A)来获得当前活动视图所在的行号和列号。在函数GetActivePane中会获得pFrameWnd的当前活动视图并调用IsChildPane来检查当前活动视图是否属于拆分器中的子窗格。而就在行B所示的IsChildPane这个函数中失败了,通过在调试状态下的观察,可以看到是因为行C中*pCol < m_nCols的值为false,所以导致的断言失败。
那么*pCol和m_nCols到底代表着什么含义呢,*pCol是由当前窗格的ID与AFX_IDW_PANE_FIRST宏的值计算得出的,那么为什么*pCol < m_nCols的值为false?要想搞清楚这个问题,就必须知道AFX_IDW_PANE_FIRST的含义。
查看MFC的源代码,找到AFX_IDW_PANE_FIRST宏的定义:
#define AFX_IDW_PANE_FIRST 0xE900 // first pane (256 max)
以及与之对应的另一个宏:
#define AFX_IDW_PANE_LAST 0xE9ff
明白了上述原理后,回头再看看*pCol和m_nCols以及行C附近的代码,不难明白*pCol指的是当前列的列号,由当前的ID除以16后的余数得到(即求“模”),而m_nCols则表达了当前的列的个数,可以从MFC源码中的CreateStatic、SplitColumn、DeleteColumn等函数中被赋值的过程推断得出。正常情况下*pCol的值肯定是要小于m_nCols的(由于列号从0开始),当出现*pCol的值大于或者等于m_nCols时,就会导致断言失败。
上述的分析似乎很显而易见,逻辑上也很明了,但大家不禁会问:“为什么当前列号会大于等于列数呢?”这得看CXTTabView中的tab在创建的时候到底做了什么,来看源码,见XTP源码中的XTTabBase.cpp文件:
可以看出,XTP的源码实现中竟然将在TabView中嵌入的子窗口视图的ID号从AFX_IDW_PANE_FIRST开始编号?!这样的实现在TabView未作为SplitterWnd的pane时是不会出问题的,但一旦在诸如文章一开始提到的类似情境时,便会出现问题。那么,问题是怎么发生的呢?让我们来剖析剖析。
当在Mainfrm中用CreateStatic将主视图被拆分为左右两个视图时,左边的视图其ID被赋值为AFX_IDW_PANE_FIRST即59648,右边的则为59649(当非嵌套的splitterwnd时,pane的ID一般都是从AFX_IDW_PANE_FIRST开始的)。接下来在右边的pane中创建了5个tab view,分别嵌入了5个tabctrl。从上面的源码可以看出,这5个tab view的ID从59648到59652,此时,当激活第二个tabctrl(从0开始)后,此时pFrameWnd的当前活动视图即为第二个 tabctrl内嵌的视图,即59650号窗口。这样当执行前面列出的IsChildPane函数时得到的列号*pCol即为(59650-59648)%16=2,而m_nCols为2,所以此时*pCol < m_nCols不成立!!从而导致断言失败。这就是问题发生的整个过程和缘由。
从上面的分析归纳出来一句话,这是一个由于误用AFX_IDW_PANE_FIRST宏而导致的在特定情境下才会发生的潜在的bug,其根本原因是创建tabview的时候用到的ID段号占用了静态拆分器预留的ID段号,要纠正这个bug,最彻底的做法是将创建tabview时用到的ID段号与静态拆分器预留的ID段号彻底分开。幸好XTP的源码是可以由我们自己修改的,可以通过如下方法解决此bug。
重新定义一个宏XTP_IDW_TAB_FIRST,表明tabview窗格的ID起始号,并且其数值区间的选取要避开常用的一些预留ID号的区间(这些预留段号可以参见afxres.h头文件)。然后修改XTTabBase.cpp文件中的CreateTabView函数,如下:
重新编译XTP的DLL文件,再次在文章开始的情景中使用HideColumn时,一切正常。
转载自: http://blog.csdn.net/zhouzhenyan/article/details/2714497
- 由AFX_IDW_PANE_FIRST宏的含义分析界面库XTP的一个bug
- 由AFX_IDW_PANE_FIRST宏的含义分析界面库XTP的一个bug
- 由一个BUG想到的
- 由一个bug想到的
- XTP dockingpane的使用方法
- 由一个疑难Bug想到的... ...
- 一个由sscanf函数引起的bug
- 由一个手机BUG想到的
- 一个由CountDownLatch引发的Bug
- XTP界面库使用的OFFICE2007皮肤在windows2000或2003 server中滚动条不能拖动
- 创建XTP图表的方法
- 养成好的编码习惯----由一个bug想到的
- 由一个vc内嵌asm的BUG引出的...
- 由一个vc内嵌asm的BUG引出的...
- 由一个vc内嵌asm的BUG引出的...
- 由一个vc内嵌asm的BUG引出的...
- 一个由Django的save方法引发的bug
- 由一个bug引起的关于list的思考
- oracle10g logminer
- 点亮GT2440开发板上的led(基于IO内存实现)
- H文件编写小结
- apache ftpserver 配置解析
- 谈谈对程序员的培养
- 由AFX_IDW_PANE_FIRST宏的含义分析界面库XTP的一个bug
- Content Provider
- 根据模型生成数据库
- 《Win32多线程程序设计》读书笔记之内核对象
- 机器学习降维算法一:PCA (Principal Component Analysis)
- Java开源网络服务器端组件
- CMOS Sensor的调试经验
- 排序算法的稳定性
- WAVE文件