Monday, March 23, 2009

ANSI to Unicode: Stream.Write(S[1], Length(S))

Last weekend I have migrated Delphi 2007 code to Delphi 2009. The code uses Stream.Write to write strings to a stream. There are multiple ways of migrating such code.


Stream.Write(S[1], Length(S))



  1. Do a project-wide search for “.Write(”

  2. Read through the search results and identify the Write() calls with Strings.

    Things like Variable[1], PChar(, Pointer(, PAnsiChar( help you to identify the source lines.

  3. Find out the code’s intention to decide if you need an AnsiString, or if a UTF8 string would do it, or if you can use a UnicodeString because it is an internally used stream.

  4. Rewrite the code if necessary




The following code writes a string to an internal stream to save the control’s state. This allows us to write the UnicodeString without any conversion. The missing “* SizeOf(Char)” is the only required source code change.


ANSI:



procedure TJvCustomDBTreeView.DestroyWnd;
var
Node: TTreeNode;
temp: string;
strLength: Integer;
HasChildren: Byte;
begin
if Items.Count > 0 then
begin
// save master values into stream
FMastersStream := TMemoryStream.Create;
Node := Items.GetFirstNode;
while Assigned(Node) do
begin
// save MasterValue as string
temp := VarToStr(TJvDBTreeNode(Node).MasterValue);
strLength := length(temp);
FMastersStream.Write(strLength, SizeOf(strLength));
{*} FMastersStream.Write(temp[1], strLength);
HasChildren := Byte(Node.HasChildren);
FMastersStream.Write(HasChildren, SizeOf(HasChildren));
Node := Node.GetNext;
end;
end;
inherited DestroyWnd;
end;


UNICODE:



procedure TJvCustomDBTreeView.DestroyWnd;
var
Node: TTreeNode;
temp: string;
strLength: Integer;
HasChildren: Byte;
begin
if Items.Count > 0 then
begin
// save master values into stream
FMastersStream := TMemoryStream.Create;
Node := Items.GetFirstNode;
while Assigned(Node) do
begin
// save MasterValue as string
temp := VarToStr(TJvDBTreeNode(Node).MasterValue);
strLength := Length(temp);
FMastersStream.Write(strLength, SizeOf(strLength));
{*} FMastersStream.Write(PChar(temp)[0], strLength * SizeOf(Char)); // internally used stream + bugfix
HasChildren := Byte(Node.HasChildren);
FMastersStream.Write(HasChildren, SizeOf(HasChildren));
Node := Node.GetNext;
end;
end;
inherited DestroyWnd;
end;




The following code writes control names to a stream. For control names the UTF8 encoding can be used to accept Unicode control names. Declaring “ControlName” as UTF8String and calling UTF8Encode is all you have to do. If you need an AnsiString then you can declare “ControlName” as AnsiString and instead of calling UTF8Encode you must do a hard typecast to AnsiString.


ANSI:



procedure TJvDockTabPageControl.SaveToStream(Stream: TStream);
var
//...
{*}ControlName: string;
CurrentControl: TControl;
begin
//...
for I := 0 to ACount - 1 do
begin
if Pages[I].ControlCount > 0 then
begin
CurrentControl := Pages[I].Controls[0];
{*} ControlName := CurrentControl.Name;
NameLen := Length(ControlName);
Stream.Write(NameLen, SizeOf(NameLen));
if NameLen > 0 then
Stream.Write(Pointer(ControlName)^, NameLen);
//...
end;
end;
//...
end;


UNICODE:



procedure TJvDockTabPageControl.SaveToStream(Stream: TStream);
var
//...
{*}ControlName: UTF8String;
CurrentControl: TControl;
begin
//...
for I := 0 to ACount - 1 do
begin
if Pages[I].ControlCount > 0 then
begin
CurrentControl := Pages[I].Controls[0];
{*} ControlName := UTF8Encode(CurrentControl.Name);
NameLen := Length(ControlName);
Stream.Write(NameLen, SizeOf(NameLen));
if NameLen > 0 then
Stream.Write(ControlName[1], NameLen);
//...
end;
end;
//...
end;




Another possibility is to support ANSI and Unicode with one function by using the TEncoding class. If No Encoding is specified the default (ANSI with AciveCodePage) is used. This change requires a complete rewrite but gives you the more flexibility.


ANSI:



procedure TJvStringGrid.SaveToStream(Stream: TStream);
var
//...
{*}St: array [0..BufSize - 1] of Char;
{*}Stt: string;
begin
//...
for I := 0 to RowCount - 1 do
begin
for J := 0 to ColCount - 1 do
begin
//...
{*} Stt := Cells[J, I];
{*} for K := 1 to Length(Stt) do
{*} St[K - 1] := Stt[K];
{*} Stream.Write(St, Length(Cells[J, I]));
//...
end;
//...
end;
//...
end;


UNICODE:



procedure TJvStringGrid.SaveToStream(Stream: TStream; Encoding: TEncoding = nil);
var
//...
{*}Bytes: TBytes;
Len: Integer;
begin
if Encoding = nil then
Encoding := TEncoding.Default;
//...
for I := 0 to RowCount - 1 do
begin
for J := 0 to ColCount - 1 do
begin
//...
{*} Bytes := Encoding.GetBytes(Cells[J, I]);
{*} Len := Length(Bytes);
{*} if Len > 0 then
{*} Stream.Write(Bytes[0], Len);
//...
end;
//...
end;
//...
end;

No comments: