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