Friday, January 30, 2009

Working with IStream in Delphi

At some point you might have to work with IStream. They are never fun to work with. Especially since you can't insert into a stream without overwriting. But that's streams.

I like to use the VCL wrappers, TOleStream and TStreamAdapter, and then use TMemoryStream. Use TOleStream to wrapping up an IStream making it look like a TStream and then use TIMemoryStream to make the TMemoryStream look like an IStream again.

Let's say for some reason we get a UTF-8 stream that needs to be saved as a file but it has no BOM. Here's a small example of how to add a BOM to the stream using Delphi:


uses ActiveX, AxCtrls, Classes;

function AddBomToStream(const Stream: IStream): IStream;
var
Bom: TBytes;
OleStream: TOleStream;
MemoryStream: TMemoryStream;
Temp: Largeint;
begin
if Stream <> nil then
begin
MemoryStream := TMemoryStream.Create;
Bom := TEncoding.UTF8.GetPreamble;
MemoryStream.Write(Bom[0], Length(Bom));
Stream.Seek(0, STREAM_SEEK_SET, Temp);
OleStream := TOleStream.Create(Stream);

try
MemoryStream.CopyFrom(OleStream, OleStream.Size);
Result := TStreamAdapter.Create(MemoryStream, soOwned) as IStream;
finally
OleStream.Free;
end;
end;
end;

7 comments:

Anonymous said...

Chris, if you are messing with Delphi's IStream implementation anyway, pls take a minute to fix this one:

Report No: 45528
Status: Open
Potential issue in TStreamAdapter.Stat implementation
http://qc.codegear.com/wc/qcmain.aspx?d=45528

Anonymous said...

Streams traditionally represent file access and files have never supported inserting, so I am not sure I really see the problem (maybe I am too old school)

I assume this is mostly a problem because you are trying to use streams in the way that most people would normally use strings, which have traditionally suported inserting (dotNet being a major exception with its imutable strings)

Remember, IStream is just an interface, it does not specify implementation. If you felt that you just used memory streams, and absolutely needed to insert into a stream: create a descendant called IMyStream, add the ability to insert and then create a object that handles multiple buffers to support inserting when dealing with your streams.

Easy fix and you don't even have to break anything along the way.

I have a binary buffer object that I created as part of my ongoing D2009 migration, and it only took a day to create and test. Adding in inserting capabilities and bolting on an IStream interface would likely be the process of another day, so it is definitely possible.

Heck, in my buffer class, I can mix memory blocks and streams of any type AND binary marker flags -you probably don't need that level of complexity, so the whole operation might be no more than a day for you if you really need it.

Midiar said...

Did you mean to call the parameter Stream, not Source?

Chris Bensen said...

Midiar,

Good catch. I just updated it. Thanks!

Chris Bensen said...

First Anonymous,

What prompted this post wasn't actually getting dirty with the VCLs implementation of the IStream, but rather I was using it so I figured it would make a good post. But I'll pass your request to the appropriate parties.

Chris Bensen said...

Second Anonymous,

I know streams, well a stream and not indexed for inserting, but I personally really like working with things like dynamic arrays and lists. I just find it easier to wrap my head around. Just a personal preference and one reason why I try to stay away from streams.

Remy Lebeau said...

FYI, TOleStream does not fully support 64-bit streams, not even in XE6. TOleStream overrides the 32-bit version of TStream.Seek() instead of the 64-bit version, and it does not override the TStream.GetSize() method, so the Size property getter relies on Seek() to determine the stream size. Also, TOleStream does not override the TStream.SetSize() method, so it is not possible to resize a TOleStream even though IStream supports that.

Post a Comment