如何查询“磁盘大小”文件信息?

时间:2012-03-28 08:24:37

标签: delphi winapi filesystems

我想重现 Windows资源管理器中显示的行为 - >属性对话框 - >任何给定文件的常规属性页。具体来说,我想重现“磁盘大小”字段的确切值。

5 个答案:

答案 0 :(得分:3)

Raymond Chen关于Windows Confidential的文章介绍了如何计算该值。最相关的段落指出:

  

磁盘测量尺寸更复杂。如果驱动器支持压缩(由GetVolumeInformation函数返回的FILE_FILE_COMPRESSION标志报告)并且文件是压缩的或稀疏的(FILE_ATTRIBUTE_COMPRESSED,FILE_ATTRIBUTE_SPARSE_FILE),则文件的磁盘大小是GetCompressedFileSize函数报告的值。这将报告文件的压缩大小(如果已压缩)或文件大小减去已取消提交并在逻辑上视为零的部分(如果是稀疏的)。如果文件既不是压缩文件也不是稀疏文件,那么磁盘上的大小是FindFirstFile函数报告的文件大小,四舍五入到最近的集群。

答案 1 :(得分:3)

由于GetCompressedFileSize将返回任何卷类型的普通/压缩/备用文件的实际大小,您可以依赖此函数返回File Size on Disk(Windows Explorer将此值显示为卷Cluster Size的因子,并使用GetFileSize函数获取File Size

来自MSDN文档GetCompressedFileSize

  

如果文件不在支持压缩的卷上,或者   稀疏文件,或者如果文件没有压缩或稀疏文件,   获得的值是实际文件大小,与返回的值相同   通过调用GetFileSize。

因此逻辑由以下代码描述(在Windows XP上使用FAT32 / FAT / CDfs文件进行测试):

procedure FileSizeEx(const FileName: string; out Size, SizeOnDisk: UINT);
var
  Drive: string;
  FileHandle: THandle;
  SectorsPerCluster,
  BytesPerSector,
  Dummy: DWORD;
  ClusterSize: DWORD;
  SizeHigh, SizeLow: DWORD;
begin
  Assert(FileExists(FileName));
  Drive := IncludeTrailingPathDelimiter(ExtractFileDrive(FileName));
  if not GetDiskFreeSpace(PChar(Drive), SectorsPerCluster, BytesPerSector, Dummy, Dummy) then
    RaiseLastOSError;

  ClusterSize := SectorsPerCluster * BytesPerSector;

  FileHandle := CreateFile(PChar(FileName), 0, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
    nil, OPEN_EXISTING, 0, 0);
  if (FileHandle = INVALID_HANDLE_VALUE) then
    RaiseLastOSError;
  try
    SizeLow := Windows.GetFileSize(FileHandle, @SizeHigh);
    if (GetLastError <> NO_ERROR) and (SizeLow = INVALID_FILE_SIZE) then
      RaiseLastOSError;
    Size := UINT(SizeHigh shl 32 or SizeLow);
  finally
    if (FileHandle <> INVALID_HANDLE_VALUE) then
      CloseHandle(FileHandle);
  end;

  SizeLow := GetCompressedFileSize(PChar(FileName), @SizeHigh);
  if (GetLastError <> NO_ERROR) and (SizeLow = INVALID_FILE_SIZE) then
    RaiseLastOSError;

  SizeOnDisk := UINT(SizeHigh shl 32 or SizeLow);
  if (SizeOnDisk mod ClusterSize) > 0 then
    SizeOnDisk := SizeOnDisk + ClusterSize - (SizeOnDisk mod ClusterSize);
end;

我们检查Get­Volume­Information是否有压缩/稀疏支持,然后GetFileAttributes检查FILE_ATTRIBUTE_COMPRESSEDFILE_ATTRIBUTE_SPARSE_FILE,但是GetCompressedFileSize为我们内部做了这些(通过调用NtQueryInformationFile),我认为这些测试没有意义。

答案 2 :(得分:2)

您可以使用GetFileInformationByHandleEx函数获取FILE_COMPRESSION_INFO结构,其CompressedFileSize字段是您需要的值(与GetCompressedFileSize返回的相同)。

答案 3 :(得分:2)

正如其他人所说,您需要使用GetFileInformationByHandleEx,但看起来您需要使用FILE_STANDARD_INFOFILE_ID_BOTH_DIR_INFO。您想要的信息在每个的AllocationSize成员中返回,但第二个用于目录句柄,列出文件而不是目录本身(注意:不是递归,只是顶级)。为了方便起见,FILE_STANDARD_INFO有一个Directory布尔值,如果您不确定,请先调用它。根据{{​​1}}的文档,

  

AllocationSize   包含指定为文件分配多少空间的值(以字节为单位)。此值通常是底层物理设备的扇区或簇大小的倍数。

这似乎为您提供了FILE_ID_BOTH_DIR_INFO信息。

我还没有找到Size on Disk结构的Delphi翻译。难度似乎是最终成员FILE_ID_BOTH_DIR_INFO,其描述为:

  

FileName [1]
  包含文件名字符串的第一个字符。在内存中跟随字符串的其余部分。

我不确定如何在Delphi中处理它。

答案 4 :(得分:0)

根据大卫雷蒙德的文章摘录发布例行程序。 随意改进它!

uses
  System.SysUtils, Windows;

function GetClusterSize(Drive: String): integer;
var
  SectorsPerCluster, BytesPerSector, dummy: Cardinal;
begin
  SectorsPerCluster := 0;
  BytesPerSector := 0;
  GetDiskFreeSpace(PChar(Drive), SectorsPerCluster, BytesPerSector, dummy, dummy);

  Result := SectorsPerCluster * BytesPerSector;
end;

function FindSizeOnDisk(Drive: String; AFilename: string): Int64;
var
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
  FileSystemFlags: DWORD;
  HighSize: DWORD;
  FRec: TSearchRec;
  AClusterSize: integer;
  AFileSize, n: Int64;
begin
  Result := 0;
  Drive := IncludeTrailingPathDelimiter(ExtractFileDrive(Drive));
  GetVolumeInformation( PChar(Drive), nil, 0, @VolumeSerialNumber,
    MaximumComponentLength, FileSystemFlags, nil, 0);
  if ((FileSystemFlags AND FILE_FILE_COMPRESSION) <> 0) AND
    ((FileSystemFlags AND (FILE_VOLUME_IS_COMPRESSED OR
    FILE_SUPPORTS_SPARSE_FILES)) <> 0) then
  begin // Compressed or Sparse disk
    Result := GetCompressedFileSize(PChar(AFilename), @HighSize);
    // Not sure if this is correct on a sparse disk ??
  end
  else
  begin
    if (System.SysUtils.FindFirst(AFilename, faAnyFile, FRec) = 0) then
    begin
      AFileSize := FRec.Size;
      AClusterSize := GetClusterSize(Drive);
      n := AFileSize mod AClusterSize;
      if n > 0 then // Round up to nearest cluster size
        Result := AFileSize + (AClusterSize - n)
      else
        Result := AFileSize;
      System.SysUtils.FindClose(FRec);
    end;
  end;
end;