Как я могу с легкостью удалить флеш-диск USB с помощью delphi?

Как я могу обнаружить и удалить флеш-диск USB программно с помощью delphi?

Я видел некоторые примеры на этом веб-сайте, но у них нет четкого объяснения, как это сделать!

Примеры действительно помогут!

Ответ 1

Это быстрый и грязный перевод этого примера кода для удаления диска с сайта support.microsoft.com. Однако он работает только для пользователей с правами администратора в моей системе.

Для получения дополнительной информации о работе с USB-устройствами в целом выполните ссылку этот ответ concept03.

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;

Ответ 2

Ключ для удаления USB-диска используется CM_Request_Device_Eject,

Отметьте это примерное приложение delphi, основанное на этой статье How to Prepare a USB Drive for Safe Removal и которое использует JEDI API Library & 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 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 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 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.

Ответ 3

Это не выталкивает диск, но он очищает буферы накопителей и делает его безопасным для удаления. Он требует административных прав под Vista и выше (и XP, если работает как пользователь с ограниченными правами, IIRC). Вероятно, у него должна быть попытка. Наконец, чтобы убедиться, что вызов CloseHandle вызван; Я оставляю это как упражнение для читателя, поскольку код formattig здесь плотный, без горизонтальной прокрутки.: -)

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()));