Чтение байтов из файла в нужной позиции с помощью Inno Setup

Я хочу открыть двоичный файл размером 50 МБ, прочитать только последние 4 байта и преобразовать его в строку для какой-то цели.

Единственный способ найти это сейчас - использовать LoadStringFromFileЧтобы загрузить файл полностью в память, скопируйте последние 4 байта, однако этот метод очень медленный, потому что двоичный файл тяжелый.

Есть ли лучший способ сделать это в сценарии установки Inno?

Обновить:


Это последняя рабочая функция, которую я отредактировал из ответа Мартина Прикрыла.

function readlast4byte() : AnsiString;
var
  Stream: TFileStream;
  Buffer: string;
  Count: Integer;
  Index: Integer;
begin
  Count := 4;

  Stream := TFileStream.Create('C:\test.txt', fmOpenRead);

  try
    Stream.Seek(-Count, soFromEnd);

    SetLength(Buffer, 1);
    SetLength(Result, Count);
    for Index := 1 to Count do
    begin
      Stream.ReadBuffer(Buffer, 1);
      Result[Index] := Chr(Ord(Buffer[1]))  ;
    end;
  finally
    Stream.Free;
  end;
end;

Обновление 2:

Также это еще одна отличная рабочая функция, написанная TLama и должна также помечаться как ответ:

[Code]
#IFNDEF Unicode
  #DEFINE CharSize 1
#ELSE
  #DEFINE CharSize 2
#ENDIF

type
  TSeekOrigin = (
    soBeginning,
    soCurrent,
    soEnd
  );

#IFDEF UNICODE
function BufferToAnsi(const Buffer: string): AnsiString;
var
  W: Word;
  I: Integer;
begin
  SetLength(Result, Length(Buffer) * 2);
  for I := 1 to Length(Buffer) do
  begin
    W := Ord(Buffer[I]);
    Result[(I * 2)] := Chr(W shr 8); // high byte
    Result[(I * 2) - 1] := Chr(Byte(W)); // low byte
  end;
end;
#ENDIF

function ReadStringFromFile(const FileName: string; Origin: TSeekOrigin; Offset, Length: Integer;
  var S: AnsiString): Boolean;
var
  Buffer: string;
  Stream: TFileStream;
begin
  Result := True;
  try
    Stream := TFileStream.Create(FileName, fmOpenRead);
    try
      Stream.Seek(Offset, Ord(Origin));
      SetLength(Buffer, Length div {#CharSize});
      Stream.ReadBuffer(Buffer, Length);
      #IFNDEF UNICODE
        S := Buffer;
      #ELSE
        S := BufferToAnsi(Buffer);
      #ENDIF
    finally
      Stream.Free;
    end;
  except
    Result := False;
  end;

    end;

1 ответ

Решение

Ты можешь использовать TFileStream класс поддержки:

TStream = class(TObject)
  function Read(Buffer: String; Count: Longint): Longint;
  function Write(Buffer: String; Count: Longint): Longint;
  function Seek(Offset: Longint; Origin: Word): Longint;
  procedure ReadBuffer(Buffer: String; Count: Longint);
  procedure WriteBuffer(Buffer: String; Count: Longint);
  function CopyFrom(Source: TStream; Count: Longint): Longint;
  property Position: Longint; read write;
  property Size: Longint; read write;
end;

THandleStream = class(TStream)
  constructor Create(AHandle: Integer);
  property Handle: Integer; read;
end;

TFileStream = class(THandleStream)
  constructor Create(Filename: String; Mode: Word);
end;

Использовать .Seek(-4, soFromEnd) свойство искать указатель чтения в нужной позиции.

Сложность в том, что TStream работает с символами, а не байтами, поэтому вы должны конвертировать строки Unicode, которые вы читаете, обратно в байты.

При чтении всего четырех байтов чтение байта за байтом намного проще, что предотвращает любые многобайтовые преобразования:

procedure ReadFileEnd;
var
  Stream: TFileStream;
  Buffer: string;
  Count: Integer;
  Index: Integer;
begin
  Count := 4;

  Stream := TFileStream.Create('my_binary_file.dat', fmOpenRead);

  try
    Stream.Seek(-Count, soFromEnd);

    SetLength(Buffer, 1);
    for Index := 1 to Count do
    begin
      Stream.ReadBuffer(Buffer, 1);
      Log(Format('Byte %2.2x: %2.2x', [Index, Ord(Buffer[1])]));
    end;
  finally
    Stream.Free;
  end;
end;

Вот общая альтернатива @TLama, которая эффективно работает для произвольного большого чтения:
https://pastebin.com/nzGEdXVj


Кстати, для меня LoadStringFromFile Функция, по-видимому, достаточно эффективна для загрузки файла размером 50 МБ. Это займет всего 40 мс.

Другие вопросы по тегам