【问题标题】:How can i remove a USB flash disk programmatically using delphi?如何使用 delphi 以编程方式删除 USB 闪存盘?
【发布时间】:2010-09-30 21:15:17
【问题描述】:

如何使用 delphi 以编程方式检测和删除 USB 闪存盘?

我在这个网站上看到了一些例子,但他们没有清楚地解释如何去做!

请举例子真的很有帮助!

【问题讨论】:

    标签: delphi usb


    【解决方案1】:

    这是对this sample code 的快速而肮脏的翻译,用于删除来自 support.microsoft.com 的驱动器。但是,它仅适用于在我的系统上具有管理员权限的用户。

    有关使用 USB 设备的更多信息,请点击this answerconcept03 中的链接。

    function OpenVolume(ADrive: char): THandle;
    var
      RootName, VolumeName: string;
      AccessFlags: DWORD;
    begin
      RootName := ADrive + ':\'; (* '\'' // keep SO syntax highlighting working *)
      case GetDriveType(PChar(RootName)) of
        DRIVE_REMOVABLE:
          AccessFlags := GENERIC_READ or GENERIC_WRITE;
        DRIVE_CDROM:
          AccessFlags := GENERIC_READ;
      else
        Result := INVALID_HANDLE_VALUE;
        exit;
      end;
      VolumeName := Format('\\.\%s:', [ADrive]);
      Result := CreateFile(PChar(VolumeName), AccessFlags,
        FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
      if Result = INVALID_HANDLE_VALUE then
        RaiseLastWin32Error;
    end;
    
    function LockVolume(AVolumeHandle: THandle): boolean;
    const
      LOCK_TIMEOUT = 10 * 1000; // 10 Seconds
      LOCK_RETRIES = 20;
      LOCK_SLEEP = LOCK_TIMEOUT div LOCK_RETRIES;
    
    // #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
      FSCTL_LOCK_VOLUME = (9 shl 16) or (0 shl 14) or (6 shl 2) or 0;
    var
      Retries: integer;
      BytesReturned: Cardinal;
    begin
      for Retries := 1 to LOCK_RETRIES do begin
        Result := DeviceIoControl(AVolumeHandle, FSCTL_LOCK_VOLUME, nil, 0,
          nil, 0, BytesReturned, nil);
        if Result then
          break;
        Sleep(LOCK_SLEEP);
      end;
    end;
    
    function DismountVolume(AVolumeHandle: THandle): boolean;
    const
    // #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
      FSCTL_DISMOUNT_VOLUME = (9 shl 16) or (0 shl 14) or (8 shl 2) or 0;
    var
      BytesReturned: Cardinal;
    begin
      Result := DeviceIoControl(AVolumeHandle, FSCTL_DISMOUNT_VOLUME, nil, 0,
        nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function PreventRemovalOfVolume(AVolumeHandle: THandle;
      APreventRemoval: boolean): boolean;
    const
    // #define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS)
      IOCTL_STORAGE_MEDIA_REMOVAL = ($2d shl 16) or (1 shl 14) or ($201 shl 2) or 0;
    type
      TPreventMediaRemoval = record
        PreventMediaRemoval: BOOL;
      end;
    var
      BytesReturned: Cardinal;
      PMRBuffer: TPreventMediaRemoval;
    begin
      PMRBuffer.PreventMediaRemoval := APreventRemoval;
      Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_MEDIA_REMOVAL,
        @PMRBuffer, SizeOf(TPreventMediaRemoval), nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function AutoEjectVolume(AVolumeHandle: THandle): boolean;
    const
    // #define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
      IOCTL_STORAGE_EJECT_MEDIA = ($2d shl 16) or (1 shl 14) or ($202 shl 2) or 0;
    var
      BytesReturned: Cardinal;
    begin
      Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_EJECT_MEDIA, nil, 0,
        nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function EjectVolume(ADrive: char): boolean;
    var
      VolumeHandle: THandle;
    begin
      Result := FALSE;
      // Open the volume
      VolumeHandle := OpenVolume(ADrive);
      if VolumeHandle = INVALID_HANDLE_VALUE then
        exit;
      try
        // Lock and dismount the volume
        if LockVolume(VolumeHandle) and DismountVolume(VolumeHandle) then begin
          // Set prevent removal to false and eject the volume
          if PreventRemovalOfVolume(VolumeHandle, FALSE) then
            AutoEjectVolume(VolumeHandle);
        end;
      finally
        // Close the volume so other processes can use the drive
        CloseHandle(VolumeHandle);
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      EjectVolume('E');
    end;
    

    【讨论】:

    • 如果非用户管理员可以移除管理员正在使用的 USB 磁盘(甚至确定它是否正在使用),那将是非常糟糕的。所以这个限制似乎是合乎逻辑的。
    • 并非如此。即使我可以通过通知区域中的“安全删除硬件”图标删除 U 盘,此代码对我(非管理员,“高级用户”的成员)不起作用。它适用于“运行方式...”和管理员帐户,但...
    • @mghie,如果您在代码完整的地方修复了您的帖子,请告诉我,我将删除我的答案(公平,因为您完成了大部分工作 - 我只是使用了 C代码来填写不存在的内容)。
    • 如承诺的那样,我的已删除(并为我得到的那个+1)。
    • @Jerry:我使用了不同的技巧来使 SO 语法高亮显示愉快。它看起来仍然有点奇怪,但是现在应该可以简单地复制并粘贴到 Unicode IDE 中。
    【解决方案2】:

    这不会弹出驱动器,但会刷新驱动器缓冲区并使其安全移除。它需要 Vista 和更高版本下的管理权限(如果作为受限权限用户运行,则需要 XP,IIRC)。它可能应该尝试一下..finally 以确保 CloseHandle 被调用;我把它作为练习留给读者,因为这里的代码格式很紧凑,没有水平滚动。 :-)

    unit USBDriveFlush;
    
    interface
    
      uses Windows;
    
    type
      // Taken from JEDI JwaWinIoctl
      PSTORAGE_HOTPLUG_INFO = ^STORAGE_HOTPLUG_INFO;
      {$EXTERNALSYM PSTORAGE_HOTPLUG_INFO}
      _STORAGE_HOTPLUG_INFO = record
        Size: DWORD; // version
        MediaRemovable: BOOLEAN; // ie. zip, jaz, cdrom, mo, etc. vs hdd
        MediaHotplug: BOOLEAN;   // ie. does the device succeed a lock 
                                 // even though its not lockable media?
        DeviceHotplug: BOOLEAN;  // ie. 1394, USB, etc.
        WriteCacheEnableOverride: BOOLEAN; // This field should not be 
                                 // relied upon because it is no longer used
      end;
      {$EXTERNALSYM _STORAGE_HOTPLUG_INFO}
      STORAGE_HOTPLUG_INFO = _STORAGE_HOTPLUG_INFO;
      {$EXTERNALSYM STORAGE_HOTPLUG_INFO}
      TStorageHotplugInfo = STORAGE_HOTPLUG_INFO;
      PStorageHotplugInfo = PSTORAGE_HOTPLUG_INFO;    
    
      function FlushUSBDrive(const Drive: string): Boolean;
    
    implementation
    
    function FlushUSBDrive(const Drive: string): Boolean;
    var
      shpi : TStorageHotplugInfo;
      retlen : DWORD; //unneeded, but deviceiocontrol expects it
      h : THandle;
    begin
      Result := False;
      h := CreateFile(PChar('\\.\' + Drive),
                      0,
                      FILE_SHARE_READ or FILE_SHARE_WRITE,
                      nil,
                      OPEN_EXISTING,
                      0,
                      0);
      if h <> INVALID_HANDLE_VALUE then
      begin
        shpi.Size := SizeOf(shpi);
    
        if DeviceIoControl(h,
                           IOCTL_STORAGE_GET_HOTPLUG_INFO,
                           nil,
                           0,
                           @shpi,
                           SizeOf(shpi),
                           retlen,
                           nil) then
        begin
          //shpi now has the existing values, so you can check to
          //see if the device is already hot-pluggable
          if not shpi.DeviceHotplug then
          begin
            shpi.DeviceHotplug:= True;
    
            //Need to use correct administrator security privilages here
            //otherwise it'll just give 'access is denied' error
            Result := DeviceIoControl(h,
                                      IOCTL_STORAGE_SET_HOTPLUG_INFO,
                                       @shpi,
                                       SizeOf(shpi),
                                       nil,
                                       0,
                                       retlen,
                                       nil);
          end;
        end;
        CloseHandle(h);
      end;
    end;
    

    使用示例:

    if FlushUSBDrive('G:') then
      ShowMessage('Safe to remove USB drive G:')
    else
      ShowMessage('Flush of drive G: failed!' +
        SysErrorMessage(GetLastError()));
    

    【讨论】:

    • 它一直失败,在闪存棒上返回 false...检查管理权限...
    • 适用于我的拇指驱动器和外部 USB 1TB 驱动器。你有错误吗? (“它一直在失败”和“也没有工作”并不是很多信息。)
    • 没有通过if not shpi.DeviceHotplug then begin
    • 如果您没有收到错误,并且shpi.DeviceHotplug 返回 true,则驱动器已经可以安全弹出。我对您设备的功能或它处于什么状态一无所知。:-) 我只能告诉您该代码有效,并且自从我几年前最初发现并修改它以来已经使用了不止一次。跨度>
    • 是的,它返回错误的可能原因有很多种。我想我必须在不同场景下的许多不同机器上对其进行测试。
    【解决方案3】:

    删除U盘的关键是使用CM_Request_Device_Eject函数,

    检查这个示例 delphi 应用程序,基于这篇文章 How to Prepare a USB Drive for Safe Removal 并使用 JEDI API Library &amp; Security Code Library

    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      JwaWinIoctl,
      Cfg,
      CfgMgr32,
      SetupApi,
      Windows,
      SysUtils;
    
    
    function GetDrivesDevInstByDeviceNumber(DeviceNumber : LONG; DriveType : UINT; szDosDeviceName: PChar) : DEVINST;
    var
     StorageGUID : TGUID;
     IsFloppy : Boolean;
     hDevInfo : SetupApi.HDEVINFO;
     dwIndex  : DWORD;
     res      : BOOL;
     pspdidd  : PSPDeviceInterfaceDetailData;
     spdid    : SP_DEVICE_INTERFACE_DATA;
     spdd     : SP_DEVINFO_DATA;
     dwSize   : DWORD;
     hDrive   : THandle;
     sdn      : STORAGE_DEVICE_NUMBER;
     dwBytesReturned : DWORD;
    begin
      Result:=0;
        IsFloppy := pos('\\Floppy', szDosDeviceName)>0; // who knows a better way?
        case DriveType of
        DRIVE_REMOVABLE:
                    if ( IsFloppy ) then
                      StorageGUID := GUID_DEVINTERFACE_FLOPPY
                    else
                      StorageGUID := GUID_DEVINTERFACE_DISK;
    
        DRIVE_FIXED:  StorageGUID := GUID_DEVINTERFACE_DISK;
        DRIVE_CDROM:    StorageGUID := GUID_DEVINTERFACE_CDROM;
        else
            exit
      end;
    
        // Get device interface info set handle for all devices attached to system
        hDevInfo := SetupDiGetClassDevs(@StorageGUID, nil, 0, DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
      if (NativeUInt(hDevInfo) <> INVALID_HANDLE_VALUE) then
      try
        // Retrieve a context structure for a device interface of a device information set
        dwIndex := 0;
        //PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
        spdid.cbSize := SizeOf(spdid);
    
        while true do
        begin
          res := SetupDiEnumDeviceInterfaces(hDevInfo, nil, StorageGUID, dwIndex, spdid);
          if not res then
            break;
    
          dwSize := 0;
          SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, nil, 0, dwSize, nil); // check the buffer size
    
          if ( dwSize<>0) then
          begin
           pspdidd := AllocMem(dwSize);
           try
            pspdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
            ZeroMemory(@spdd, sizeof(spdd));
            spdd.cbSize := SizeOf(spdd);
            res := SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, pspdidd, dwSize, dwSize, @spdd);
            if res then
            begin
              // open the disk or cdrom or floppy
              hDrive := CreateFile(pspdidd.DevicePath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
             if ( hDrive <> INVALID_HANDLE_VALUE ) then
              try
                  // get its device number
                  dwBytesReturned := 0;
                  res := DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, sizeof(sdn), dwBytesReturned, nil);
                  if res  then
                  begin
                    if ( DeviceNumber = sdn.DeviceNumber) then
                    begin  // match the given device number with the one of the current device
                      Result:= spdd.DevInst;
                      exit;
                    end;
                  end;
              finally
                CloseHandle(hDrive);
              end;
            end;
           finally
             FreeMem(pspdidd);
           end;
          end;
          Inc(dwIndex);
        end;
      finally
       SetupDiDestroyDeviceInfoList(hDevInfo);
      end;
    end;
    
    procedure EjectUSB(const DriveLetter:char);
    var
      szRootPath, szDevicePath : PChar;
      szVolumeAccessPath : PChar;
      hVolume : THandle;
      DeviceNumber : LONG;
      sdn  : STORAGE_DEVICE_NUMBER;
      dwBytesReturned : DWORD;
      res : BOOL;
      resCM : Cardinal;
      DriveType : UINT;
      szDosDeviceName : array [0..MAX_PATH-1] of Char;
      DevInst  : CfgMgr32.DEVINST;
      VetoType : PNP_VETO_TYPE;
      VetoName : array [0..MAX_PATH-1] of WCHAR;
      bSuccess : Boolean;
      DevInstParent : CfgMgr32.DEVINST;
      tries :  Integer;
    begin
        szRootPath := PChar(DriveLetter+':\');
        szDevicePath := PChar(DriveLetter+':');
      szVolumeAccessPath := PChar(Format('\\.\%s:',[DriveLetter]));
    
      DeviceNumber:=-1;
        // open the storage volume
      hVolume := CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
        if (hVolume <> INVALID_HANDLE_VALUE) then
       try
        //get the volume's device number
        dwBytesReturned := 0;
        res := DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, SizeOf(sdn), dwBytesReturned, nil);
        if res then
          DeviceNumber := sdn.DeviceNumber;
       finally
         CloseHandle(hVolume);
       end;
       if DeviceNumber=-1 then exit;
    
        // get the drive type which is required to match the device numbers correctely
        DriveType := GetDriveType(szRootPath);
    
        // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
        QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
    
        // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
        DevInst := GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);
    
        if ( DevInst = 0 ) then
       exit;
    
      VetoType := PNP_VetoTypeUnknown;
        bSuccess := false;
    
        // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
        DevInstParent := 0;
        resCM := CM_Get_Parent(DevInstParent, DevInst, 0);
    
        for tries:=0 to 3 do // sometimes we need some tries...
      begin
            FillChar(VetoName[0], SizeOf(VetoName), 0);
    
            // CM_Query_And_Remove_SubTree doesn't work for restricted users
            //resCM = CM_Query_And_Remove_SubTree(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
            //resCM = CM_Query_And_Remove_SubTree(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART);  // with messagebox (W2K, Vista) or balloon (XP)
    
            resCM := CM_Request_Device_Eject(DevInstParent, @VetoType, @VetoName[0], Length(VetoName), 0);
            resCM := CM_Request_Device_Eject(DevInstParent,nil, nil, 0, 0); // optional -> shows messagebox (W2K, Vista) or balloon (XP)
    
            bSuccess := (resCM=CR_SUCCESS) and (VetoType=PNP_VetoTypeUnknown);
            if ( bSuccess )  then
                break;
    
            Sleep(500); // required to give the next tries a chance!
        end;
    
        if ( bSuccess ) then
            Writeln('Success')
      else
          Writeln('Failed');
    end;
    
    begin
      try
        LoadSetupApi;
        LoadConfigManagerApi;
        EjectUSB('F');
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    
    end.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-31
      • 1970-01-01
      • 2010-09-15
      • 2021-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多