Как запросить информацию о файле «Размер на диске»? - PullRequest
6 голосов
/ 28 марта 2012

Я хочу воспроизвести поведение, показанное в Проводник Windows -> Диалог свойств -> Страница общих свойств для любого данного файла.В частности, я хочу воспроизвести точное значение поля «Размер на диске».

Ответы [ 5 ]

3 голосов
/ 29 марта 2012

Поскольку GetCompressedFileSize возвращает фактический размер обоих обычных / сжатых / резервных файлов любого типа , вы можете положиться на эту функцию, чтобы вернуть File Size on Disk (Проводник Windows отображает это значение в виде коэффициента размера кластера тома) и получает File Size с помощью функции GetFileSize.

Из документов 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_COMPRESSED или FILE_ATTRIBUTE_SPARSE_FILE, НО, поскольку GetCompressedFileSize делает это для нас внутренне (позвонив по номеру NtQueryInformationFile), Я не вижу смысла в этих тестах.

3 голосов
/ 28 марта 2012

В статье Раймона Чена о Windows Confidential описано, как рассчитывается это значение. Самый подходящий абзац гласит:

Размер на диске более сложен. Если накопитель поддерживает сжатие (как сообщается флагом FILE_FILE_COMPRESSION, возвращаемым функцией GetVolumeInformation), а файл сжат или является разреженным (FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_SPARSE_FILE), тогда размер на диске для файла является значением, сообщенным функцией GetCompressedFileS. Это сообщает сжатый размер файла (если сжат) или размер файла минус части, которые были отменены и логически обработаны как ноль (если они редки). Если файл не является ни сжатым, ни разреженным, то Размер на диске - это размер файла, сообщаемый функцией FindFirstFile, округленный до ближайшего кластера.

2 голосов
/ 28 марта 2012

Как уже говорили другие, вам нужно использовать GetFileInformationByHandleEx, но похоже, что вам нужно использовать FILE_STANDARD_INFO или FILE_ID_BOTH_DIR_INFO.Требуемая информация возвращается в элементе AllocationSize каждого, но вторая - для дескрипторов каталога, чтобы вывести список файлов внутри самого каталога (не рекурсивный, просто верхнего уровня).Чтобы сделать это проще, у FILE_STANDARD_INFO есть логическое значение Directory, поэтому сначала назовите его, если вы не уверены.Согласно документации для FILE_ID_BOTH_DIR_INFO,

AllocationSize Содержит значение, указывающее, сколько места выделено для файла в байтах.Это значение обычно кратно размеру сектора или кластера базового физического устройства.

Это, кажется, дает вам информацию Size on Disk.

Я не нашел Delphi-перевода структуры FILE_ID_BOTH_DIR_INFO.Кажется, что сложность заключается в последнем члене WCHAR FileName[1], который описывается как:

FileName [1]
Содержит первый символ строки имени файла,За этим следует в памяти остаток строки.

Я не уверен, как это будет обрабатываться в Delphi.

2 голосов
/ 28 марта 2012

Вы можете использовать функцию GetFileInformationByHandleEx для получения FILE_COMPRESSION_INFO структуры, ее поле CompressedFileSize - это нужное вам значение (то же, что возвращается GetCompressedFileSize).

0 голосов
/ 28 марта 2012

Размещение рутины согласно выписке Дэвида из статьи Рэймонда. Не стесняйтесь улучшать это!

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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...