delphi中两个bug的分析与修复
在使用delphi 7进行三层数据库开发时,遇到了两个小问题,通过反复试验,终于找出了delphi 7中的两个小bug并进行了修复(好像delphi 6中也有相同的bug),撰写此文与大家一起分享成功的喜悦。我也是初学delphi,文中一定存在不少说的不对的地方,还请各位朋友多多指正。
bug1.传参时中文被截断的问题:
bug再现的方法:
后台用sql server 2000,里面有一个xshetong表用于试验,您可以根据您的实际情况进行调整。
先创建一个数据服务器:新建项目,创建一个远程数据模块,上面放置adoconnection、adodataset、datasetprovider各一,并做好相应设置,其中adodataset的comamndtext留空,并把它的option中的poallowcommandtext设置为true。编译运行。
再创建客户端程序:新建项目,在窗体上放置dcomconnection,连上前面上创建的数据服务器,再放置一个clientdataset,把它的连接设成这里的dcomconnection,并设置它的providername为上面的服务器上的datasetprovider的名字。最后放置datasource和dbgrid各一并作相应设置用于查看结果,再放置一button用于测试。
在button的onclick中写下类似于下面的代码(这里我用了xshetong的表和它的两个字段hth(char 15)、gcmc(varchar 100),您可以根据你的实际测试情况进行调整):
with clientdataset1 do
begin
close;
commandtext := 'insert into xshetong(hth, gcmc) values(:hth,:gcmc)';
params[0].asstring := '12345';
params[1].asstring := '会截断的中文字';
execute;
close;
commandtext := 'select * from xshetong';
open;
end;
运行程序,点击按钮,看到记录被插入了,可惜结果并不正确,“会截断的中文字”变成了“会截断”,但没有中文的“12345”倒是正确的插入了。
bug分析与修复:
为了对照起见,我试着直接用一个adoconnection和adocommand、adotable进行c/s构架测试,结果是正确的,中文字不会被切断。这说明了此bug只在三层构架上出现。
用sql server事件探查器探查提交到sql server上运行的语句,发现两层构架与三层构架的情况有以下不同:
两层构架:
exec sp_executesql n'insert into xshetong(hth, gcmc) values(@p1,@p2)', n'@p1 varchar(15),@p2 varchar(100)', '12345', '会截断的中文字'
三层构架:
exec sp_executesql n'insert into xshetong(hth, gcmc) values(@p1,@p2)', n'@p1 varchar(5),@p2 varchar(7)', '12345', '会截断
显然,两层构架时,参数的长度是按实际库结构传的,三层构架时,参数长度是按实际参数的字符串长度传的,而实际字符串长度又似乎是算错了,没有把一个中文当两个字符长度处理。
没有办法只好进行跟踪调试,为了调试delphi的vcl库,需要在工程选项的“compiler options”中选上“use debug dcus”。
先跟踪客户端程序,clientdataset1.execute后,先后经历了tcustomclientdataset.exectue、tcustomeclientdataset.packageparams、tcustomclientdataset.doexecute等一系列函数,一直到appserver.as_execute(providername, commandtext, params, ownerdata); 把请求提交到服务器均没有什么异常情况,看来问题出在服务器端。
对服务器进行跟踪,反复试验后,我把重点落在了tcustomadodataset.pssetcommandtext函数身上,经过反复细致的跟踪,目标越来越精确:tcustomadodataset.pssetparams、tparameter.assign、tparameter.setvalue、vardatasize。终于找到了bug的源头:vardatasize函数,下面是它的代码:
function vardatasize(const value: olevariant): integer;
begin
if varisnull(value) then
result := -1
else if varisarray(value) then
result := vararrayhighbound(value, 1) + 1
else if tvardata(value).vtype = varolestr then
begin
result := length(pwidestring(@tvardata(value).volestr)^); //出问题的行
if result = 0 then
result := -1;
end
else
result := sizeof(olevariant);
end;
就是在这个函数中计算实参的长度的,它把value中的值取出地址,并把它作为一个widestring的指针去求字符串长度,结果就导致了“会截断的中文字”这个字符串的长度变成了7,而不是14。
问题找到了,解决起来也就不困难了,只要简单的把
result := length(pwidestring(@tvardata(value).volestr)^); //出问题的行
改成
result := length(pansistring(@tvardata(value).volestr)^); //没问题了
就可以了。
但是这样就会导致求英文字符串的长度时长度被加倍了,所以也可以把这一行改成: