delphifilestreamunicode-stringdelphi-10.1-berlin

Filestream Bytes not written properly


Why do I get the wrong output using the following code in RAD Studio 10.1?

var
  sPalette : string;
  mystream: TfileStream;
begin
  mystream := TfileStream.Create('C:\Data\test.bmp', fmCreate);
  sPalette := #1#2#3#4#5#6;
  mystream.WriteBuffer(Pointer(sPalette)^, Length(sPalette));
  mystream.Free;
end;

Got Output : 01 00 02 00 03 00

Expected output : 01 02 03 04 05 06


Solution

  • In Delphi 2009+, string is a 16-bit, UTF-16 encoded UnicodeString. You are not taking into account that SizeOf(Char) is 2 bytes, not 1 byte as you are expecting. Length(string) is expressed in number of characters, not in number of bytes. Your string is 6 characters in length, but is 12 bytes in size. You are writing only the 1st 6 bytes of the string to your file. And since your string contains ASCII characters below #128, every other byte will be $00.

    Use an 8-bit AnsiString instead, eg:

    var
      sPalette : AnsiString;
      mystream: TFileStream;
    begin
      mystream := TFileStream.Create('C:\Data\test.bmp', fmCreate);
      try
        sPalette := #1#2#3#4#5#6;
        mystream.WriteBuffer(Pointer(sPalette)^, Length(sPalette));
      finally
        mystream.Free;
      end;
    end;
    

    Or, use TEncoding to convert the Unicode string to an 8-bit byte encoding:

    var
      sPalette : string;
      bytes: TBytes;
      mystream: TFileStream;
    begin
      mystream := TFileStream.Create('C:\Data\test.bmp', fmCreate);
      try
        sPalette := #1#2#3#4#5#6;
        bytes := TEncoding.Default.GetBytes(sPalette);
        mystream.WriteBuffer(Pointer(bytes)^, Length(bytes));
      finally
        mystream.Free;
      end;
    end;
    

    Alternatively:

    var
      sPalette : string;
      mystream: TStreamWriter;
    begin
      mystream := TStreamWriter.Create('C:\Data\test.bmp', False, TEncoding.Default);
      try
        sPalette := #1#2#3#4#5#6;
        mystream.Write(sPalette);
      finally
        mystream.Free;
      end;
    end;
    

    Though, you really should not be using a string for binary data in the first place. Use a byte array instead, eg:

    var
      bytes: TBytes;
      mystream: TFileStream;
    begin
      mystream := TFileStream.Create('C:\Data\test.bmp', fmCreate);
      try
        SetLength(bytes, 6);
        bytes[0] := $1;
        ...
        bytes[5] := $6;
        mystream.WriteBuffer(Pointer(bytes)^, Length(bytes));
      finally
        mystream.Free;
      end;
    end;
    

    Or better, just use a TBitmap object, since you are writing to a .bmp file, eg:

    var
      bmp: TBitmap;
    begin
      bmp := TBitmap.Create;
      try
        ...
        bmp.SaveToFile('C:\Data\test.bmp');
      finally
        bmp.Free;
      end;
    end;