【问题标题】:Asynchronous ReadFile in Delphi XE2Delphi XE2中的异步ReadFile
【发布时间】:2012-07-17 19:36:07
【问题描述】:

我正在编写一个小型 PE 文件分析器,我必须读取 PE 文件的内容。我是通过ReadFile函数来实现的,如下图:

function TMainForm.GetPEData(var filename: string) : boolean;
var
  hFile:   DWORD;
  IDH:     TImageDosHeader;
  INH:     TImageNtHeaders;
  ISH:     TImageSectionHeader;
  dwRead: DWORD;
  szBuff: array[0..7] of Char;
  i:      WORD;
  PE: TPEFile;
begin
  Result := False;
  PE := TPeFile.Create;
  if PE.LoadFromFile (filename) then  
    Form2.edEntryPoint.Text := IntToHex(PE.RvaToFileOffset(PE.AddressOfEntryPoint), 8);
  SplashScreen.sLabel1.Caption := 'PE File Loaded';
  hFile := CreateFile(PChar(filename), GENERIC_READ, 
                      FILE_SHARE_WRITE, nil, 
                      OPEN_EXISTING, 0, 0);
  if hFile <> INVALID_HANDLE_VALUE then
  begin
    SetFilePointer(hFile, 0, nil, FILE_BEGIN);
    SplashScreen.sLabel1.Caption := 'Reading DOS File Headers...';
    ReadFile(hFile, IDH, 64, dwRead, nil);
    if IDH.e_magic = IMAGE_DOS_SIGNATURE then
    begin
      SetFilePointer(hFile, IDH._lfanew, nil, FILE_BEGIN);
      SplashScreen.sLabel1.Caption := 'Reading NT File Headers...';
      //Here is where the UI freezes while the file is read...
      ReadFile(hFile, INH, 248, dwRead, nil);
      if INH.Signature = IMAGE_NT_SIGNATURE then
      begin
        Form2.edImageBase.Text := IntToHex(INH.OptionalHeader.ImageBase, 8);
        Form2.edSizeOfImage.Text := IntToHex(INH.OptionalHeader.SizeOfImage, 8);
        Form2.edLinkerVersion.Text := IntToStr(INH.OptionalHeader.MajorLinkerVersion) + '.' + 
              IntToStr(INH.OptionalHeader.MinorLinkerVersion);
        Form2.edFileAlignment.Text := IntToHex(INH.OptionalHeader.FileAlignment, 8);
        Form2.edSectionAlignment.Text := IntToHex(INH.OptionalHeader.SectionAlignment, 8);
        Form2.edSubSystem.Text := IntToHex(INH.OptionalHeader.Subsystem, 4);
        Form2.edEPFilestamp.Text := IntToStr(INH.FileHeader.TimeDateStamp);
        Form2.edFileType.Text := GetPEFileType(PE.ImageNtHeaders.Signature);

        for i := 0 to INH.FileHeader.NumberOfSections - 1 do
        begin
          SetFilePointer(hFile, IDH._lfanew + 248 + i * 40, nil, FILE_BEGIN);
          ReadFile(hFile, ISH, 40, dwRead, nil);
          CopyMemory(@szBuff[0], @ISH.Name[0], 8);

          with Form2.sListView1.Items.Add do
          begin
            Caption := ShortString(szBuff);
            SubItems.Add(IntToHex(ISH.VirtualAddress, 8));
            SubItems.Add(IntToHex(ISH.Misc.VirtualSize, 8));
            SubItems.Add(IntToHex(ISH.PointerToRawData, 8));
            SubItems.Add(IntToHex(ISH.SizeOfRawData, 8));
            SubItems.Add(IntToHex(ISH.Characteristics, 8));
          end;
        end;
      end;
    end;
    CloseHandle(hFile);
    Result := True;
  end;
end;

不好的是,根据文件的大小,我注意到ReadFile 经常会滞后——而且它是同步发生的。与此同时,UI 冻结并且对用户来说看起来非常错误,他们会很想终止它。我考虑过线程,但我只是想看看有没有什么方法可以在异步模式下使用ReadFile。如果没有,我会跳到线程,即使我的代码有很多要修改的地方。

提前谢谢你。

【问题讨论】:

  • 只是出于好奇,您在这里重新发明轮子有什么原因吗? 2007 年之前的 Delphi 版本有一个转储 PE 信息的演示(此处没有演示名称,但稍后会发布),JEDI Code Library 有一个完整的单元 (JclPEImage) 包含即用型为您完成所有这些工作的功能。 (甚至还有一个 PEViewer 演示,它以与 DependencyWalker 非常相似的方式显示所有内容。)所有代码在非常大的可执行文件(5 MB+)上也能正常运行,没有任何 UI 问题或滞后。
  • 嘿,@KenWhite,我对实现没有任何偏好。事实上,我排除了 JEDI,因为一个单元链接到另一个单元等等,这 - 恕我直言 - 使得整个项目比我有一个单一的功能更难维护。我会看一下旧的 Delphi 演示,我想我仍然有 Delphi 7 的光盘。
  • 当我进入我的家庭系统时,我会从 D7 中找到演示名称并将其发布在此处的评论中。 JCLJVCL 更好地使用一个单元一个单元,因为它是严格的代码。它确实添加了一些其他内容,但在这种情况下,您将在您的使用子句中添加一个单元名称以包含JclPEImage,并从您的代码中调用简单的函数。这还不错(尽管我同意 JVCL 令人讨厌地相互依赖于它包含的其他单元)。
  • 即使是 JCL 与其他 JCL 单元的耦合也相当糟糕。
  • D7 中的演示名称是ResXPlor,在$(DELPHI)\Demos 文件夹中。有一个名为 ExeImage 的单元,我相信它包含您正在寻找的代码,尽管我没有专门检查它。 @WarrenP,我并没有说这还不错;我说它没有 JVCL那么糟糕。 :-)

标签: multithreading delphi asynchronous readfile


【解决方案1】:

在这种情况下,我总是将整个文件读取到内存中,并且我使用 TFileStream 类来简化操作。

将整个文件放在内存中比较简单,PE 文件通常很小。

  type
    TSections = array [0..0] of TImageSectionHeader;
    PSections = ^TSections;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  FS : TFileStream;
  fisier : PImageDosHeader;
  INH : PImageNtHeaders;
  ISH : PSections;
  i   : Word;
begin
  FS := TFileStream.Create('fisierul_tau.exe',fmOpenRead);
  GetMem(fisier,FS.size); //Aloci memorie pentru fisier
  FS.Read(fisier^,FS.Size); // Il citesti;
  FS.Free;
  INH := PImageNtHeaders(DWORD(fisier) + DWORD(fisier^._lfanew));
  ISH := PSections(DWORD(INH) + SizeOf(TImageNtHeaders));
  for i := 0 to INH^.FileHeader.NumberOfSections - 1 do
  begin
      ShowMessage(PAnsiChar(@ISH[i].Name[0]));
  end;
end;

【讨论】:

  • 我认为这也是最好的方法。虽然我解决了我的问题,但这似乎是最接近我最初问题的方法。非常感谢,尤其是描述性的 cmets :)
【解决方案2】:

ReadFile 函数从文件中读取数据,并从 文件指针指示的位置。你可以使用这个功能 同步和异步操作。

可以异步使用 ReadFile,但根据您的 UI,这可能不是最佳解决方案。您是否希望您的用户在等待 PE 文件加载时执行任何操作?

如果您希望您的用户等待但确信您的程序没有冻结,您可以添加一个进度条或只更新您的 SplashScreen。

for i := 0 to INH.FileHeader.NumberOfSections - 1 do
begin
   SplashScreen.sLabel1.Caption := 'Reading section ' + IntToStr(i) + ' of ' + IntToStr(INH.FileHeader.NumberOfSections);
   SplashScreen.sLabel1.Update; // see Ken Whites comment
   // Application.ProcessMessages;
   ...
end

【讨论】:

  • 不,我只想等待文件以一种体面的、非阻塞的方式处理 UI。用户除了等待函数的结果之外没有太多事情要做。处理消息不会改变任何东西,我在发布之前已经尝试过,因为主要瓶颈似乎是 ReadFile 函数。
  • 不要使用Application.ProcessMessages。使用SplashScreen.sLabel1.Update,它完成了同样的事情(显示新文本)而没有ProcessMessages 的副作用。 ProcessMessages 应该像瘟疫一样避免。
  • 按照 Ken 的建议使用 Application.ProcessMessages 或 SplashScreen.sLabel1.Update 处理消息,将使您的 UI 有机会更新其外观。我建议在循环中这样做,因为我认为这会占用你所有的时间。但是,您可以在每次更改 SplashScreen.sLabel1.Caption 时执行此操作。
  • 如果您只是调用 Application.ProcessMessages 而不更改 UI,则不会发生任何事情。请记住,如果您在 onclick 处理程序中执行此操作,则按钮将保持单击状态,直到处理程序返回。
  • 令我沮丧的是,除了“正在读取 NT 文件头...”之外,表单仍然没有更新。它冻结了大约 20 秒左右,然后又像什么都没发生过一样回来。问题出现在这里: ReadFile(hFile, INH, 248, dwRead, nil);这就是为什么我试图弄清楚是否有办法异步读取文件。
猜你喜欢
  • 2015-08-19
  • 2021-04-16
  • 1970-01-01
  • 1970-01-01
  • 2012-09-21
  • 2011-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多