How do you read a file in Delphi into a byte array?

2

I am trying to read a file into a byte array in Delphi XE2.

This is my current code:

function FileToBytes(const AName: string; var Bytes: TBytes): Boolean;
var
  Ms: TMemoryStream;
begin
  Result := False;
  if not FileExists(AName) then
    Exit;
  Ms := TMemoryStream.Create;
  try
    Ms.LoadFromFile(AName);
    if Ms.Size > 0 then
    begin
      Ms.Position := 0;
      MS.ReadBuffer(Bytes[0], Ms.Size);
      Result := True;
    end;
  finally
    Ms.Free;
  end;
end;

procedure runFile();
var
  Bytes: TBytes;
  OpFile: String;
begin
  OpFile := 'C:\Users\Kenny\Documents\calc.exe';
  Bytes := nil;
  if FileToBytes(OpFile, Bytes) then
  begin
    //do someting with Bytes(array of Byte)

  end;
end;

I am getting an error at this line:

MS.ReadBuffer(Bytes[0], Ms.Size);

The error is:

access violation at 0x00404727: write of address 0x00000008

Any help in solving this would be greatly appreciated.

arrays
delphi
byte
delphi-xe2
asked on Stack Overflow Apr 29, 2021 by Kenneth Barker

1 Answer

3

You did not allocate the array, which explains the error. You can fix your code like this:

Ms.LoadFromFile(AName);
SetLength(Bytes, Ms.Size);
Ms.Position := 0;
MS.ReadBuffer(Pointer(Bytes)^, Ms.Size);
Result := True;

Note that I have avoided the need to check for a zero length file, and used Pointer(Bytes) so that the code will work when range checking is active.

I would also note that your code falls foul of what I refer to as the Delphi memory stream anti-pattern. You read the file into a memory stream, which is essentially a byte array. And then you copy from that byte array to another byte array. You write the entire file to two separate byte arrays. That's one more than necessary. Better to write it like this:

function FileToBytes(const AName: string; var Bytes: TBytes): Boolean;
var
  Stream: TFileStream;
begin
  if not FileExists(AName) then
  begin
    Result := False;
    Exit;
  end;
  Stream := TFileStream.Create(AName, fmOpenRead);
  try
    SetLength(Bytes, Stream.Size);
    Stream.ReadBuffer(Pointer(Bytes)^, Stream.Size);
  finally
    Stream.Free;
  end;
  Result := True;
end;

This way you read directly from the file into the target byte array.

I'm not terribly fond of having this as a function which returns a boolean to indicate success. There are plenty of ways in which the code could fail other than the file not existing. And these will lead to exceptions with your design. I'd prefer to see pure exception based error handling, or pure error code based error handling. But not the blended approach of your design.

As Andreas points out in comments, you can also use the RTL library function System.IOUtils.TFile.ReadAllBytes to perform this task. Although personally I tend to avoid the entire System.IOUtils unit because of numerous design issues. Whilst some of these have been sorted out in more recent releases, I believe that the XE2 version is liable to be somewhat riddled with issues.

answered on Stack Overflow Apr 29, 2021 by David Heffernan • edited Apr 29, 2021 by David Heffernan

User contributions licensed under CC BY-SA 3.0