Visual Studio / MFC 之 bug 搜集(不断更新中)

2008-05-26 21:01:37 旧日重来

Visual Studio 是 Windows 下最优秀和最常用的编程开发工具之一。正如 Windows 一样,Visual Studio 的 bug 也不少,但是微软公司大了,气也粗的很,很多 bug 存在了很久了,就是不见它修正一下。编程工具本身的 bug 显然是令人头疼的,这里搜集了 Visual Studio 和 MFC 的一些 bug,供大家参考。不过需要指出的是,虽然 VS 的 bug 不少,但毕竟是经过了多年考验的东东,实际开发中遇到问题,还是首先怀疑一下自己的代码,由于 VS 的 bug 造成的错误碰上的概率还是非常之小的。

本文不断更新中,最新列表请查看: http://live.aulddays.com/tech/08/vsbugs/

目录:

  1. CHttpFile::QueryInfo() 参数检查错误
  2. CToolBar 按钮位置计算 bug
  3. VC++ 6.0 for 循环变量作用域问题

CHttpFile::QueryInfo() 参数检查错误

存在版本:VS6.0-VS2005;危害程度:;影响范围:小。

在 MFC CHttpFile 类中有如下函数:

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, CString& str,
	LPDWORD lpdwIndex = NULL) const; 

根据 MSDN,其中的 dwInfoLevel 参数指明了查询的种类,并且可以包含以下这些 Flags:http://topic.csdn.net/t/20050329/09/3889748.html

HTTP_QUERY_CUSTOM
HTTP_QUERY_FLAG_REQUEST_HEADERS
HTTP_QUERY_FLAG_SYSTEMTIME
HTTP_QUERY_FLAG_NUMBER

可是测试发现,只要 dwInfoLevel 带了以上标志的任何一个就会引发断言错误。而用相同的参数调用该函数的另一个功能基本一样的重载:

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel,
  LPVOID lpvBuffer, LPDWORD lpdwBufferLength,
  LPDWORD lpdwIndex = NULL) const

就可以成功。对照这两个函数的源代码的开头部分就可以发现(VC++ 6.0):

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, CString& str,
	LPDWORD lpdwIndex) const;
{
  ASSERT(dwInfoLevel <= HTTP_QUERY_MAX && dwInfoLevel >= 0);

  // 函数的剩余部分
}
BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel,
  LPVOID lpvBuffer, LPDWORD lpdwBufferLength,
  LPDWORD lpdwIndex) const
{
  ASSERT((HTTP_QUERY_HEADER_MASK & dwInfoLevel)
    <= HTTP_QUERY_MAX && dwInfoLevel != 0);

  // 函数的剩余部分
}

很显然第一个重载忽视了 HTTP_QUERY_FLAG_REQUEST_HEADERS 等参数的存在,因而造成了误报的断言错误,而第二个版本则考虑更急全面。值得注意的是,在第一个版本中甚至还出现了 dwInfoLevel >= 0 这样的低级错误,要知道,DWORD 类型是无符号的,因而值总是大于等于 0 的!

直到最新的 VS 中 MS 也没有修正这个 bug。不过 知道了原因,就很容易避免这个bug了。可以选择调用两个重载中正确的那个(它们的功能完全相同,只是传递字符串的方式不同),也可以自己继承 CHttpFile 类并将错误的函数修改成正确的形式。

CToolBar 按钮位置计算 bug

存在版本:VS6.0-VS2005;危害程度:小;影响范围:

如下图所示,对于默认的 MFC CToolBar 工具栏(图中的 Original MFC Toolbar),如果把鼠标移到任意一个按钮的最左边一个像素的位置,相应的工具提示和状态栏提示会显示其左边一个按钮的信息,而不是该按钮的信息。

CToolBar 按钮位置计算 bug

查看 CToolBar 中相关代码的原始版本可以发现(Visual C++ 6.0 版本,新版中的有细微差别,但这个 bug 一直存在):

int CToolBar::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
ASSERT_VALID(this);
ASSERT(::IsWindow(m_hWnd));
// check child windows first by calling CControlBar
int nHit = CControlBar::OnToolHitTest(point, pTI);
if (nHit != -1)
   return nHit;

  // now hit test against CToolBar buttons
  CToolBar* pBar = (CToolBar*)this;
  int nButtons = (int)pBar->DefWindowProc(TB_BUTTONCOUNT, 0, 0);
  for (int i = 0; i < nButtons; i++)
  {
    CRect rect;
    TBBUTTON button;
    if (pBar->DefWindowProc(TB_GETITEMRECT, i, (LPARAM)&rect))
    {
      ++rect.bottom;  // Bug 代码行 (旧日重来注)
      ++rect.right;   // Bug 代码行 (旧日重来注)
      if (rect.PtInRect(point) &&
        pBar->DefWindowProc(TB_GETBUTTON, i, (LPARAM)&button) &&
        !(button.fsStyle & TBSTYLE_SEP))
      {
        int nHit = GetItemID(i);
        if (pTI != NULL && pTI->cbSize > = sizeof(AFX_OLDTOOLINFO))
          {
            pTI->hwnd = m_hWnd;
            pTI->rect = rect;
            pTI->uId = nHit;
            pTI->lpszText = LPSTR_TEXTCALLBACK;
          }
        // found matching rect, return the ID of the button
        return nHit != 0 ? nHit : -1;
      }
    }
  }
  return -1;
}

在上面的代码中,标记了”bug 代码行“注释的两行就是造成此 bug 的原因。纠正此 bug 的方法就是自己写个继承原 CToolBar 的类,并且重载原始的 CToolBar::OnToolHitTest() 函数,代码和原来的相同,只是去掉错误的两行,并将 CToolBar* pBar = (CToolBar*)this;  这行里的 CToolBar 改成你自己的类名即可。修改后的效果如上图的 Fixed Toolbar。

这个 bug 虽然不起眼,但是影响范围及其广泛,几乎所有用 MFC 写的程序都受到影响。而此 bug 的起源也非常让人捉摸不透。很显然,引起 bug 的两行代码是故意加上的,但是作者又没有给出任何信息为甚么要加上它们。一个貌似合理的解释是 MFC 的作者想给自己的作品加上某种“签名”,这样他们可以很容易的发现哪些程序用了 MFC。另一个相似的例子是 MFC 的状态栏,仔细看的话,MFC 的状态栏比系统默认的状态栏“矮”了一截,而功能上完全不受影响。凭这两样,有经验的人可以从界面上一眼就认出用 MFC 写的程序!

VC++ 6.0 for 循环变量作用域问题

存在版本:VS6.0;危害程度:;影响范围:

在 VC++6.0 中,for 后面循环条件中定义的变量在 for 循环结束后仍然有效,但按照标准 C++ 的规定,循环条件中定义的变量的作用域只限于该 for 循环体中。

严格来说这不能算是一个 bug,而是 VS6.0 发布的时候 C++ 还未标准化,并且在传统 C++ 中对 for 循环变量的解释就是如 VC++6.0 中那样解释的。但是 VS6.0 的这个问题影响相当深远,几乎所有的 VC++6.0 的代码在向 VS 高版本移植时都会遇到此问题。甚至 MS 自己的代码都受到影响:在最新的 DirectShow SDK(包含于 Platform SDK)中,由于引入了一些新特性,该版本的 DShow SDK 不再支持 VS6.0 而只支持高版本的 VS;但富有戏剧性的是 DShow SDK 自带的很多示例代码仍然是按照 VC++6.0 的 for 规范写的,因此在高版本的 VC++ 中反而编译不过,着实让人为 MS 大汗一把……

此问题的具体描述如下:

  // VC++ 6.0 for 循环变量作用域问题演示代码 by 旧日重来
  void foo()
  {
    for(int i = 0; i < 100; ++i)
    {
      // Some operation
    }
    
    // 以下这行按标准 C++ 中正确,但 VC++6.0 中语法错
    for(int i = 0; i < 100; ++i)
    {
      // Some operation
    }
    
    // 以下这行VC++6.0 中正确,但按标准 C++ 语法错
    i = 0;
  }

要写出同时兼容 VC++6.0 和高版本 VC++ 的代码有两个方法,一是把要重复使用的循环变量的定义提到 for 循环条件之前,二是针对 VC++6.0 在需要重复使用循环变量的地方对 for 进行一下“改造”,使之可以“模拟”标准 C++ 的规定:

#ifdef _MSC_VER
#if _MSC_VER == 1200  // 只对 VC++6.0
#define for if(false);else for
#endif
#endif

查看:原文地址;来源:live.aulddays.com


注意:本站所有文章除特别说明外均为原创,版权所有,转载请务必以超链接方式注明作者出处,并禁止用作商业用途