Delphi Pascal - 使用SetFilePointerEx和GetFileSizeEx,在读取文件时获取物理媒体的确切大小

时间:2010-12-31 01:04:45

标签: delphi api delphi-2009 pascal

我不知道如何使用RTL中没有的任何API。我一直在使用SetFilePointer和GetFileSize将物理磁盘读入缓冲区并将其转储到文件中,这样的循环就可以完成2GB以下闪存卡的工作:

SetFilePointer(PD,0,nil,FILE_BEGIN);
SetLength(Buffer,512);
ReadFile(PD,Buffer[0],512,BytesReturned,nil);

然而,GetFileSize的限制为2GB,SetFilePointer也是如此。我绝对不知道如何对外部API进行delcare,我已经查看了RTL并搜索了许多示例并且没有找到正确的答案。

我试过这个

function GetFileSizeEx(hFile: THandle; lpFileSizeHigh: Pointer): DWORD; 
    external 'kernel32';

并按照建议

function GetFileSizeEx(hFile: THandle; var FileSize: Int64): DWORD;
    stdcall; external 'kernel32';

但该函数返回0,即使我使用的是有效的磁盘句柄,我已经确认并使用较旧的API转储数据。

我正在使用SetFilePointer跳转每512字节和ReadFile写入缓冲区,反过来我可以使用它来设置我何时使用WriteFile将初始程序加载程序代码或其他内容写入磁盘。我需要能够将文件指针设置为超过2gb。

有人可以帮我制作外部声明,并且可以调用GetFileSizeEx和SetFilePointerEx,这样我就可以修改旧代码,使用4到32GB的闪存卡。

5 个答案:

答案 0 :(得分:8)

我建议你看看这个Primoz Gabrijelcic blog article和他的GpHugeFile单元,它应该给你足够的指针来获取文件大小。

编辑1 现在根据问题的编辑,这看起来相当愚蠢。

编辑2 既然已经接受了这个答案,经过对jachguate答案的长篇评论,我觉得有必要总结已经学到的东西。

  • GetFileSizeSetFilePointer没有2GB 限制,它们可以用于文件 基本上是任意大小。

  • GetFileSizeExSetFilePointerEx很多 更容易使用因为它们有效 直接使用64位数量和 有更简单的错误条件 信号。

  • OP实际上并不需要 计算他的磁盘大小。以来 OP正在阅读整个 磁盘的内容大小不是 需要。所需要的只是 按顺序读取内容,直到 什么都没有留下。

  • 事实上 GetFileSize / GetFileSizeEx 不支持设备句柄 (例如物理磁盘或卷) OP要求。更重要的是, SetFilePointer / SetFilePointerEx 不能寻求到这种设备的尽头 处理

  • 为了获得a的大小 磁盘,卷或分区,应该 通过了 IOCTL_DISK_GET_LENGTH_INFO 控制代码 DeviceIoControl

最后,如果您需要使用GetFileSizeExSetFilePointerEx,那么可以将它们声明如下:

function GetFileSizeEx(hFile: THandle; var lpFileSize: Int64): BOOL;
    stdcall; external 'kernel32.dll';
function SetFilePointerEx(hFile: THandle; liDistanceToMove: Int64;
    lpNewFilePointer: PInt64; dwMoveMethod: DWORD): BOOL;
    stdcall; external 'kernel32.dll';

获得这些API的一种简单方法是通过优秀的JEDI API Library

答案 1 :(得分:3)

GetFileSizeEx例程需要一个指向LARGE_INTEGER数据类型的指针,文档说:

如果您的编译器内置支持64位整数,请使用QuadPart成员存储64位整数

幸运的是,Delphi内置了对64位整数的支持,所以请使用它:

var
  DriveSize: LongWord;
begin
  GetFilePointerSizeEx(PD, @DriveSize);
end;
另一方面,

SetFilePointerEx期望liDistanceToMove,lpNewFilePointer的参数都是64位整数。我的理解是它需要有符号的整数,但是如果我对这些文档有所了解,那么你有未经过64位整数的UInt64数据类型。

答案 2 :(得分:2)

替代编码

自杀,首先你的方法是错误的,并且由于你的错误方法,你遇到了一些毛病问题,Windows处理磁盘驱动器作为文件打开的方式。在伪代码中,您的方法似乎是:

Size = GetFileSize;
for i=0 to (Size / 512) do
begin
  Seek(i * 512);
  ReadBlock;
  WriteBlockToFile;
end;

这在功能上是正确的,但有一种更简单的方法来做同样的事情而不实际获得SizeOfDisk并且没有寻求。从文件(或流)中读取内容时,“指针”会随着您刚读取的数据量自动移动,因此您可以跳过“搜索”。用于从文件读取数据的所有函数都返回实际读取的数据量:您可以使用它来知道何时到达文件末尾而不知道要开始的文件大小!

以下是使用Delphi的TFileStream了解如何将物理磁盘读入文件,而不了解磁盘设备的信息:

var DiskStream, DestinationStream:TFileStream;
    Buff:array[0..512-1] of Byte;
    BuffRead:Integer;
begin
  // Open the disk for reading
  DiskStream := TFileStream.Create('\\.\PhysicalDrive0', fmOpenRead);
  try
    // Create the file
    DestinationStream := TFileStream.Create('D:\Something.IMG', fmCreate);
    try

      // Read & write in a loop; This is where all the work's done:
      BuffRead := DiskStream.Read(Buff, SizeOf(Buff));
      while BuffRead > 0 do
      begin
        DestinationStream.Write(Buff, BuffRead);
        BuffRead := DiskStream.Read(Buff, SizeOf(Buff));
      end;

    finally DestinationStream.Free;
    end;
  finally DiskStream.Free;
  end;
end;

显然,您可以通过相反的方式执行类似的操作,从文件读取并写入磁盘。在编写代码之前,我实际上是按照你的方式尝试(获取文件大小等),并立即遇到问题!显然,Windows不知道“文件”的确切大小,除非您从中读取它。

磁盘作为文件打开的问题

对于我的所有测试,我使用这个简单的代码作为基础:

var F: TFileStream;
begin
  F := TFileStream.Create('\\.\PhysicalDrive0', fmOpenRead);
  try
    // Test code goes here...
  finally F.Free;
  end;
end;

第一个(显而易见的)尝试是:

ShowMessage(IntToStr(DiskStream.Size));

失败了。在依赖于调用FileSeek的TFileStream实现中,FileSeek无法处理大于2Gb的文件。所以我尝试使用此代码尝试GetFileSize:

var RetSize, UpperWord:DWORD;
RetSize := GetFileSize(F.Handle, @UpperWord);
ShowMessage(IntToStr(UpperWord) + ' / ' + IntToStr(RetSize));

那也失败了,即使它应该完全能够将文件大小作为64位数字返回!接下来我尝试使用SetFilePointer API,因为它也应该处理64位数字。我以为我只是寻找文件的末尾并查看结果,使用以下代码:

var RetPos, UpperWord:DWORD;
UpperWord := 0;
RetPos := SetFilePos(F.Handle, 0, @UpperWord, FILE_END);
ShowMessage(IntToStr(UpperWord) + ' / ' + IntToStr(RetPos));

此代码也失败了!现在我在想,为什么第一个代码有效?显然,逐块阅读工作正常,Windows知道何时停止阅读!所以我想也许64位文件处理例程的实现存在问题,让我们尝试以小的增量结束文件;当我们发现错误时,我们知道我们已经到达终点,我们将停止:

var PrevUpWord, PrevPos: DWORD;
    UpWord, Pos: DWORD;
UpWord := 0;
Pos := SetFilePointer(F.Handle, 1024, @UpWord, FILE_CURRENT); // Advance the pointer 512 bytes from it's current position
while (UpWord <> PrevUpWord) or (Pos <> PrevPos) do
begin
  PrevUpWord := UpWord;
  PrevPos := Pos;
  UpWord := 0;
  Pos := SetFilePointer(F.Handle, 1024, @UpWord, FILE_CURRENT);
end;

尝试这段代码时,我有一个惊喜:它不会停留在文件的位置,它会一直持续下去。它永远不会失败。说实话,我不确定它应该会失败......它可能不会失败。无论如何,当我们超过文件末尾时,在该循环中执行READ会失败,因此我们可以使用非常糟糕的混合方法来处理这种情况。

解决问题的现成例程

这是现成的例程,即使GetFileSize失败,SetFilePointer FILE_END失败,也会将物理磁盘的大小设置为文件。将它传递给一个打开的TFileStream,它将返回Int64的大小:

function Hacky_GetStreamSize(F: TFileStream): Int64;
var Step:DWORD;

    StartPos: Int64;
    StartPos_DWORD: packed array [0..1] of DWORD absolute StartPos;

    KnownGoodPosition: Int64;
    KGP_DWORD: packed array [0..1] of DWORD absolute KnownGoodPosition;

    Dummy:DWORD;

    Block:array[0..512-1] of Byte;
begin
  // Get starting pointer position
  StartPos := 0;
  StartPos_DWORD[0] := SetFilePointer(F.Handle, 0, @StartPos_DWORD[1], FILE_CURRENT);
  try
    // Move file pointer to the first byte
    SetFilePointer(F.Handle, 0, nil, FILE_BEGIN);
    // Init
    KnownGoodPosition := 0;
    Step := 1024 * 1024 * 1024; // Initial step will be 1Gb
    while Step > 512 do
    begin
      // Try to move
      Dummy := 0;
      SetFilePointer(F.Handle, Step, @Dummy, FILE_CURRENT);
      // Test: Try to read!
      if F.Read(Block, 512) = 512 then
        begin
          // Ok! Save the last known good position
          KGP_DWORD[1] := 0;
          KGP_DWORD[0] := SetFilePointer(F.Handle, 0, @KGP_DWORD[1], FILE_CURRENT);
        end
      else
        begin
          // Read failed! Move back to the last known good position and make Step smaller
          SetFilePointer(F.Handle, KGP_DWORD[0], @KGP_DWORD[1], FILE_BEGIN);
          Step := Step div 4; // it's optimal to devide by 4
        end;
    end;
    // From here on we'll use 512 byte steps until we can't read any more
    SetFilePointer(F.Handle, KGP_DWORD[0], @KGP_DWORD[1], FILE_BEGIN);
    while F.Read(Block, 512) = 512 do
      KnownGoodPosition := KnownGoodPosition + 512;
    // Done!
    Result := KnownGoodPosition;
  finally
    // Move file pointer back to starting position
    SetFilePointer(F.Handle, StartPos_DWORD[0], @StartPos_DWORD[1], FILE_BEGIN);
  end;
end;

要完成,这里有两个例程,可用于设置和获取文件指针使用Int64进行定位:

function Hacky_SetStreamPos(F: TFileStream; Pos: Int64):Int64;
var aPos:Int64;
    DWA:packed array[0..1] of DWORD absolute aPos;
const INVALID_SET_FILE_POINTER = $FFFFFFFF;
begin
  aPos := Pos;
  DWA[0] := SetFilePointer(F.Handle, DWA[0], @DWA[1], FILE_BEGIN);
  if (DWA[0] = INVALID_SET_FILE_POINTER) and (GetLastError <> NO_ERROR) then
    RaiseLastOSError;
  Result := aPos;
end;

function Hacky_GetStreamPos(F: TFileStream): Int64;
var Pos:Int64;
    DWA:packed array[0..1] of DWORD absolute Pos;
begin
  Pos := 0;
  DWA[0] := SetFilePointer(F.Handle, 0, @DWA[1], FILE_CURRENT);
  Result := Pos;
end;

最后的注释

我提供的3个例程将TFileStream作为参数,因为这是我用于文件读写的内容。他们显然只使用TFileStream.Handle,因此参数可以简单地用文件句柄替换:功能将保持不变。

答案 3 :(得分:0)

我知道这个帖子很旧,但是......

一个小建议 - 如果您使用Windows DeviceIoControl(...)功能,您可以获取驱动器几何和/或分区信息,并使用它们来获取打开的总大小/长度驱动器或分区。不再需要逐步寻找到设备的末端。

这些IOCTL也可用于为您提供正确的扇区大小,您可以使用它而不是在任何地方默认为512.

答案 4 :(得分:0)

非常有用。但是对于大于4 GB的磁盘我遇到了问题。 我解决了替换问题:

   // Ok! Save the last known good position
      KGP_DWORD[1] := 0;
      KGP_DWORD[0] := SetFilePointer(F.Handle, 0, @KGP_DWORD[1], FILE_CURRENT);

以下内容:

  // Ok! Save the last known good position
  KnownGoodPosition := KnownGoodPosition + Step;

再次感谢...

还要感谢James R. Twine。我遵循了使用IOCTL_DISK_GET_DRIVE_GEOMETRY_EX的建议并获得了没有问题的磁盘维度,也没有奇怪的解决方法。 这是代码:

TDISK_GEOMETRY = record
  Cylinders : Int64; //LargeInteger
  MediaType : DWORD; //MEDIA_TYPE
  TracksPerCylinder: DWORD ;
  SectorsPerTrack: DWORD ;
  BytesPerSector : DWORD ;
end;
TDISK_GEOMETRY_EX = record
  Geometry: TDISK_GEOMETRY ;
  DiskSize:  Int64; //LARGE_INTEGER ;
  Data : array[1..1000] of byte; // unknown length
end;
function get_disk_size(handle: thandle): int64;
var
  BytesReturned: DWORD;
  DISK_GEOMETRY_EX : TDISK_GEOMETRY_EX;
begin
  result := 0;
  if DeviceIOControl(handle,IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
     nil,0,@DISK_GEOMETRY_EX, sizeof(TDISK_GEOMETRY_EX),BytesReturned,nil)
  then result := DISK_GEOMETRY_EX.DiskSize;
end;