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

7

我想要复制在任何给定文件中展示的 Windows资源管理器 -> 属性对话框 -> 常规属性页 中的行为。具体来说,我想要复制“磁盘上占用空间大小”字段的精确值。


4
当您说“GetCompressedFileSize不是正确的函数”时,您能否详细说明一下? - David Heffernan
你确定它不起作用吗?也许你只是没有正确地组合低位和高位的DWORD。 - David Heffernan
3
我投票关闭了,因为我看不懂问题所在。相反,你只是发布了很多关于如何找出某个文件的异常之处和你的集群大小有多大的信息。而且仍然没有信息,关于GetCompressedFileSize出了什么问题,或者你从这样的函数中期望得到什么(最好提供一个例子,比如我有一个0B的文件,在资源管理器中我可以看到磁盘上的文件大小也是0B,但是GetCompressedFileSize返回其他结果)。 - TLama
2
我必须同意@TLama的观点。请问问题是什么?(请用简单易懂的英语表述) - kobik
2
问题似乎已经改变了。您能解释一下您尝试过什么以及它是如何失败的吗? - David Heffernan
显示剩余6条评论
5个回答

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

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

这似乎给出了磁盘上的大小信息。
我没有找到FILE_ID_BOTH_DIR_INFO结构的Delphi翻译。困难似乎在于最后一个成员WCHAR FileName[1],描述如下:

FileName[1]
包含文件名字符串的第一个字符。其后是字符串的其余部分。

我不确定在Delphi中如何处理这个问题。

实际上不是这样的。有一个注释说它在XP上可用,在头文件部分:WinBase.h(包括Windows.h); FileExtd.h在Windows Server 2003和Windows XP上,需要链接库,因此您可以在C++Builder中创建一个包装器DLL,然后通过该包装器调用它。 - Ken White
或者任何C或C++编译器,例如在平台SDK中分发的MS编译器。 - David Heffernan
FileName[1] 是一个可变长结构体。一旦你知道文件名的长度,你就必须在堆上为该结构体分配内存。 - David Heffernan

3

Raymond ChenWindows Confidential文章描述了如何计算该值。最相关的段落如下:

磁盘上的大小测量更加复杂。如果驱动器支持压缩(通过Get­Volume­Information函数返回的FILE_FILE_COMPRESSION标志报告),并且文件已经被压缩或稀疏(FILE_ATTRIBUTE_COMPRESSED,FILE_ATTRIBUTE_SPARSE_FILE),那么文件的磁盘占用大小是由Get­Compressed­File­Size函数报告的值。这个函数报告文件的压缩大小(如果已经压缩)或者文件减去那些被取消分配并逻辑上视为零的部分(如果是稀疏)。如果文件既没有被压缩也不是稀疏的,则磁盘上的大小是由Find­First­File函数报告的文件大小向上舍入到最近的簇。


3
GetCompressedFileSize返回的是任何卷类型的普通/压缩/稀疏文件的实际大小,因此您可以依靠此函数返回“磁盘上的文件大小”(Windows资源管理器将此值显示为卷簇大小因子),并使用GetFileSize函数获取“文件大小”。
GetCompressedFileSize的MSDN文档中得知:

如果该文件不位于支持压缩或稀疏文件的卷上,或者该文件不是压缩的或稀疏文件,则获得的值是实际文件大小,与调用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
你可以使用GetFileInformationByHandleEx函数来获取 FILE_COMPRESSION_INFO 结构体,其中的CompressedFileSize字段即为你需要的值(与GetCompressedFileSize返回值相同)。请注意保留HTML标签,最好让内容更加易于理解。

2
尽管请注意,在XP上使用这个并不容易。 - David Heffernan
现在我完全困惑了。从成功调用返回的FILE_COMPRESSION_INFO填充了所有零。然而,我最终弄清楚了有问题的文件的异常之处,并更新了问题。 - OnTheFly
如果您传递FILE_STANDARD_INFO并读取AllocationSize会怎样?当它失败时,请尝试FILE_STREAM_INFO并读取StreamAllocationSize。仅适用于Vista及更高版本。 - The_Fox
零?你调用了带有参数 FileCompressionInfoGetFileInformationByHandleEx 函数来获取 FILE_INFO_BY_HANDLE_CLASS 吗? 更新:随着时间的推移,原始问题发生了很大变化...你想知道/找到什么? - Adriano Repetti

1

根据David从Raymond的文章中提取的例程进行发布。 随意改进它!

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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接