发现了一个CListCtrl::GetItemText的Bug及解决方案

近段在写个小程序时发现了CListCtrl::GetItemText的Bug,微软的源代码如下:

VC6对应文件: VC98\MFC\SRC\winctrl2.cpp
VC7.1对应文件: Vc7\atlmfc\src\mfc\winctrl2.cpp
CString CListCtrl::GetItemText(int nItem, int nSubItem) const
{
 ASSERT(::IsWindow(m_hWnd));
 LVITEM lvi;
 memset(&lvi, 0, sizeof(LVITEM));
 lvi.iSubItem = nSubItem;
 CString str;
 int nLen = 128;
 int nRes;
 do
 {
  nLen *= 2;
  lvi.cchTextMax = nLen;
  lvi.pszText = str.GetBufferSetLength(nLen);
  nRes  = (int)::SendMessage(m_hWnd, LVM_GETITEMTEXT, (WPARAM)nItem,
   (LPARAM)&lvi);
 } while (nRes == nLen-1);
 str.ReleaseBuffer();
 return str;
}

问题出在标红色的句子,我发现如果读取的第(lvi.pszText + lvi.cchTextMax - 1)字节刚好是双字节字符的第一个字节时(比如中文汉字的第一个字节),该字节最后被替换为'\0'(这符合lvi.pszText以'\0'结尾的约定),但返回读取的字节数是lvi.cchTextMax而不是预想的(lvi.cchTextMax-1),导致(nRet == nLen-1)结果为FALSE而结束读取数据,但是数据仍没有读完,需要继续读取。

解决办法很简单,把(nRet == nLen-1)改成(nRet >= nLen - 1),真搞不懂为什么没有人向微软反应这个错误并促使他们改过来呢。

我自己写了一个外部函数:

CString GetItemText(CListCtrl& lvw, int nItem, int nSubItem)
{
 ASSERT(::IsWindow(lvw.m_hWnd));
 LVITEM lvi;
 memset(&lvi, 0, sizeof(LVITEM));
 lvi.iSubItem = nSubItem;
 CString str;
 int nLen = 128;
 int nRes;
 do
 {
  nLen *= 2;
  lvi.cchTextMax = nLen;
  lvi.pszText = str.GetBufferSetLength(nLen);
  nRes  = (int)::SendMessage(lvw.m_hWnd, LVM_GETITEMTEXT, (WPARAM)nItem,
   (LPARAM)&lvi);
 } while (nRes >= nLen-1);
 str.ReleaseBuffer();
 return str;
}

Leave a Reply


提醒: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。请务必注意user必须和评论者名相匹配(大小写一致)。