Delphi - 使用 DeviceIoControl 传递 IOCTL_DISK_GET_LENGTH_INFO 来获取闪存介质物理大小(非分区)

Posted

技术标签:

【中文标题】Delphi - 使用 DeviceIoControl 传递 IOCTL_DISK_GET_LENGTH_INFO 来获取闪存介质物理大小(非分区)【英文标题】:Delphi - Using DeviceIoControl passing IOCTL_DISK_GET_LENGTH_INFO to get flash media physical size (Not Partition) 【发布时间】:2011-01-03 00:52:16 【问题描述】:

好的,这是其他几个问题的结果。看来我对建议做错了,此时使用建议的 API 获取媒体大小时出现错误。我在物理磁盘级别工作,而不是在分区或文件系统的范围内。

我在做什么

我正在尝试获取闪存卡从第一个块到最后一个块的大小,引导记录分区空间等等。虽然我不需要它来转储卡片中的信息,但我确实需要动态写入能力。我想说的是,在离卡片末端很远的地方放一个标记,不管它有多大。有人建议我将 IOCTL_DISK_GET_LENGTH_INFO 传递给 DeviceIoControl,起初我没有任何结果,但现在我终于从 windows 50 收到错误。

项目来源

这是主单元的 pastebin 代码 (Delphi 2009) - http://clutchx2.pastebin.com/iMnq8kSx

这是应用程序源代码和可执行文件,其中包含一个用于输出正在发生的状态的表单 - http://www.mediafire.com/?js8e6ci8zrjq0de

它可能更容易使用下载,除非您只是在代码中寻找问题。我也会把代码贴在这里。

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TfrmMain = class(TForm)
    edtDrive: TEdit;
    lblDrive: TLabel;
    btnMethod1: TButton;
    btnMethod2: TButton;
    lblSpace: TLabel;
    edtSpace: TEdit;
    lblFail: TLabel;
    edtFail: TEdit;
    lblError: TLabel;
    edtError: TEdit;
    procedure btnMethod1Click(Sender: TObject);
  private
     Private declarations 
  public
     Public declarations 
  end;

  TDiskExtent = record
    DiskNumber: Cardinal;
    StartingOffset: Int64;
    ExtentLength: Int64;
  end;
  DISK_EXTENT = TDiskExtent;
  PDiskExtent = ^TDiskExtent;
  TVolumeDiskExtents = record
    NumberOfDiskExtents: Cardinal;
    Extents: array[0..0] of TDiskExtent;
  end;
  VOLUME_DISK_EXTENTS = TVolumeDiskExtents;
  PVolumeDiskExtents = ^TVolumeDiskExtents;

var
  frmMain: TfrmMain;

const
  FILE_DEVICE_DISK                     = $00000007;
  METHOD_BUFFERED                      = 0;
  FILE_ANY_ACCESS                      = 0;
  IOCTL_DISK_BASE                      = FILE_DEVICE_DISK;
  IOCTL_VOLUME_BASE                    = DWORD('V');
  IOCTL_DISK_GET_LENGTH_INFO = $80070017;
  IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = ((IOCTL_VOLUME_BASE shl 16) or (FILE_ANY_ACCESS shl 14) or (0 shl 2) or METHOD_BUFFERED);

implementation

$R *.dfm

function GetLD(Drive: Char): Cardinal;
var
  Buffer : String;
begin
  Buffer := Format('\\.\%s:',[Drive]);
  Result := CreateFile(PChar(Buffer),GENERIC_READ Or GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  If Result = INVALID_HANDLE_VALUE Then
    begin
    Result := CreateFile(PChar(Buffer),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  end;
end;

function GetPD(Drive: Byte): Cardinal;
var
  Buffer : String;
begin
  If Drive = 0 Then
    begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  Buffer := Format('\\.\PHYSICALDRIVE%d',[Drive]);
  Result := CreateFile(PChar(Buffer),GENERIC_READ Or GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  If Result = INVALID_HANDLE_VALUE Then
    begin
    Result := CreateFile(PChar(Buffer),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  end;
end;

function GetPhysicalDiskNumber(Drive: Char): Byte;
var
  LD : DWORD;
  DiskExtents : PVolumeDiskExtents;
  DiskExtent : TDiskExtent;
  BytesReturned : Cardinal;
begin
  Result := 0;
  LD := GetLD(Drive);
  If LD = INVALID_HANDLE_VALUE Then Exit;
  Try
    DiskExtents := AllocMem(Max_Path);
    DeviceIOControl(LD,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,nil,0,DiskExtents,Max_Path,BytesReturned,nil);
    If DiskExtents^.NumberOfDiskExtents > 0 Then
      begin
      DiskExtent := DiskExtents^.Extents[0];
      Result := DiskExtent.DiskNumber;
    end;
  Finally
    CloseHandle(LD);
  end;
end;

procedure TfrmMain.btnMethod1Click(Sender: TObject);
var
  PD : DWORD;
  CardSize: Int64;
  BytesReturned: DWORD;
  CallSuccess: Boolean;
begin
  PD := GetPD(GetPhysicalDiskNumber(edtDrive.Text[1]));
  If PD = INVALID_HANDLE_VALUE Then
    Begin
      ShowMessage('Invalid Physical Disk Handle');
      Exit;
    End;
  CallSuccess := DeviceIoControl(PD, IOCTL_DISK_GET_LENGTH_INFO, nil, 0, @CardSize, SizeOf(CardSize), BytesReturned, nil);
  if not CallSuccess then
    begin
      edtError.Text := IntToStr(GetLastError());
      edtFail.Text := 'True';
    end
    else edtFail.Text := 'False';
  CloseHandle(PD);
end;

end.

我在表单上放置了第二个方法按钮,这样如果我愿意,我可以在应用程序中编写一组不同的代码。只有最少的错误处理和保护措施,没有任何东西是通过源代码调试所必需的。

媒体类型和界面

我在使用 PSP 作为阅读器的索尼记忆棒上进行了尝试,因为我在我的机器中找不到使用二重奏的适配器。目标是 MS,我的一半用户使用 PSP,而另一半则不使用。但是,这在 SD 卡上应该可以正常工作,这也是我工作的次要目标。我在一个 USB 存储卡读卡器和几张 SD 卡上试过这个。

问题

现在我已经修复了我的尝试,但返回了一个错误。 50 ERROR_NOT_SUPPORTED 请求不受支持。

类似的应用程序

我找到了一个应用程序,它使用这个 API 以及很多相关函数来完成我正在尝试做的事情。我正准备研究它,该应用程序名为 DriveImage,它的源代码在这里 - http://sourceforge.net/projects/diskimage/

我从该应用程序中真正注意到的唯一一件事是使用了 TFileStream 并使用它来获取物理磁盘的句柄。

【问题讨论】:

您也可以使用 WMI 来完成这项任务。 【参考方案1】:

我知道这里可能会发生什么。

试试这个:

对于每个“FILE_SHARE_READ”,将其更改为“FILE_SHARE_WRITE 或 FILE_SHARE_READ”

来自 msdn:

“如果你想使用 \.\X: 打开一个卷,你必须使用 FILE_SHARE_WRITE | FILE_SHARE_READ,而不仅仅是 FILE_SHARE_WRITE。如果你省略 FILE_SHARE_READ,你会在大多数卷上得到 ERROR_NOT_SUPPORTED”

http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx

编辑:

你会讨厌这个,但它失败的真正原因是你对 IOCTL_DISK_GET_LENGTH_INFO 的定义是错误的。用这个替换它:

((IOCTL_DISK_BASE shl 16) or (FILE_READ_ACCESS shl 14) or ($0017 shl 2) or METHOD_BUFFERED);

结果是 0x7405C 而不是 0x80070017

【讨论】:

T6 天哪,你收到了我的电子邮件,哈哈。很高兴终于再次见到你。如您所见,我仍在使用您为一个项目提供的句柄代码。它几乎是我丢失源时唯一没有丢失的东西。 哈哈,是的。我确实认出了代码;)。这解决了问题吗? 还没有尝试过,这就是为什么我确实取消了检查一分钟。但根据 Klutsh 的说法,他遇到了同样的问题,这就是问题所在,所以是的,谢谢老兄。 碰巧我正在做一个需要 CreateFile() 的项目,然后我重新阅读了 msdn 页面。当我看到这个问题时,我记得看到一些关于 ERROR_NOT_SUPPORTED 的内容。 这可能是我仍在做的事情,但似乎并没有改变这种情况。我会等一下,我正在查看 Klutsh 的 C# 代码,看看他也在做什么。【参考方案2】:

问题已经回答了。作为替代方案,您可以使用 IOCTL_DISK_GET_DRIVE_GEOMETRY 来获取磁盘大小。这是我在项目中使用的工作代码:

const
  IOCTL_DISK_GET_DRIVE_GEOMETRY  = $00070000;

type
$MINENUMSIZE 4
  TMediaType = (
    Unknown,                // Format is unknown
    F5_1Pt2_512,            // 5.25", 1.2MB,  512 bytes/sector
    F3_1Pt44_512,           // 3.5",  1.44MB, 512 bytes/sector
    F3_2Pt88_512,           // 3.5",  2.88MB, 512 bytes/sector
    F3_20Pt8_512,           // 3.5",  20.8MB, 512 bytes/sector
    F3_720_512,             // 3.5",  720KB,  512 bytes/sector
    F5_360_512,             // 5.25", 360KB,  512 bytes/sector
    F5_320_512,             // 5.25", 320KB,  512 bytes/sector
    F5_320_1024,            // 5.25", 320KB,  1024 bytes/sector
    F5_180_512,             // 5.25", 180KB,  512 bytes/sector
    F5_160_512,             // 5.25", 160KB,  512 bytes/sector
    RemovableMedia,         // Removable media other than floppy
    FixedMedia,             // Fixed hard disk media
    F3_120M_512             // 3.5", 120M Floppy
  );
$MINENUMSIZE 1
  PDiskGeometry = ^TDiskGeometry;
  TDiskGeometry = packed record
    Cylinders: int64;
    MediaType: TMediaType;
    TracksPerCylinder: DWORD;
    SectorsPerTrack: DWORD;
    BytesPerSector: DWORD;
  end;

var
  H: THandle;
  BytesReturned: DWORD;
  DG: TDiskGeometry;
  DSize: int64;

begin
  H:= CreateFile(PChar('\\.\G:'), GENERIC_READ,
    FILE_SHARE_WRITE or FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if Handle = INVALID_HANDLE_VALUE then
    raise Exception.Create('OOps!');
  if not DeviceIOControl(H, IOCTL_DISK_GET_DRIVE_GEOMETRY, nil, 0,
    @DG, SizeOf(TDiskGeometry), BytesReturned, nil) then
      raise Exception.Create('OOps #2!');
  DSize:= DG.Cylinders * DG.TracksPerCylinder;
  DSize:= DSize * (DG.SectorsPerTrack * DG.BytesPerSector);
  ShowMessage(IntToStr(DSize));
end;

【讨论】:

尽管问题已经解决,但我非常感谢您对这个主题的补充。为你点赞。

以上是关于Delphi - 使用 DeviceIoControl 传递 IOCTL_DISK_GET_LENGTH_INFO 来获取闪存介质物理大小(非分区)的主要内容,如果未能解决你的问题,请参考以下文章

delphi7中XpMenu控件怎么使用

如何使用delphi combobox

为啥 Delphi 程序员会使用 Lazarus 作为 IDE 而不是使用 Delphi 的 IDE? [关闭]

delphi中使用HTTP控件,怎么使用POST的异步方式

delphi 树形控件的使用

delphi_Case...Of语句使用?