关于流操作的问题(200分)

  • 主题发起人 主题发起人 gaisy
  • 开始时间 开始时间
G

gaisy

Unregistered / Unconfirmed
GUEST, unregistred user!
我自己定义了一种文件格式,包括文件头和数据段。
在使用TMemoryStream读出的时候,碰到了写问题。
1、怎么从流中读出字符串(知道字符串长度)
2、我写的读数据的过程本身执行没发生错误(而且读出的数据也都正确),但是过程退出
后发生非法地址存取错误。这样的错误主要是什么原因引起的呢?
那位高手来讲讲课!
 
读数据的过程如下:
过程执行后返回到主界面时出错。
procedure TfrmMain.ToolButton4Click(Sender: TObject);
var
dStm, mStm: TMemoryStream;
P: PIFHeader;
iSize: Integer;
PStruct: String;
begin
mStm := TMemoryStream.Create;
dStm := TMemoryStream.Create;
try
mStm.LoadFromFile('G:/test.if');
New(P);
iSize := SizeOf(P^);
mStm.ReadBuffer(P^,iSize);
mStm.ReadBuffer(PStruct,P^.StructLen);
dStm.CopyFrom(mStm, P^.DataLen);
dStm.Position := 0;
dStm.SaveToFile('g:/test.123');
finally
Dispose(P);
mStm.Position := 0;
mStm.Free;
dStm.Free;
end;
end;
 
好象是用
iSize := Sizeof(TIFHeader);
TIFHeader 是 PIFHeader的结构
--
Sorry,没看清楚,应该是这样:
在mStm.ReadBuffer(PStruct,P^.StructLen);上面加一句
SetLenght(PStruct,StructLen);
由于你的字串变量没有申请内存所致
 
因为你没有为你的 PIFHeader 类型的变量 P 申请内存。
要看你的 PIFHeader 如何定义的了,一般而言:
P := AllocMem(SizeOf(IFHeader));
但要是你的 PIFHeader 中还包括 AnsiString 或对象,就不能确定分配的字节数了
建议你在写进去之前,先把整个结构的大小(用 SizeOf 获取)写进去
那么在读的时候,先读出该数值,然后决定分配多少空间,就好办了。

读一个字符串:
var
s: string;
begin
SetLength(s, 100);
aStream.Read(s[1], 100);
...
 
今天调试时发现如下难解的问题:(红色标出)我是Win2000 Pro+Delphi6+SP2
begin
mStm := TMemoryStream.Create;
dStm := TMemoryStream.Create;
try
mStm.LoadFromFile('G:/test.if');
New(P);
iSize := SizeOf(P^);
mStm.ReadBuffer(P^,iSize);
mStm.ReadBuffer(PStruct,P^.StructLen);
dStm.CopyFrom(mStm, P^.DataLen);[red]//该句执行时mStm.Position等于0,P^.DataLen已经不可访问[/red]
dStm.Position := 0;
dStm.SaveToFile('g:/test.123');
finally
Dispose(P);
mStm.Position := 0;
mStm.Free;
dStm.Free;
end;
end;
 
对不起,我对流不很熟。
我们以前类似的情况是用结构数据链来实现的。
 
看看 陈经韬 的文章

很经典
 
New(p) 用得对吗?
 
有个TStringStream,
你转到它里,然后它里面有读出字符串的方法
你可试试。
 
今天调试时发现如下难解的问题:(红色标出)我是Win2000 Pro+Delphi6+SP2
begin
mStm := TMemoryStream.Create;
dStm := TMemoryStream.Create;
try
mStm.LoadFromFile('G:/test.if');
New(P);
iSize := SizeOf(P^);

//////////////////////////////////////////////////////////////////////////////
mStm.Seek(0,0);//将游标移到流的第一个字节.
/////////////////////////////////////////////////////////////////////////////

mStm.ReadBuffer(P^,iSize);

/////////////////////////////////////////////////////////////////////////////////
// mStm.ReadBuffer(PStruct,P^.StructLen);
setlength(PStruct,P^.StructLen);
////一定要先给字符串分配内存.
mStm.ReadBuffer(PStruct[1],P^.StructLen);
//字符串其实是一个特珠类型的指针,所以你不能用你原来的方法来读字符串.
///////////////////////////////////////////////////////////////////////////////

dStm.CopyFrom(mStm, P^.DataLen);//该句执行时mStm.Position等于0,P^.DataLen已经不可访问

dStm.Position := 0;
dStm.SaveToFile('g:/test.123');
finally
Dispose(P);
mStm.Position := 0;
mStm.Free;
dStm.Free;
end;
end;
 
谢谢大家在百忙之中帮助我,可是上面的几个修改意见都还是不行,我试过了,把读取字符串
操作取消掉,程序就没有问题了。
从流中读取字符串我查了很久了,都没有讲解。
不知那位大侠有流操作的经验,帮帮我。
 
既然是自己定义的数据格式、自己读写数据文件,推荐用数据链的方式存取。
清晰明了,可以任意的查询、删除、添加链。

 
此问题其实不是因为流,而是因为ReadBuffer老式的内存管理机制与生存期自管理机制下
的string类型的差异引起的。

之所以执行执行期间没有错误就是因为string的内存分配是智能的,需要时就分配,
不需要时就不分配。同样在释放时string类型也是自动进行的。非法访问的错误就发生在
string类型的字符串在不使用时已自动释放,而系统再次试图释放它时。

这是为什么呢?首先,ReadBuffer读取的字符串会自动放在内存的某个位置(全局堆),
string类型会自动指向它,而不是复制该字符串的内容。一旦程序结束,string类型指向
的字符串就会自动释放,而系统并不知道这一点,ReadBuffer在结束时仍会通知系统释放
自己在堆中分配的内存,显然此时该内存已不存在。

解决的问题很简单。一,使用PChar或字符数组,或者将string转换为PChar;二,直接复制
字符串的内容,后者效率虽然较低,但肯定不会出错。鄙人的将要出版的新书
《参透Delphi/Kylix》对此有所讨论。
 
你先告诉我 PIFHeader 的定义,我就可以告诉你答案!

如果你的 PIFHeader = ^TIFHeader,那么请说出 TIFHeader 的具体定义!

这很重要
 
谢谢大家的关注,这个问题我已经解决了。可能有些东西我没有说清楚,让大家不能有的放矢
这是我的文件头定义
[green]type
PIFHeader = ^TIFHeader;
TIFHeader = record
HeaderSignatureBegin: array[0..3] of Byte;
Version: Byte;
IFType: Byte;
EncryptType: Byte;
LimitDate: TDateTime;
CRC: array[0..3] of Byte;
StructLen: Byte;
DataLen: Longint;
Blank: array[0..3] of Byte;
HeaderSignatureEnd: array[0..3] of Byte;
end;[/green]
这一句是我原来往流中加入字符串的语句:
[red]mStm.WirteBuffer(Struct, Length(Struct));//Struct是字符串变量[/red]
 
现在我把解决的方法总结一下:
在写入字符串到流中改成了如下的语句:
var
sStm: TStringStream;
Struct: String;
……
Struct := '文件描述信息';
sStm := TStringStream.Create(Struct);//生成字符串流
mStm.CopyFrom(sStm,i);//将字符串流数据放入内存流中

读出的语句是:
var
dStm, mStm: TMemoryStream;
sStm: TStringStream;
P: PIFHeader;
i, j, iSize: Integer;
Struct: String;
begin
mStm := TMemoryStream.Create;
dStm := TMemoryStream.Create;
sStm := TStringStream.Create(Struct);
try
mStm.LoadFromFile('G:/test.if');
New(P);
iSize := SizeOf(P^);
mStm.Seek(0,0);
mStm.ReadBuffer(P^,iSize);

i := P^.StructLen;
j := P^.DataLen;

sStm.CopyFrom(mStm, i);
Struct := sStm.DataString;
//ShowMessage(Struct);
dStm.CopyFrom(mStm, j);
……
这样做就不会出现内存存取错误了!
这次提问收获很大,对Delphi流、字符串的认识清晰多了。
在此通过QQ也认识了很多大富翁们,对你们的帮助深表感谢!
特别是萧月禾、程云几句点拨正中要害,深表敬佩!感激感激!向你们学习


 
十一少,你的讲解也很透彻。书到时候一定拜读!终于结题了
 
后退
顶部