分享
 
 
 

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

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

今天我在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 - ODBC's 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 isn't 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已

经完全重写了...

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有