Delphi Pascal - Использование SetFilePointerEx и GetFileSizeEx, получение точного размера физического носителя при чтении в файл - PullRequest
2 голосов
/ 31 декабря 2010

Я не знаю, как использовать любой API, которого нет в RTL.Я использовал SetFilePointer и GetFileSize, чтобы прочитать физический диск в буфер и выгрузить его в файл, что-то вроде этого в цикле делает работу для карт флэш-памяти под 2 ГБ:

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

Однако GetFileSize имеетограничение в 2 ГБ и так же SetFilePointer.Я абсолютно не знаю, как исключить внешний API, я посмотрел 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 для записи исходного кода загрузчика программы или чего-то еще на диск.Мне нужно иметь возможность установить указатель файла за пределы 2 ГБ, а то и дальше.

Может ли кто-нибудь помочь мне сделать внешние объявления и вызов как GetFileSizeEx, так и SetFilePointerEx, которые работают, чтобы я мог изменить свой старый код для работы с скажемФлэш-карты от 4 до 32 ГБ.

Ответы [ 5 ]

8 голосов
/ 31 декабря 2010

Я предлагаю вам взглянуть на эту статью блога Primoz Gabrijelcic и его GpHugeFile блок, который должен дать вам достаточно указателей для получения размера файла.

Редактировать 1 Это выглядит довольно глупым ответом в свете редактирования вопроса.

Редактировать 2 Теперь, когда этот ответ принят после долгих обсужденийкомментариев к ответу jachguate, я считаю необходимым обобщить то, что было изучено.

  • GetFileSize и SetFilePointer не имеютОграничение 2 ГБ, их можно использовать для файлов произвольного размера.

  • GetFileSizeEx и SetFilePointerEx намного проще в использованиипотому что они работают напрямую с 64-битными величинами и имеют гораздо более простые сигналы об ошибках.

  • ОП на самом деле не нужно было вычислять размер своего диска.Поскольку OP считывал все содержимое диска, размер не был необходим.Все, что требовалось, - это читать содержимое последовательно, пока ничего не осталось.

  • Фактически GetFileSize / GetFileSizeEx doне поддерживает дескрипторы устройств (например, физического диска или тома), как было запрошено OP.Более того, SetFilePointer / SetFilePointerEx не может искать конец таких дескрипторов устройства.

  • Для того, чтобы получить размердиска, тома или раздела необходимо передать управляющий код IOCTL_DISK_GET_LENGTH_INFO на DeviceIoControl.

Наконец, следуетвам нужно использовать GetFileSizeEx и SetFilePointerEx, тогда они могут быть объявлены следующим образом:

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-импорт -через превосходную библиотеку API JEDI .

3 голосов
/ 31 декабря 2010

Подпрограмма GetFileSizeEx ожидает указатель на тип данных LARGE_INTEGER , а документация гласит:

Если ваш компилятор имеет встроенную поддержку 64-битных целых чиселиспользуйте член QuadPart для хранения 64-разрядного целого числа

К счастью, Delphi имеет встроенную поддержку 64-разрядных целых чисел, поэтому используйте ее:

var
  DriveSize: LongWord;
begin
  GetFilePointerSizeEx(PD, @DriveSize);
end;

SetFilePointerEx , с другой стороны, ожидает параметры для liDistanceToMove, lpNewFilePointer, оба 64-битных целых числа.Насколько я понимаю, нужны целые числа со знаком, но у вас есть тип данных UInt64 для 64-битных целых Unsingned, если я неправильно понимаю документацию.

2 голосов
/ 31 декабря 2010

Альтернативное кодирование

Самоубийство, в первую очередь ваш подход неверен, и из-за вашего неправильного подхода вы столкнулись с некоторыми сложными проблемами, связанными с тем, как 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));

Не удалось.В реализации TFileStream, которая зависит от вызова FileSeek, и FileSeek не может обрабатывать файлы размером более 2 ГБ.Поэтому я попробовал GetFileSize, используя этот код:

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

Это также не помогает, даже если оно должно быть способно вернуть размер файла как 64-битное число!Затем я попытался использовать API SetFilePointer, потому что он также должен обрабатывать 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;

Последние примечания

Три подпрограммы, которые я предоставляю, принимают в качестве параметра TFileStream, потому что это то, что я использую для чтения и записи файлов.Очевидно, они используют только TFileStream.Handle, поэтому параметр можно просто заменить дескриптором файла: функциональность останется прежней.

0 голосов
/ 09 мая 2015

Очень, очень полезно.Но у меня проблема с дисками объемом более 4 ГБ.Я решил заменить:

   // 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;

Еще раз большое спасибо ...

И большое спасибо также Джеймсу Р. ТвайнуЯ последовал совету использования 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;
0 голосов
/ 19 сентября 2012

Я знаю, что эта тема старая, но ...

Одно небольшое предложение - если вы используете функцию Windows DeviceIoControl (...) , вы можете получить информацию о геометрии диска и / или информации о разделах и использовать их для получения общего размера / длины открытого диска. или раздел. Больше не нужно возиться с пошаговым поиском до конца устройства.

Эти IOCTL также можно использовать для получения правильного размера сектора тома, и вы можете использовать его вместо значения по умолчанию 512 везде.

...