Как я могу обнаружить и удалить флеш-диск USB программно с помощью delphi?
Я видел некоторые примеры на этом веб-сайте, но у них нет четкого объяснения, как это сделать!
Примеры действительно помогут!
Как я могу обнаружить и удалить флеш-диск USB программно с помощью delphi?
Я видел некоторые примеры на этом веб-сайте, но у них нет четкого объяснения, как это сделать!
Примеры действительно помогут!
Это быстрый и грязный перевод этого примера кода для удаления диска с сайта 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;
Ключ для удаления 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.
Это не выталкивает диск, но он очищает буферы накопителей и делает его безопасным для удаления. Он требует административных прав под 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()));