Last week, I blogged about the changes to the Delphi string type and how it might affect some of your memory management code.  Those were just warmups for today.

Today I’m writing about TStream and Delphi 2009.  Reading and writing streams was the biggest source of issues in porting my code to Delphi 2009.  I suspect that it will be the same for most of you.  Hopefully today’s post will help you prepare your code ahead of time.

Again, the root of the problem lies in the fact that most of the time when we write code to read or write streams, we assume that a Char is one byte, and a string’s length in Chars is the same as its size in bytes.  Since this isn’t true any more, our code that assumes that it is may be broken.

Note that the following is true for ALL stream classes, whether it’s TMemoryStream, TFileStream, or some other TStream implementation.

TStream.Write

TStream.Write expects the number of BYTES to write to the stream, not the number of Chars. The following code is very common, but incorrect:

Stream.Write(Pointer(myString)^, Length(myString));

This code will compile without complaint in Delphi 2009, but it won’t do what you probably wanted it to, which is write the whole string to the stream.  Your first instinct might be to replace Length(myString) with SizeOf(myString) but that won’t work either, since the SizeOf(someString) is always 4 (it’s just a pointer, remember?).  Generally, we should use the Length * SizeOf construct that I disliked a couple days ago:

Stream.Write(Pointer(myString)^, Length(myString) * SizeOf(Char));

I’d like to point out that this code is 100% backwards compatible with prior versions of Delphi.  It behaves correctly in Delphi 5, when SizeOf(Char) was 1, and it behaves correctly in Delphi 2009 when SizeOf(Char) is 2.  This is particularly important in Castalia, which uses the same code base to compile for the last six versions of Delphi.

I said that this solution works generally, but there are instances when you may want to do something else… Specifically, if you want to write in some encoding other than UTF-16.  We’ll leave that for later though (hint: the answer involves a new class called TEncoding).

TStream.Read

Of course, if you change your stream write code, you’ll probably need to change your stream read code. It all depends, of course, on how the stream is written. A typical pattern is to write the length of the string, then the string:

L := Length(myString);

Stream.Write(L, SizeOf(Integer));

Stream.Write(Pointer(myString)^, Length(myString) * SizeOf(Char));

Now, the old way to read this will look like this:

Stream.Read(L, SizeOf(Integer));

SetLength(myString, L);

Stream.Read(pointer(myString)^, L);

But this won’t work, as it will only read L bytes, which is going to be half the string when SizeOf(Char) is 2.  I’m sure by now you’re already a step ahead of me on the solution:

Stream.Read(L, SizeOf(Integer));

SetLength(myString, L);

Stream.Read(pointer(myString)^, L * SizeOf(Char));

The first two lines were fine – reading an Integer isn’t affected by the change in the size of a Char, and SetLength takes the number of Char elements in the string, just as it always has.  Reading the string from the stream, however, we need to make sure we’re telling the stream object how many BYTES to read, not how many CHARS.

Once again, the general rule holds: If the routine deals specifically with strings, it expects the number of CHARS to use.  If it works with general memory buffers, it expects the number of BYTES to use.  The trick here is just translating between CHARS and BYTES in appropriate places.

As I said at the beginning, I’m predicting that stream reading and writing is going to be the single most common cause of issues when porting code to Delphi 2009.  The simplest way to go about it is to do what I’ve noted in this post.  We’ll come back to a couple of other solutions in a few days, but tomorrow I’m going to talk about a couple of Windows API calls that you might be using that will need a bit of work.