2012-07-17 161 views
2

我正在寫一個小型PE文件分析器,並且必須讀取PE文件的內容。我通過ReadFile功能這樣做,如下圖所示:Delphi中的異步讀取文件XE2

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經常滯後 - 和它同步發生。與此同時,用戶界面凍結,看起來可怕的錯誤,誰會試圖終止它。我曾考慮過線程,但我只想看看是否有任何方法可以在異步模式下使用ReadFile。如果沒有,我會跳到線程,即使我需要在代碼中修改很多。

預先感謝您。

+1

出於好奇,是否有一個原因,你在這裏重新發明輪子? 2007年之前的Delphi版本有一個轉儲PE信息的演示(這裏沒有演示名稱,但稍後會發布),[JEDI代碼庫](http://delphi-jedi.org)有一個整個單元('JclPEImage')包含爲您準備好所有這些東西的即用功能。 (甚至有一個PEViewer演示程序以與DependencyWalker非常相似的方式顯示所有內容)。即使是非常大的可執行文件(5 MB +),所有代碼都可以很好地執行,而且不存在任何UI問題或滯後。 – 2012-07-17 20:19:26

+1

嗨,@KenWhite,我對這個實現沒有任何偏好。事實上,我排除了JEDI,因爲一個單元與另一個單元相連,而且這個 - 恕我直言 - 使得整個項目難以維護,比起我只有一個單一的功能。我會看看舊的德爾福演示,我想我仍然有德爾福7的光盤。 – 2012-07-17 20:33:56

+0

當我到達我的家庭系統時,我會從D7中找到演示名稱,並在此處發表評論。 'JCL'比'JVCL'好一點,因爲它是嚴格的代碼。它確實增加了一些其他的東西,但是在這種情況下,你需要在你的uses子句中添加一個單元名稱來包含'JclPEImage',並從代碼中調用簡單的函數。這並不糟糕(儘管我同意JVCL與其他單位共同依賴它)。 – 2012-07-17 20:40:47

回答

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; 
+0

我認爲這也是最好的方法。雖然我解決了我的問題,但這似乎是最接近我最初問題的方法。非常感謝,尤其是描述性評論:) – 2012-07-18 20:23:30

1

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 
+0

不,我只是想等待文件以體面的,非阻塞的方式處理,就UI而言。用戶沒有太多的工作要比等待函數的結果。處理這些消息不會改變任何內容,因爲主要的瓶頸似乎是ReadFile函數,所以在發佈之前我已經嘗試過了。 – 2012-07-17 20:04:13

+2

請勿使用Application.ProcessMessages。使用'SplashScreen.sLabel1.Update',它可以完成同樣的事情(顯示新文本),而不需要'ProcessMessages'的副作用。應該像瘟疫一樣避免「ProcessMessages」。 – 2012-07-17 20:06:11

+0

按照Ken的建議,使用Application.ProcessMessages或SplashScreen.sLabel1.Update處理消息將使您的UI有機會更新其外觀。我建議在循環中這樣做,因爲我認爲這是佔用你所有的時間。但是,每次更改SplashScreen.sLabel1.Caption時都可以執行此操作。 – 2012-07-17 20:35:51