【问题标题】:Read bytes from file at desired position with Inno Setup使用 Inno Setup 从文件中读取所需位置的字节
【发布时间】:2015-07-05 07:28:42
【问题描述】:

我想打开一个 50 MB 的二进制文件并只读取最后 4 个字节并将其转换为字符串以用于某种目的。

我现在发现的唯一方法是使用LoadStringFromFile,将文件完全加载到内存中,然后复制最后 4 个字节,但是这种方法非常慢,因为二进制文件很重。

在 Inno Setup 脚本中有更好的方法吗?

更新:


这是我根据 Martin Prikryl 的回答编辑的最终工作函数

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;

【问题讨论】:

    标签: inno-setup pascalscript


    【解决方案1】:

    你可以使用TFileStreamsupport class:

    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 function 似乎足够高效以加载 50 MB 文件。它只需要 40 毫秒。

    【讨论】:

    • 更好地使用 SeeksoEnd 来源。在确定流大小时,您将节省寻找开头和结尾的开销。
    • @TLama 当然,谢谢,我忽略了Seek。虽然我无法使用TFileStreamReadReadBuffer 都有错误的签名。 Buffer 不是“By ref”参数。
    • 应该没问题。您可能忘记分配缓冲区。这就是ReadBuffer 不能为您做的事情。更糟糕的是,在 Unicode Inno Setup 中,您必须将每个字符分解为字节才能从 Unicode 字符中获取 ANSI 字符。应该有相当多的字节参数数组。附言OP 知道 LoadStringFromFile 功能。
    • 干得好!我要添加的是try..finally 块,因为异常风险很高。但这只是一个细节。这是something generic。哦,对不起,我错过了 Inno Setup 中没有 soEnd 选项。不过有soFromEnd 一个。
    • 非常感谢 Martin 和 @TLama,两个答案都很好,我测试了两个代码,它工作正常。我昨天尝试过使用 CreateFile/ReadFile api,它为我赢得了大量时间来宣布职位。来自 TLama 的这段代码是一个完整版本的功能,对我来说很安全,您应该将其发布为答案,并且该网站应该具有将 2 个答案标记为已批准答案的功能。
    猜你喜欢
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2022-06-22
    • 1970-01-01
    • 2017-02-12
    • 1970-01-01
    • 2016-12-22
    相关资源
    最近更新 更多