CArchive::ReadString(CString& rString)的一个bug

我一个小工具,vs2015 MFC对话框程序,任务是读取很多个小的文本文件,每个文件的内容大概几十到1000多字节。

这些文件的内容主要是中文文字,有换行,也可能会有数字、字母、标点符号等,就是一个普通的文本文件。

这些文件的编码不确定,但目前为止发现了三种编码,分别是unicode、ansi、utf-8,其中大多数为ansi编码。本文说的有问题,就是在读取有的ansi编码的文本文件时出现问题。

mfc程序采用unicode编码,读取文件的部分大致如下:

CFile file;
file.Open(strFileName, CFile::modeRead);
int fenc = 0;	// 文件的编码类型
CString strFileConten = TEXT("");
CString strTmp;
file.Read(&fenc, 2);
CArchive ar(&file, CArchive::load);
ar.GetFile()->SeekToBegin();
while (ar.ReadString(strTmp)
{
	// 如果文件是unicode编码,则每次只读取一行,而且行结尾没有换行符,所以这里就要给它加上换行符,否则后面在取一行时,就会一次性全部取出来而导致异常
	if (strFileConten.GetLength())
		strFileConten += TEXT("\r\n");
	strFileConten += strTmp;
}

这段代码就是打开文件并读取它。全部按照unicode来读取,在读完之后,根据fenc以及取strFileConten的部分内容进行判断实际的编码,然后再按照对应的编码进行处理,这个流程是没有问题的,上面说的三种编码都可以成功处理,现在问题出在读取文件的while上了。

对于有的ansi文件,成功读取了fenc,但是while循环一次都没有进去,明明文件是有内容的,而且内容也没有特别的情况,为啥就读取失败了呢?

于是就跟踪进ReadString()函数中,这个函数如下所示:

BOOL CArchive::ReadString(CString& rString)
{
	rString = _T("");    // empty string without deallocating
	const int nMaxSize = 128;
	LPTSTR lpsz = rString.GetBuffer(nMaxSize);
	LPTSTR lpszResult;
	int nLen;
	for (;;)
	{
		lpszResult = ReadString(lpsz, (UINT)-nMaxSize); // store the newline
		rString.ReleaseBuffer();

		// if string is read completely or EOF
		if (lpszResult == NULL ||
			(nLen = AtlStrLen(lpsz)) < nMaxSize ||
			lpsz[nLen-1] == '\n')
		{
			break;
		}

		nLen = rString.GetLength();
		lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
	}

	// remove '\n' from end of string if present
	lpsz = rString.GetBuffer(0);
	nLen = rString.GetLength();
	if (nLen != 0 && lpsz[nLen-1] == '\n')
		rString.GetBufferSetLength(nLen-1);

	return lpszResult != NULL;
}

根据我的理解,这个函数是每次读取128个字符,由于是按照unicode读取的,所以每个字符就是2个字节,每次最多读取256个字节,如果中间遇到了换行符,就停止读取,然后返回,否则就一直读取下,直到把整个文件读取完毕。

对于有问题的一个文件,我跟踪进来查看,发现for中的ReadString是没有问题的,它返回的lpszResult是有值的,但这仅是第一次循环,在第二次循环时,它返回了NULL,于是,在函数的结尾处,就变成了 return NULL != NULL,即返回了FALSE,于是,我的代码就是读取错误了,但此时,strTmp里是有内容的,而且是正常的。从这里看,这个函数是有问题的,其实问题不止一个,目前发现了两个。

通过以上的跟踪,说明了这个函数采用 lpszResult是否为空来判断返回值是不正确的,它只表示了最后一次的状态,而不能说明前面的状态。再仔细看读取失败的文件,它的长度是257个字节,后来我删掉了两个字节(一个回车换行),变成255个字节后,再去读取,发现就没有报错了,我把另外一个文件的内容长度也弄成257字节,结果读它也是失败。这样问题就比较明了了,当文件的内容长度刚好是257时,它就失败。仔细检查发现,在ReadString()中,for第一遍刚好读取256字节,就剩下一个字节了,for的第二遍时候,其调用的ReadString()函数内,直接导致了文件结尾,由于还没有读出一个字节,所以它就直接返回了NULL,结果这个正常的ReadString()也返回了FALSE。

知道了问题,就对它进行改造,采用类继承的办法解决。类声明:

class CMyArchive :
	public CArchive
{
public:
	CMyArchive(CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL);
	virtual ~CMyArchive();

	BOOL ReadString(CString& rString);
};

类定义:

CMyArchive::CMyArchive(CFile* pFile, UINT nMode, int nBufSize, void* lpBuf):
	CArchive(pFile, nMode, nBufSize, lpBuf)
{
}


CMyArchive::~CMyArchive()
{
}

BOOL CMyArchive::ReadString(CString& rString)
{
	rString = _T("");    // empty string without deallocating
	const int nMaxSize = 128;
	LPTSTR lpsz = rString.GetBuffer(nMaxSize);
	LPTSTR lpszResult = NULL;	// 这里必需初始化为NULL
	LPTSTR lpResTmp;
	int nLen;
	for (;;)
	{
		lpResTmp = CArchive::ReadString(lpsz, (UINT)-nMaxSize); // store the newline
		rString.ReleaseBuffer();

		// 这里增加一个判断附值
		if (lpResTmp)
			lpszResult = lpResTmp;

		// if string is read completely or EOF
		if (lpszResult == NULL ||
			(nLen = AtlStrLen(lpsz)) < nMaxSize ||
			lpsz[nLen - 1] == '\n')
		{
			break;
		}

		nLen = rString.GetLength();
		lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
	}

	// remove '\n' from end of string if present
	lpsz = rString.GetBuffer(0);
	nLen = rString.GetLength();
	if (nLen != 0 && lpsz[nLen - 1] == '\n')
		rString.GetBufferSetLength(nLen - 1);

	return lpszResult != NULL;
}

使用的时候,仅仅把原来的 CArchive ar(&file, CArchive::load); 改成 CMyArchive ar(&file, CArchive::load); 即可。

这样改造之后,再测试,问题解决。

 

后记:

上面说有两个问题,今天解决了一个问题,这里面的另外一个问题,就是在上面描述的情况下,如果文件的长度是寄数,则会导致少读取最后一个字节。这个问题后面有时间的时候再来解决,现在是没有时间了,这个问题针对我的这个工具,影响不大,所以做了一些处理就暂时避过了由其导致的问题。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值