我不知道如何使用RTL中没有的任何API。我一直在使用SetFilePointer和GetFileSize将物理磁盘读入缓冲区并将其转储到文件中,这样的循环就可以完成2GB以下闪存卡的工作:
SetFilePointer(PD,0,nil,FILE_BEGIN);
SetLength(Buffer,512);
ReadFile(PD,Buffer[0],512,BytesReturned,nil);
然而,GetFileSize的限制为2GB,SetFilePointer也是如此。我绝对不知道如何对外部API进行delcare,我已经查看了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将初始程序加载程序代码或其他内容写入磁盘。我需要能够将文件指针设置为超过2gb。
有人可以帮我制作外部声明,并且可以调用GetFileSizeEx和SetFilePointerEx,这样我就可以修改旧代码,使用4到32GB的闪存卡。
答案 0 :(得分:8)
我建议你看看这个Primoz Gabrijelcic blog article和他的GpHugeFile单元,它应该给你足够的指针来获取文件大小。
编辑1 现在根据问题的编辑,这看起来相当愚蠢。
编辑2 既然已经接受了这个答案,经过对jachguate答案的长篇评论,我觉得有必要总结已经学到的东西。
GetFileSize
和
SetFilePointer
没有2GB
限制,它们可以用于文件
基本上是任意大小。
GetFileSizeEx
和
SetFilePointerEx
很多
更容易使用因为它们有效
直接使用64位数量和
有更简单的错误条件
信号。
OP实际上并不需要 计算他的磁盘大小。以来 OP正在阅读整个 磁盘的内容大小不是 需要。所需要的只是 按顺序读取内容,直到 什么都没有留下。
事实上
GetFileSize
/ GetFileSizeEx
不支持设备句柄
(例如物理磁盘或卷)
OP要求。更重要的是,
SetFilePointer
/ SetFilePointerEx
不能寻求到这种设备的尽头
处理
为了获得a的大小
磁盘,卷或分区,应该
通过了
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的一种简单方法是通过优秀的JEDI API Library。
答案 1 :(得分:3)
GetFileSizeEx例程需要一个指向LARGE_INTEGER数据类型的指针,文档说:
如果您的编译器内置支持64位整数,请使用QuadPart成员存储64位整数
幸运的是,Delphi内置了对64位整数的支持,所以请使用它:
var
DriveSize: LongWord;
begin
GetFilePointerSizeEx(PD, @DriveSize);
end;
另一方面,SetFilePointerEx期望liDistanceToMove,lpNewFilePointer的参数都是64位整数。我的理解是它需要有符号的整数,但是如果我对这些文档有所了解,那么你有未经过64位整数的UInt64数据类型。
答案 2 :(得分:2)
自杀,首先你的方法是错误的,并且由于你的错误方法,你遇到了一些毛病问题,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));
失败了。在依赖于调用FileSeek的TFileStream实现中,FileSeek无法处理大于2Gb的文件。所以我尝试使用此代码尝试GetFileSize:
var RetSize, UpperWord:DWORD;
RetSize := GetFileSize(F.Handle, @UpperWord);
ShowMessage(IntToStr(UpperWord) + ' / ' + IntToStr(RetSize));
那也失败了,即使它应该完全能够将文件大小作为64位数字返回!接下来我尝试使用SetFilePointer API,因为它也应该处理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;
我提供的3个例程将TFileStream作为参数,因为这是我用于文件读写的内容。他们显然只使用TFileStream.Handle,因此参数可以简单地用文件句柄替换:功能将保持不变。
答案 3 :(得分:0)
我知道这个帖子很旧,但是......
一个小建议 - 如果您使用Windows DeviceIoControl(...)功能,您可以获取驱动器几何和/或分区信息,并使用它们来获取打开的总大小/长度驱动器或分区。不再需要逐步寻找到设备的末端。
这些IOCTL也可用于为您提供正确的扇区大小,您可以使用它而不是在任何地方默认为512.
答案 4 :(得分:0)
非常有用。但是对于大于4 GB的磁盘我遇到了问题。 我解决了替换问题:
// 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;
再次感谢...
还要感谢James R. Twine。我遵循了使用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;