我使用 Lazarus 1.2.2 和 Freepascal 2.6.4。
我有一个名为 QuickHash 的程序,可以对文件进行哈希处理,当我在 Linux 上运行它时,它也可以用于对物理磁盘进行哈希处理 (/dev/sdXX)。不过,我想在 Windows 版本中添加该功能。
我认为要访问物理设备(例如磁盘),必须使用 CreateFile 。具体来说,CreateFileW。
因此,用户单击一个按钮来扫描计算机中的磁盘并将它们列在列表框中。然后,用户双击的内容将被解析 (ListBox.GetSelectedText) 中的字符串“\.\PhyscialDiskX”,并将其分配给字符串变量,
strDiskID := getDiskID(Listbox.GetSelectedText);
效果很好。
然后我尝试创建该磁盘的句柄:
hSelectedDisk := Windows.CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
基于this article ,特别是“您必须同时使用 CreateFile() FILE_SHARE_READ 和 FILE_SHARE_WRITE 标志才能访问驱动器”我还尝试了以下其他两种组合:
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ AND FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
这三个人都成功分配了句柄。但是,顶部语法和底部语法最终会生成错误(如下所述)。中间选项立即返回零字节文件的默认初始化哈希,即 SHA1 的 DA39...。
我的问题是我无法将该句柄(这是一个整数)传递给 Freepascal md5 和 SHA1 单元的 SHA1File 和 MD5FILE 函数。他们期望一个文件名,该文件名必须是一个字符串。
因此,如果我将 strDiskID ('\.\PhyscialDiskX') 传递给它(这会完全破坏分配句柄的对象),我确实会获得磁盘事件,并且程序似乎正在运行。
strDiskHashValue := SHA1Print(SHA1File(strDiskID));
ShowMessage(strDiskHashValue);
CloseHandle(hSelectedDisk);
但即使在非常小的磁盘(如 500Mb USB 驱动器)上运行时,也会花费很多分钟并最终返回“运行错误 1117”,根据 this意思是
"ERROR_IO_DEVICE
1117 (0x45D)
The request could not be performed because of an I/O device error."
但是,我在几个工作磁盘上尝试过,错误仍然存在。
所以,我的问题最终是,我到底如何将成功分配的 THandle 传递给哈希函数? (UI 也在 Lazarus forums 上提出了这个问题,但有时我会从那些看不到那里帖子的成员那里得到答案)
最佳答案
您将无法将音量句柄传递给不需要音量句柄的函数。这些是非常特殊的 handle ,对其使用要求非常严格。最重要的是,您必须读取扇区对齐的 block ,并且其大小是扇区大小的倍数。
所以解决方案是让你负责读取数据。将其读入缓冲区,然后将该缓冲区传递给哈希库。这意味着您需要一个可以重复调用来处理新数据的哈希库。所有综合哈希库都将提供此类功能。
共享模式标志通过按位或组合:
FILE_SHARE_READ or FILE_SHARE_WRITE
我会像这样创建句柄:
hSelectedDisk := CreateFileW(PWideChar(strDiskID), FILE_READ_DATA,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
首先我会集中精力阅读本书的内容。一旦你能做到这一点,哈希就会成为常规。
从评论看来,您在编写哈希代码时遇到了一些麻烦。首先,您需要分配一个扇区大小倍数的缓冲区:
var
Buffer: Pointer;
....
GetMem(Buffer, BufferSize);
使用IOCTL_DISK_GET_DRIVE_GEOMETRY
找出扇区大小。并记下文档中的这段文字:
To read or write to the last few sectors of the volume, you must call DeviceIoControl and specify FSCTL_ALLOW_EXTENDED_DASD_IO. This signals the file system driver not to perform any I/O boundary checks on partition read or write calls. Instead, boundary checks are performed by the device driver.
现在您有了缓冲区,您可以读取内容并对其进行哈希处理。
var
ctx: TSHA1Context;
Digest: TSHA1Digest;
BytesRead: DWORD;
....
SHA1Init(ctx);
repeat
if not ReadFile(hSelectedDisk, Buffer^, BufferSize, BytesRead, nil) then
// handle error, raise exception
SHA1Update(ctx, Buffer^, BytesRead);
until BytesRead < BufferSize;
SHA1Final(ctx, Digest);
我没有尝试编译或测试此代码。它并不意味着完整或全面。它只是为了向您展示如何解决该问题。
关于winapi - 在 Windows 上使用 CreateFileW 在物理磁盘上运行 Freepascal 哈希函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24171497/