How to pass generic record as parameter to TFileStream.Read function?

Viewed 179

I have several record types to read from file, for example

  PDescriptorBlockHeader = ^TDescriptorBlockHeader;
  TDescriptorBlockHeader = packed record

    BlockType: UInt32;
    BlockAttributes: UInt32; // +4
    OffsetToFirstEvent: UInt16; // +8
    OsId: byte; // +10
    OsVersion: byte;
    DisplayableSize: UInt64;  // +12
    FormatLogicalAddress: UInt64; // +20
    SessionId: UInt64; // +28
    ControlBlockID: UInt32; // +36
    StringStorage: MTF_TAPE_ADDRESS; // +40
    OsSpecificData: MTF_TAPE_ADDRESS; // +44
    StringType: byte; // +48
    Reserved: byte; // +49
    HeaderChecksum: UInt16; //+50
  end;

and I want to use common function to read from file

type
  TReaderHelper = class
    class procedure ReadToStruct<T:record>(stream: TFileStream; offset: Int64);
  end;

implementation

class procedure TReaderHelper.ReadToStruct<T>(stream: TFileStream; offset: Int64);
var
  rd: integer;
begin
  stream.Position := offset;
  if stream.Position <> offset then
    raise Exception.Create('Seek error');
  rd := stream.Read(T, sizeof(T));
  if rd <> sizeof(T) then
    raise Exception.Create('Read ' + IntToStr(rd) + ' instead of ' + IntToStr(sizeof(T)));
end;

Compiler gives me error E2571 Type parameter 'T' doesn't have class or interface constraint at rd := stream.Read(T, sizeof(T));. Is it possible to pass that generic record as parameter to TFileStream.Read function?

2 Answers

You are trying to read directly to T which is the type. You need to provide a variable of that type into which to read.

type
  TReaderHelper = class
    class procedure ReadToStruct<T: record>(stream: TStream; offset: Int64; out Data: T);
  end;

class procedure TReaderHelper.ReadToStruct<T>(stream: TStream; offset: Int64; out Data: T);
begin
  stream.Position := offset;
  stream.ReadBuffer(Data, sizeof(T));
end;

Rather than supply a specific stream class like TFileStream, it is more flexible to supply a generic stream class. This allows you to use this method with different stream implementations.

The seek exception that you raised serves no purpose because it is possible to seek beyond the end of a file. Any errors arise in subsequent read or write actions.

The other exception is fine, but it is perhaps simpler to use ReadBuffer and let the stream class raise an exception in case the requested amount of data cannot be read.

T represents a Type, not a variable. You need to pass a variable to Read(). Add an output variable to your code and read into it, eg :

type
  TReaderHelper = class
    class procedure ReadToStruct<T:record>(stream: TFileStream; offset: Int64: out rec: T);
  end;

implementation

class procedure TReaderHelper.ReadToStruct<T>(stream: TFileStream; offset: Int64; out rec: T);
var
  rd: integer;
begin
  stream.Position := offset;
  if stream.Position <> offset then
    raise Exception.Create('Seek error');
  rd := stream.Read(rec, sizeof(T));
  if rd <> sizeof(T) then
    raise Exception.Create('Read ' + IntToStr(rd) + ' instead of ' + IntToStr(sizeof(T)));
end;

Alternatively :

type
  TReaderHelper = class
    class function ReadToStruct<T:record>(stream: TFileStream; offset: Int64): T;
  end;

implementation

class function TReaderHelper.ReadToStruct<T>(stream: TFileStream; offset: Int64): T;
var
  rd: integer;
begin
  stream.Position := offset;
  if stream.Position <> offset then
    raise Exception.Create('Seek error');
  rd := stream.Read(Result, sizeof(T));
  if rd <> sizeof(T) then
    raise Exception.Create('Read ' + IntToStr(rd) + ' instead of ' + IntToStr(sizeof(T)));
end;
Related