您的位置:首页 > 其它

摘录:VC6自带的MFC4.2中CString.Format与CRecordSet的兼容性问题

2005-05-19 15:27 651 查看

VC6自带的MFC4.2中CString.Format与CRecordSet的兼容性问题

世界商业评论ICXO.COM ( 日期:2005-01-17 16:53)
今天我在bbs的vc版上转悠,看到由个哥们出了这样的问题:
说他在编写mfc的数据库程序(odbc)的时候出现了错误,再插入新记录后调用update的时候出现了assert,由于再bbs上,我和他通过信息交流了一下,发现他在addnew和update之间调用了format。直觉告诉我问题出在这里。
于是分析了一下。这个是我在bbs上发的帖子。

这个问题我仔细看了一下,问题出在mfc内部:下面所述仅适用于vc6带的mfc4.2

我们在使用odbc进行数据库的插入操作时,都是这么一个流程:
addnew()
//给成员赋值
update()
而在mfc的源文件dbcore.cpp 1040行,有这样一行注释:
// buffer address must not change - odbcs sqlbindcol depends upon this
由于mfc在进行默认的数据源绑定时,使用cstring绑定字符串型的成员,而cstring使用的是动态的内存管理方式,因此这个缓冲区地址其实是可以改变的,因此,在dbcore.cpp的1041行开始便是这样几句:

void* pvbind;
pvbind = value.getbuffer(0);
value.releasebuffer();
if (pvbind != pinfo->m_pvbindaddress)
{
trace1(error: cstring buffer (column %u) address has changed!n,
nfield);
assert(false);
}

因此,如果你在调用addnew和update之间把cstring的缓冲区移动了,对不起,你必须收到一个assert。(nickshen好像就是这里的问题吧)

这样问题就很清楚了,就是在你调用addnew和update之间不能移动缓冲区。但是据我和nickshen私下讨论的结果,他只是在其中调用了cstring的成员format,想要把一个浮点数转换成字符串,如果这么做就会有问题,但是直接赋值就不会,难道format会移动cstring的缓冲区?

于是我跟踪了一下cstring的format函数,发现在被format函数调用的formatv函数的流程是这样的:先根据格式串算出大约格式化之后的字符串要占多大的空间,然后就是看是否分配新的缓冲区,然后sprintf。这个学过数据结构的都可以理解。远离很简单,但是有这么一个问题:在formatv函数中,有这么一段
(strex.cpp, 659行起)
case f:
va_arg(arglist, double_arg);
nitemlen = 128; // width isnt truncated
// 312 == strlen(-1 (309 zeroes).)
// 309 zeroes == max precision of a double
nitemlen = max(nitemlen, 312 nprecision);
break;
这个是format对于格式串中的%f的处理,在一个switch块中。
switch之后,
// adjust nmaxlen for output nitemlen
nmaxlen = nitemlen;
...
getbuffer(nmaxlen);
看到了没?如果你使用了%f,mfc会很保守地认为你的一个%f会占用312的字符的位置(的确够保守的,至于为何时312,注释说得很清楚),于是用这个巨大的数调用getbuffer。

然后是cstring的operator=(lpctstr),这个就简单多了,不用保守的计算,源字符串有多少个字符就分配多少个字节,同样通过getbuffer。

在getbuffer的实现中,简单的说就是看看原来的长度够不够,不够重新分配一块够大的,然后memcpy,于是,缓冲区移动了。

慢着!
如果说长度不够就要移动缓冲区,而且两种操作都会移动缓冲区,那么为何只有format会出错,赋值不会?

谁说不会?你尝试赋给你的变量一个长度超过256的字符串试试,肯定出错,我试过了。
那么,这个256又是何处来的?你在用一个recordset第一步一定是open吧。跟踪一下发现,open中有一步是bindfieldtocolumns (dbcore.cpp 3854),经过一系列的分发,程序到了dbrfx.cpp 777:
case cfieldexchange::bindfieldtocolumn:
...
// constrain to user specified max length, subject to 256 byte min
if (cbcolumn > (uint)nmaxlength || cbcolumn < 256)
cbcolumn = nmaxlength;// set up binding addres
void* pvdata;
value.getbuffersetlength(cbcolumn 1);
pvdata = value.lockbuffer(); // will be overwritten if unicode
那么这个nmaxlength是多少呢?这个看看afxdb.h中对于rfx_text的声明,255!
明白了?

这么一说事情就很清楚了,所有的一切都是由于mfc内部造成的,由于我们大多数时候都不会像数据库中插入一个长度超过255的字符串(事实上access和sql server都只支持最多255个字符),因此不会有问题,但是偏偏mfc的工程师们在做format函数的时候保守了,于是,只要你用了%f格式符,就有问题无疑了。

知道了原因,解决方案就很简单了:
1、如果你可以改数据库,不妨把那个string(varchar)类型的字段改成double。
2、如果你没有这个权限,或者数据太多已经不能改了,那么只有退而求其次,先定义一个buffer,sprintf一下,然后赋值给cstring。

的确很麻烦。

同样的程序,再vc7下调试没有问题,有空再跟踪一下看看吧。mfc7.1的cstring已
经完全重写了...

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: