检测何时使用 Delphi 在 Windows 中安装卷
Detecting when a volume is mounted in Windows with Delphi
我正在尝试了解 MSDN 上有关设备事件的文档
以及如何在安装卷时触发通知。
我已使用以下 post 中提供的信息为 USB 设备执行此操作:detect usb drive/device using delphi
以及在互联网上找到的其他信息,
但我注意到直接挂载卷会更容易检测到。
所以我的问题是:如何在我的 Delphi 应用程序中实现设备事件处理?
我正在查看以下文档:https://msdn.microsoft.com/en-us/library/windows/desktop/aa363217(v=vs.85).aspx
但我真的不知道怎么弄,运行。
到目前为止,我已经尝试了以下代码,它编译正确,
但什么也没发生,请把我推向正确的方向:
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
end;
PDevBroadcastHandle = ^DEV_BROADCAST_HANDLE;
DEV_BROADCAST_HANDLE = packed record
dbch_size : DWORD ;
dbch_devicetype : DWORD ;
dbch_reserved : DWORD ;
dbch_handle : THandle ;
dbch_hdevnotify : HDEVNOTIFY ;
dbch_eventguid : TGUID ;
dbch_nameoffset : LongInt ;
dbch_data : byte ;
end;
...
procedure WMDeviceChange(var Msg: TMessage);
const
DBT_DEVTYP_HANDLE = [=12=]06;
GUID_IO_VOLUME_MOUNT: TGUID = '{B5804878-1A96-11D2-8FFD-00A0C9A06D32}';
...
function TForm1.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := 0;
dbv.dbch_hdevnotify := nil;
dbv.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
dbv.dbch_nameoffset := 0;
dbv.dbch_data := 0;
r := RegisterDeviceNotification(FWindowHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
end;
procedure TForm1.WMDeviceChange(var Msg: TMessage);
var
VData: PDevBroadcastHandle;
begin
ShowMessage('Hello!');
end;
您必须像这样声明您的 WMDeviceChange
方法才能接收消息:
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;
此外,由于您的 WMDeviceChange
方法是表单的一部分,您应该使用表单 window Handle
来注册消息。
r := RegisterDeviceNotification(Handle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
由于 Handle
可以在 Form 的生命周期内重新创建,因此您应该覆盖 Form 的 CreateWnd
方法并在那里添加注册。
或者更好的是,您可以将功能封装在另一个 class:
TDeviceDetector = class
protected
fHandle: THandle;
procedure WndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
function RegisterThis: Boolean;
end;
constructor TDeviceDetector.Create;
begin
inherited;
fHandle := AllocateHWnd(WndProc);
end;
destructor TDeviceDetector.Destroy;
begin
DeallocateHWnd(fHandle);
inherited;
end;
function TDeviceDetector.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := 0;
dbv.dbch_hdevnotify := nil;
dbv.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
dbv.dbch_nameoffset := 0;
dbv.dbch_data := 0;
r := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
end;
procedure TDeviceDetector.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_DEVICECHANGE then
begin
ShowMessage('Hello!');
end
else Message.Result := DefWindowProc(FHandle, Message.Msg, Message.wParam, Message.lParam); // Default Message Handler
end;
到目前为止,你的问题还挺多的。这是我能看到的。
收件人不正确
您正在将 window 句柄传递给 RegisterDeviceNotification
。但是,您的 window 句柄是否实现了 WM_DEVICECHANGE
的消息处理程序还远未明确。我建议使用 AllocateHWnd
获取 window 句柄,并在您提供给 AllocateHWnd
.
的 window 过程中处理 WM_DEVICECHANGE
调用UnregisterDeviceNotification失败
RegisterDeviceNotification
的文档说:
Device notification handles returned by RegisterDeviceNotification must be closed by calling the UnregisterDeviceNotification function when they are no longer needed.
你没有做到这一点。当您不想再收到通知时,您必须保留由 RegisterDeviceNotification
编辑的句柄 return 并将其传递给 UnregisterDeviceNotification
。
记录打包错误
您声明了压缩记录。这是个错误。由于我不清楚的原因,Delphi 开发人员打包他们的记录似乎是一个普遍的错误。打包导致性能不佳。更糟糕的是,在对对齐的记录执行互操作时,打包只会导致记录的布局不正确。这些记录没有打包。
此外,我认为您的记录不应包含 dbch_data
成员。这仅用于 DBT_CUSTOMEVENT
,我认为这不适用于您。我会这样声明记录:
type
DEV_BROADCAST_HANDLE = record
dbch_size : DWORD ;
dbch_devicetype : DWORD ;
dbch_reserved : DWORD ;
dbch_handle : THandle ;
dbch_hdevnotify : HDEVNOTIFY ;
dbch_eventguid : TGUID ;
dbch_nameoffset : LONG ;
end;
弱错误检查
您确实检查了对 RegisterDeviceNotification
的调用的 return 值。那挺好的。但是,如果该调用失败,则您不会调用 GetLastError
来找出原因,如文档中所述。我会这样写电话:
var
DevNotificationHandle: HDEVNOTIFY;
....
DevNotificationHandle := RegisterDeviceNotification(...);
Win32Check(DevNotificationHandle <> 0);
这样,任何错误都将转换为异常,并带有代表 Win32 错误代码的文本错误消息。
dbch_devicetype
的值可能不正确
我认为你应该通过 DBT_DEVTYP_DEVICEINTERFACE
而不是 DBT_DEVTYP_HANDLE
。如果您切换到 DBT_DEVTYP_DEVICEINTERFACE
并解决我上面提出的所有问题,那么您将收到通知。例如:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FWindow: HWND;
FDevNotificationHandle: HDEVNOTIFY;
procedure WndMethod(var Message: TMessage);
function HandleDeviceChange(Event: DWORD; Data: Pointer): Boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
DEV_BROADCAST_HANDLE = record
dbch_size: DWORD;
dbch_devicetype: DWORD;
dbch_reserved: DWORD;
dbch_handle: THandle;
dbch_hdevnotify: HDEVNOTIFY;
dbch_eventguid: TGUID;
dbch_nameoffset: LONG;
end;
const
DBT_DEVTYP_DEVICEINTERFACE = [=12=]05;
GUID_IO_VOLUME_MOUNT: TGUID = '{B5804878-1A96-11D2-8FFD-00A0C9A06D32}';
procedure TForm1.FormCreate(Sender: TObject);
var
dbh: DEV_BROADCAST_HANDLE;
begin
FWindow := AllocateHWnd(WndMethod);
dbh := Default(DEV_BROADCAST_HANDLE);
dbh.dbch_size := SizeOf(dbh);
dbh.dbch_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
dbh.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
FDevNotificationHandle := RegisterDeviceNotification(FWindow, @dbh,
DEVICE_NOTIFY_WINDOW_HANDLE);
Win32Check(FDevNotificationHandle <> nil);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FDevNotificationHandle <> nil then
Win32Check(UnregisterDeviceNotification(FDevNotificationHandle));
DeallocateHWnd(FWindow);
end;
procedure TForm1.WndMethod(var Message: TMessage);
begin
case Message.Msg of
WM_DEVICECHANGE:
Message.Result := ord(HandleDeviceChange(Message.WParam,
Pointer(Message.LParam)));
else
Message.Result := DefWindowProc(FWindow, Message.Msg, Message.WParam,
Message.LParam);
end;
end;
function TForm1.HandleDeviceChange(Event: DWORD; Data: Pointer): Boolean;
begin
Memo1.Lines.Add(Format('%4x', [Event]));
Result := True;
end;
end.
请注意,一组默认通知会广播到顶级 windows。所以你甚至可能不需要注册,因为我相信音量变化是默认设置的一部分。
编辑:在论坛上找到了一个关于此的组件。它称为 TSHChangeNotify,由 Elliott Shevinm 于 2000 年编写 (!)
See the forum thread here。它包含源代码和修复程序。
还有一篇由 Zarko Gajic 撰写的文章解释了它是如何工作的
found here
仅使用 VCL 时,它在 Delphi XE7 on Windows 8.1 中工作得很好。
更新: 我修改了代码,现在它可以在带有 Firemonkey 的 Delphi XE7 上运行。
The updated code can be found here.
我的设置方式是将 TSHChangeNotify.HardDriveOnly
设置为 FALSE
并将 TSHChangeNotify.Execute
放入主窗体 OnCreate
过程中。
感谢 Dalija 和 David,我收到了一些有用的信息和代码。
然而他们的回答并没有解决我的问题。
就像大卫在其中一条评论中所说的那样,他只是想 "push me in the right direction" 正如我自己在我的问题中所建议的那样。很公平。
我开始谷歌搜索 "guid_io_volume_mount c++" 看看我是否能在其他地方找到一些我可以移植的可用代码。
我看到这个论坛 post:http://www.mofeel.net/957-microsoft-public-vc-language/2343.aspx
OP 在他的代码中提到应该使用 CreateFileA,以便基本上 "monitor" 只要挂载点发生变化。
挂载点好像是盘符,比如C:、D:、E:等...
所以基本上发生的事情是使用以下代码
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := CreateFileA('\.\C:', GENERIC_READ, FILE_SHARE_READ+FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, 0);
dbv.dbch_hdevnotify := 0;
dbv.dbch_nameoffset := 0;
r := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
我们让 OS 知道每当 "C:" 改变了它的安装状态 (mount/dismount),OS 就会向我们的 WndProc 消息捕捉器发送一条消息。
下面提供了我的完整源代码,仍然有一些问题,但到目前为止它提供了一个概念。
它至少可以检测到何时安装了指定的卷。
当您右键单击一个卷并 select "Eject".
时检测到卸载
现在请记住,这段代码不仅仅可以检测挂载点何时更改,this MSDN article has all the GUID's you need to do some pretty neat stuff.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.dialogs;
type
TDeviceDetector = class
protected
fHandle: THandle;
fLogger: TMemo;
fOnCDROM_EXCLUSIVE_LOCK ,
fOnCDROM_EXCLUSIVE_UNLOCK ,
fOnDEVICE_BECOMING_READY ,
fOnDEVICE_EXTERNAL_REQUEST ,
fOnMEDIA_ARRIVAL ,
fOnMEDIA_EJECT_REQUEST ,
fOnMEDIA_REMOVAL ,
fOnVOLUME_CHANGE ,
fOnVOLUME_CHANGE_SIZE ,
fOnVOLUME_DISMOUNT ,
fOnVOLUME_DISMOUNT_FAILED ,
fOnVOLUME_FVE_STATUS_CHANGE ,
fOnVOLUME_LOCK ,
fOnVOLUME_LOCK_FAILED ,
fOnVOLUME_MOUNT ,
fOnVOLUME_NAME_CHANGE ,
fOnVOLUME_NEED_CHKDSK ,
fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE ,
fOnVOLUME_PREPARING_EJECT ,
fOnVOLUME_UNIQUE_ID_CHANGE ,
fOnVOLUME_UNLOCK ,
fOnVOLUME_WEARING_OUT : TNotifyEvent;
procedure WndProc(var Message: TMessage);
procedure Log(AStr: string);
public
constructor Create;
destructor Destroy; override;
function RegisterThis: Boolean;
property Logger: TMemo read fLogger write fLogger;
published
property OnCDROM_EXCLUSIVE_LOCK : TNotifyEvent read fOnCDROM_EXCLUSIVE_LOCK write fOnCDROM_EXCLUSIVE_LOCK ;
property OnCDROM_EXCLUSIVE_UNLOCK : TNotifyEvent read fOnCDROM_EXCLUSIVE_UNLOCK write fOnCDROM_EXCLUSIVE_UNLOCK ;
property OnDEVICE_BECOMING_READY : TNotifyEvent read fOnDEVICE_BECOMING_READY write fOnDEVICE_BECOMING_READY ;
property OnDEVICE_EXTERNAL_REQUEST : TNotifyEvent read fOnDEVICE_EXTERNAL_REQUEST write fOnDEVICE_EXTERNAL_REQUEST ;
property OnMEDIA_ARRIVAL : TNotifyEvent read fOnMEDIA_ARRIVAL write fOnMEDIA_ARRIVAL ;
property OnMEDIA_EJECT_REQUEST : TNotifyEvent read fOnMEDIA_EJECT_REQUEST write fOnMEDIA_EJECT_REQUEST ;
property OnMEDIA_REMOVAL : TNotifyEvent read fOnMEDIA_REMOVAL write fOnMEDIA_REMOVAL ;
property OnVOLUME_CHANGE : TNotifyEvent read fOnVOLUME_CHANGE write fOnVOLUME_CHANGE ;
property OnVOLUME_CHANGE_SIZE : TNotifyEvent read fOnVOLUME_CHANGE_SIZE write fOnVOLUME_CHANGE_SIZE ;
property OnVOLUME_DISMOUNT : TNotifyEvent read fOnVOLUME_DISMOUNT write fOnVOLUME_DISMOUNT ;
property OnVOLUME_DISMOUNT_FAILED : TNotifyEvent read fOnVOLUME_DISMOUNT_FAILED write fOnVOLUME_DISMOUNT_FAILED ;
property OnVOLUME_FVE_STATUS_CHANGE : TNotifyEvent read fOnVOLUME_FVE_STATUS_CHANGE write fOnVOLUME_FVE_STATUS_CHANGE ;
property OnVOLUME_LOCK : TNotifyEvent read fOnVOLUME_LOCK write fOnVOLUME_LOCK ;
property OnVOLUME_LOCK_FAILED : TNotifyEvent read fOnVOLUME_LOCK_FAILED write fOnVOLUME_LOCK_FAILED ;
property OnVOLUME_MOUNT : TNotifyEvent read fOnVOLUME_MOUNT write fOnVOLUME_MOUNT ;
property OnVOLUME_NAME_CHANGE : TNotifyEvent read fOnVOLUME_NAME_CHANGE write fOnVOLUME_NAME_CHANGE ;
property OnVOLUME_NEED_CHKDSK : TNotifyEvent read fOnVOLUME_NEED_CHKDSK write fOnVOLUME_NEED_CHKDSK ;
property OnVOLUME_PHYSICAL_CONFIGURATION_CHANGE : TNotifyEvent read fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE write fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE;
property OnVOLUME_PREPARING_EJECT : TNotifyEvent read fOnVOLUME_PREPARING_EJECT write fOnVOLUME_PREPARING_EJECT ;
property OnVOLUME_UNIQUE_ID_CHANGE : TNotifyEvent read fOnVOLUME_UNIQUE_ID_CHANGE write fOnVOLUME_UNIQUE_ID_CHANGE ;
property OnVOLUME_UNLOCK : TNotifyEvent read fOnVOLUME_UNLOCK write fOnVOLUME_UNLOCK ;
property OnVOLUME_WEARING_OUT : TNotifyEvent read fOnVOLUME_WEARING_OUT write fOnVOLUME_WEARING_OUT ;
end;
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
end;
var
Form1: TForm1;
dd: TDeviceDetector;
implementation
{$R *.dfm}
type
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
end;
PDevBroadcastHandle = ^DEV_BROADCAST_HANDLE;
DEV_BROADCAST_HANDLE = record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
dbch_handle : THandle;
dbch_hdevnotify : HDEVNOTIFY;
dbch_eventguid : TGUID;
dbch_nameoffset : LONG;
dbch_data: array [0..0] of Byte;
end;
const
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = [=11=]000004;
DBT_CUSTOMEVENT = 06;
DBT_DEVTYP_HANDLE = [=11=]06;
GUID_IO_CDROM_EXCLUSIVE_LOCK : TGUID = '{bc56c139-7a10-47ee-a294-4c6a38f0149a}';
GUID_IO_CDROM_EXCLUSIVE_UNLOCK : TGUID = '{a3b6d27d-5e35-4885-81e5-ee18c00ed779}';
GUID_IO_DEVICE_BECOMING_READY : TGUID = '{d07433f0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_DEVICE_EXTERNAL_REQUEST : TGUID = '{d07433d0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_ARRIVAL : TGUID = '{d07433c0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_EJECT_REQUEST : TGUID = '{d07433d1-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_REMOVAL : TGUID = '{d07433c1-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_VOLUME_CHANGE : TGUID = '{7373654a-812a-11d0-bec7-08002be2092f}';
GUID_IO_VOLUME_CHANGE_SIZE : TGUID = '{3a1625be-ad03-49f1-8ef8-6bbac182d1fd}';
GUID_IO_VOLUME_DISMOUNT : TGUID = '{d16a55e8-1059-11d2-8ffd-00a0c9a06d32}';
GUID_IO_VOLUME_DISMOUNT_FAILED : TGUID = '{E3C5B178-105D-11D2-8FFD-00A0C9A06D32}';
GUID_IO_VOLUME_FVE_STATUS_CHANGE : TGUID = '{062998b2-ee1f-4b6a-b857-e76cbbe9a6da}';
GUID_IO_VOLUME_LOCK : TGUID = '{50708874-c9af-11d1-8fef-00a0c9a06d32}';
GUID_IO_VOLUME_LOCK_FAILED : TGUID = '{ae2eed10-0ba8-11d2-8ffb-00a0c9a06d32}';
GUID_IO_VOLUME_MOUNT : TGUID = '{b5804878-1a96-11d2-8ffd-00a0c9a06d32}';
GUID_IO_VOLUME_NAME_CHANGE : TGUID = '{2de97f83-4c06-11d2-a532-00609713055a}';
GUID_IO_VOLUME_NEED_CHKDSK : TGUID = '{799a0960-0a0b-4e03-ad88-2fa7c6ce748a}';
GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE : TGUID = '{2de97f84-4c06-11d2-a532-00609713055a}';
GUID_IO_VOLUME_PREPARING_EJECT : TGUID = '{c79eb16e-0dac-4e7a-a86c-b25ceeaa88f6}';
GUID_IO_VOLUME_UNIQUE_ID_CHANGE : TGUID = '{af39da42-6622-41f5-970b-139d092fa3d9}';
GUID_IO_VOLUME_UNLOCK : TGUID = '{9a8c3d68-d0cb-11d1-8fef-00a0c9a06d32}';
GUID_IO_VOLUME_WEARING_OUT : TGUID = '{873113ca-1486-4508-82ac-c3b2e5297aaa}';
function WDE_GUID_To_String(AGUID: TGUID): string; //WDE stands for Windows Device Events
begin
if AGUID = GUID_IO_CDROM_EXCLUSIVE_LOCK then result := 'GUID_IO_CDROM_EXCLUSIVE_LOCK' else
if AGUID = GUID_IO_CDROM_EXCLUSIVE_UNLOCK then result := 'GUID_IO_CDROM_EXCLUSIVE_UNLOCK' else
if AGUID = GUID_IO_DEVICE_BECOMING_READY then result := 'GUID_IO_DEVICE_BECOMING_READY' else
if AGUID = GUID_IO_DEVICE_EXTERNAL_REQUEST then result := 'GUID_IO_DEVICE_BECOMING_READY' else
if AGUID = GUID_IO_MEDIA_ARRIVAL then result := 'GUID_IO_MEDIA_ARRIVAL' else
if AGUID = GUID_IO_MEDIA_EJECT_REQUEST then result := 'GUID_IO_MEDIA_EJECT_REQUEST' else
if AGUID = GUID_IO_MEDIA_REMOVAL then result := 'GUID_IO_MEDIA_REMOVAL' else
if AGUID = GUID_IO_VOLUME_CHANGE then result := 'GUID_IO_VOLUME_CHANGE' else
if AGUID = GUID_IO_VOLUME_CHANGE_SIZE then result := 'GUID_IO_VOLUME_CHANGE_SIZE' else
if AGUID = GUID_IO_VOLUME_DISMOUNT then result := 'GUID_IO_VOLUME_DISMOUNT' else
if AGUID = GUID_IO_VOLUME_DISMOUNT_FAILED then result := 'GUID_IO_VOLUME_DISMOUNT_FAILED' else
if AGUID = GUID_IO_VOLUME_FVE_STATUS_CHANGE then result := 'GUID_IO_VOLUME_FVE_STATUS_CHANGE' else
if AGUID = GUID_IO_VOLUME_LOCK then result := 'GUID_IO_VOLUME_LOCK' else
if AGUID = GUID_IO_VOLUME_LOCK_FAILED then result := 'GUID_IO_VOLUME_LOCK_FAILED' else
if AGUID = GUID_IO_VOLUME_MOUNT then result := 'GUID_IO_VOLUME_MOUNT' else
if AGUID = GUID_IO_VOLUME_NAME_CHANGE then result := 'GUID_IO_VOLUME_NAME_CHANGE' else
if AGUID = GUID_IO_VOLUME_NEED_CHKDSK then result := 'GUID_IO_VOLUME_NEED_CHKDSK' else
if AGUID = GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE then result := 'GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE' else
if AGUID = GUID_IO_VOLUME_PREPARING_EJECT then result := 'GUID_IO_VOLUME_PREPARING_EJECT' else
if AGUID = GUID_IO_VOLUME_UNIQUE_ID_CHANGE then result := 'GUID_IO_VOLUME_UNIQUE_ID_CHANGE' else
if AGUID = GUID_IO_VOLUME_UNLOCK then result := 'GUID_IO_VOLUME_UNLOCK' else
if AGUID = GUID_IO_VOLUME_WEARING_OUT then result := 'GUID_IO_VOLUME_WEARING_OUT';
end;
constructor TDeviceDetector.Create;
begin
inherited;
fHandle := AllocateHWnd(WndProc);
end;
destructor TDeviceDetector.Destroy;
begin
DeallocateHWnd(fHandle);
inherited;
end;
function TDeviceDetector.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := CreateFileA('\.\E:', GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, 0);
dbv.dbch_hdevnotify := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);;
dbv.dbch_nameoffset := 0;
if Assigned(dbv.dbch_hdevnotify) then Result := True;
end;
procedure TDeviceDetector.WndProc(var Message: TMessage);
var data: PDevBroadcastHdr;
data_H: PDevBroadcastHandle;
begin
if Message.wParam = DBT_CUSTOMEVENT then //according to MSDN, DEV_BROADCAST_HANDLE structure is treated as a custom event.
begin
Data := PDevBroadcastHdr(Message.LParam); //we need to treat this custom evend a DEV_BROADCAST_HDR structure first...
if Data^.dbch_devicetype = DBT_DEVTYP_HANDLE then //then we check if the device type is DBT_DEVTYP_HANDLE
begin
data_H := PDevBroadcastHandle(Message.lParam); //if the device type is DBT_DEVTYP_HANDLE, we treat the custom event as a DEV_BROADCAST_HANDLE structure
//final step is to see what GUID the event of the structure DEV_BROADCAST_HANDLE has
Log(WDE_GUID_To_String(data_H^.dbch_eventguid));
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_CDROM_EXCLUSIVE_LOCK) = true then if assigned(fOnCDROM_EXCLUSIVE_LOCK) then fOnCDROM_EXCLUSIVE_LOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_CDROM_EXCLUSIVE_UNLOCK) = true then if assigned(fOnCDROM_EXCLUSIVE_UNLOCK) then fOnCDROM_EXCLUSIVE_UNLOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_DEVICE_BECOMING_READY) = true then if assigned(fOnDEVICE_BECOMING_READY) then fOnDEVICE_BECOMING_READY(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_DEVICE_EXTERNAL_REQUEST) = true then if assigned(fOnDEVICE_EXTERNAL_REQUEST) then fOnDEVICE_EXTERNAL_REQUEST(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_ARRIVAL) = true then if assigned(fOnMEDIA_ARRIVAL) then fOnMEDIA_ARRIVAL(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_EJECT_REQUEST) = true then if assigned(fOnMEDIA_EJECT_REQUEST) then fOnMEDIA_EJECT_REQUEST(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_REMOVAL) = true then if assigned(fOnMEDIA_REMOVAL) then fOnMEDIA_REMOVAL(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_CHANGE) = true then if assigned(fOnVOLUME_CHANGE) then fOnVOLUME_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_CHANGE_SIZE) = true then if assigned(fOnVOLUME_CHANGE_SIZE) then fOnVOLUME_CHANGE_SIZE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_DISMOUNT) = true then if assigned(fOnVOLUME_DISMOUNT) then fOnVOLUME_DISMOUNT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_DISMOUNT_FAILED) = true then if assigned(fOnVOLUME_DISMOUNT_FAILED) then fOnVOLUME_DISMOUNT_FAILED(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_FVE_STATUS_CHANGE) = true then if assigned(fOnVOLUME_FVE_STATUS_CHANGE) then fOnVOLUME_FVE_STATUS_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_LOCK) = true then if assigned(fOnVOLUME_LOCK) then fOnVOLUME_LOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_LOCK_FAILED) = true then if assigned(fOnVOLUME_LOCK_FAILED) then fOnVOLUME_LOCK_FAILED(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_MOUNT) = true then if assigned(fOnVOLUME_MOUNT) then fOnVOLUME_MOUNT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_NAME_CHANGE) = true then if assigned(fOnVOLUME_NAME_CHANGE) then fOnVOLUME_NAME_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_NEED_CHKDSK) = true then if assigned(fOnVOLUME_NEED_CHKDSK) then fOnVOLUME_NEED_CHKDSK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE) = true then if assigned(fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE) then fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_PREPARING_EJECT) = true then if assigned(fOnVOLUME_PREPARING_EJECT) then fOnVOLUME_PREPARING_EJECT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_UNIQUE_ID_CHANGE) = true then if assigned(fOnVOLUME_UNIQUE_ID_CHANGE) then fOnVOLUME_UNIQUE_ID_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_UNLOCK) = true then if assigned(fOnVOLUME_UNLOCK) then fOnVOLUME_UNLOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_WEARING_OUT) = true then if assigned(fOnVOLUME_WEARING_OUT) then fOnVOLUME_WEARING_OUT(self);
end;
end;
end;
procedure TDeviceDetector.Log(AStr: string);
begin
fLogger.Lines.Add(AStr);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
dd := TDeviceDetector.Create;
dd.Logger := Memo1;
if dd.RegisterThis = true then Memo1.Lines.Add('Registered!') else Memo1.Lines.Add('Failed to register!');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
dd.free;
end;
end.
我正在尝试了解 MSDN 上有关设备事件的文档 以及如何在安装卷时触发通知。
我已使用以下 post 中提供的信息为 USB 设备执行此操作:detect usb drive/device using delphi
以及在互联网上找到的其他信息, 但我注意到直接挂载卷会更容易检测到。
所以我的问题是:如何在我的 Delphi 应用程序中实现设备事件处理?
我正在查看以下文档:https://msdn.microsoft.com/en-us/library/windows/desktop/aa363217(v=vs.85).aspx
但我真的不知道怎么弄,运行。
到目前为止,我已经尝试了以下代码,它编译正确, 但什么也没发生,请把我推向正确的方向:
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
end;
PDevBroadcastHandle = ^DEV_BROADCAST_HANDLE;
DEV_BROADCAST_HANDLE = packed record
dbch_size : DWORD ;
dbch_devicetype : DWORD ;
dbch_reserved : DWORD ;
dbch_handle : THandle ;
dbch_hdevnotify : HDEVNOTIFY ;
dbch_eventguid : TGUID ;
dbch_nameoffset : LongInt ;
dbch_data : byte ;
end;
...
procedure WMDeviceChange(var Msg: TMessage);
const
DBT_DEVTYP_HANDLE = [=12=]06;
GUID_IO_VOLUME_MOUNT: TGUID = '{B5804878-1A96-11D2-8FFD-00A0C9A06D32}';
...
function TForm1.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := 0;
dbv.dbch_hdevnotify := nil;
dbv.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
dbv.dbch_nameoffset := 0;
dbv.dbch_data := 0;
r := RegisterDeviceNotification(FWindowHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
end;
procedure TForm1.WMDeviceChange(var Msg: TMessage);
var
VData: PDevBroadcastHandle;
begin
ShowMessage('Hello!');
end;
您必须像这样声明您的 WMDeviceChange
方法才能接收消息:
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;
此外,由于您的 WMDeviceChange
方法是表单的一部分,您应该使用表单 window Handle
来注册消息。
r := RegisterDeviceNotification(Handle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
由于 Handle
可以在 Form 的生命周期内重新创建,因此您应该覆盖 Form 的 CreateWnd
方法并在那里添加注册。
或者更好的是,您可以将功能封装在另一个 class:
TDeviceDetector = class
protected
fHandle: THandle;
procedure WndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
function RegisterThis: Boolean;
end;
constructor TDeviceDetector.Create;
begin
inherited;
fHandle := AllocateHWnd(WndProc);
end;
destructor TDeviceDetector.Destroy;
begin
DeallocateHWnd(fHandle);
inherited;
end;
function TDeviceDetector.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := 0;
dbv.dbch_hdevnotify := nil;
dbv.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
dbv.dbch_nameoffset := 0;
dbv.dbch_data := 0;
r := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
end;
procedure TDeviceDetector.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_DEVICECHANGE then
begin
ShowMessage('Hello!');
end
else Message.Result := DefWindowProc(FHandle, Message.Msg, Message.wParam, Message.lParam); // Default Message Handler
end;
到目前为止,你的问题还挺多的。这是我能看到的。
收件人不正确
您正在将 window 句柄传递给 RegisterDeviceNotification
。但是,您的 window 句柄是否实现了 WM_DEVICECHANGE
的消息处理程序还远未明确。我建议使用 AllocateHWnd
获取 window 句柄,并在您提供给 AllocateHWnd
.
WM_DEVICECHANGE
调用UnregisterDeviceNotification失败
RegisterDeviceNotification
的文档说:
Device notification handles returned by RegisterDeviceNotification must be closed by calling the UnregisterDeviceNotification function when they are no longer needed.
你没有做到这一点。当您不想再收到通知时,您必须保留由 RegisterDeviceNotification
编辑的句柄 return 并将其传递给 UnregisterDeviceNotification
。
记录打包错误
您声明了压缩记录。这是个错误。由于我不清楚的原因,Delphi 开发人员打包他们的记录似乎是一个普遍的错误。打包导致性能不佳。更糟糕的是,在对对齐的记录执行互操作时,打包只会导致记录的布局不正确。这些记录没有打包。
此外,我认为您的记录不应包含 dbch_data
成员。这仅用于 DBT_CUSTOMEVENT
,我认为这不适用于您。我会这样声明记录:
type
DEV_BROADCAST_HANDLE = record
dbch_size : DWORD ;
dbch_devicetype : DWORD ;
dbch_reserved : DWORD ;
dbch_handle : THandle ;
dbch_hdevnotify : HDEVNOTIFY ;
dbch_eventguid : TGUID ;
dbch_nameoffset : LONG ;
end;
弱错误检查
您确实检查了对 RegisterDeviceNotification
的调用的 return 值。那挺好的。但是,如果该调用失败,则您不会调用 GetLastError
来找出原因,如文档中所述。我会这样写电话:
var
DevNotificationHandle: HDEVNOTIFY;
....
DevNotificationHandle := RegisterDeviceNotification(...);
Win32Check(DevNotificationHandle <> 0);
这样,任何错误都将转换为异常,并带有代表 Win32 错误代码的文本错误消息。
dbch_devicetype
我认为你应该通过 DBT_DEVTYP_DEVICEINTERFACE
而不是 DBT_DEVTYP_HANDLE
。如果您切换到 DBT_DEVTYP_DEVICEINTERFACE
并解决我上面提出的所有问题,那么您将收到通知。例如:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FWindow: HWND;
FDevNotificationHandle: HDEVNOTIFY;
procedure WndMethod(var Message: TMessage);
function HandleDeviceChange(Event: DWORD; Data: Pointer): Boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
DEV_BROADCAST_HANDLE = record
dbch_size: DWORD;
dbch_devicetype: DWORD;
dbch_reserved: DWORD;
dbch_handle: THandle;
dbch_hdevnotify: HDEVNOTIFY;
dbch_eventguid: TGUID;
dbch_nameoffset: LONG;
end;
const
DBT_DEVTYP_DEVICEINTERFACE = [=12=]05;
GUID_IO_VOLUME_MOUNT: TGUID = '{B5804878-1A96-11D2-8FFD-00A0C9A06D32}';
procedure TForm1.FormCreate(Sender: TObject);
var
dbh: DEV_BROADCAST_HANDLE;
begin
FWindow := AllocateHWnd(WndMethod);
dbh := Default(DEV_BROADCAST_HANDLE);
dbh.dbch_size := SizeOf(dbh);
dbh.dbch_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
dbh.dbch_eventguid := GUID_IO_VOLUME_MOUNT;
FDevNotificationHandle := RegisterDeviceNotification(FWindow, @dbh,
DEVICE_NOTIFY_WINDOW_HANDLE);
Win32Check(FDevNotificationHandle <> nil);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FDevNotificationHandle <> nil then
Win32Check(UnregisterDeviceNotification(FDevNotificationHandle));
DeallocateHWnd(FWindow);
end;
procedure TForm1.WndMethod(var Message: TMessage);
begin
case Message.Msg of
WM_DEVICECHANGE:
Message.Result := ord(HandleDeviceChange(Message.WParam,
Pointer(Message.LParam)));
else
Message.Result := DefWindowProc(FWindow, Message.Msg, Message.WParam,
Message.LParam);
end;
end;
function TForm1.HandleDeviceChange(Event: DWORD; Data: Pointer): Boolean;
begin
Memo1.Lines.Add(Format('%4x', [Event]));
Result := True;
end;
end.
请注意,一组默认通知会广播到顶级 windows。所以你甚至可能不需要注册,因为我相信音量变化是默认设置的一部分。
编辑:在论坛上找到了一个关于此的组件。它称为 TSHChangeNotify,由 Elliott Shevinm 于 2000 年编写 (!)
See the forum thread here。它包含源代码和修复程序。 还有一篇由 Zarko Gajic 撰写的文章解释了它是如何工作的 found here
仅使用 VCL 时,它在 Delphi XE7 on Windows 8.1 中工作得很好。
更新: 我修改了代码,现在它可以在带有 Firemonkey 的 Delphi XE7 上运行。 The updated code can be found here.
我的设置方式是将 TSHChangeNotify.HardDriveOnly
设置为 FALSE
并将 TSHChangeNotify.Execute
放入主窗体 OnCreate
过程中。
感谢 Dalija 和 David,我收到了一些有用的信息和代码。
然而他们的回答并没有解决我的问题。
就像大卫在其中一条评论中所说的那样,他只是想 "push me in the right direction" 正如我自己在我的问题中所建议的那样。很公平。
我开始谷歌搜索 "guid_io_volume_mount c++" 看看我是否能在其他地方找到一些我可以移植的可用代码。
我看到这个论坛 post:http://www.mofeel.net/957-microsoft-public-vc-language/2343.aspx
OP 在他的代码中提到应该使用 CreateFileA,以便基本上 "monitor" 只要挂载点发生变化。
挂载点好像是盘符,比如C:、D:、E:等...
所以基本上发生的事情是使用以下代码
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := CreateFileA('\.\C:', GENERIC_READ, FILE_SHARE_READ+FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, 0);
dbv.dbch_hdevnotify := 0;
dbv.dbch_nameoffset := 0;
r := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);
if Assigned(r) then Result := True;
我们让 OS 知道每当 "C:" 改变了它的安装状态 (mount/dismount),OS 就会向我们的 WndProc 消息捕捉器发送一条消息。
下面提供了我的完整源代码,仍然有一些问题,但到目前为止它提供了一个概念。
它至少可以检测到何时安装了指定的卷。 当您右键单击一个卷并 select "Eject".
时检测到卸载现在请记住,这段代码不仅仅可以检测挂载点何时更改,this MSDN article has all the GUID's you need to do some pretty neat stuff.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.dialogs;
type
TDeviceDetector = class
protected
fHandle: THandle;
fLogger: TMemo;
fOnCDROM_EXCLUSIVE_LOCK ,
fOnCDROM_EXCLUSIVE_UNLOCK ,
fOnDEVICE_BECOMING_READY ,
fOnDEVICE_EXTERNAL_REQUEST ,
fOnMEDIA_ARRIVAL ,
fOnMEDIA_EJECT_REQUEST ,
fOnMEDIA_REMOVAL ,
fOnVOLUME_CHANGE ,
fOnVOLUME_CHANGE_SIZE ,
fOnVOLUME_DISMOUNT ,
fOnVOLUME_DISMOUNT_FAILED ,
fOnVOLUME_FVE_STATUS_CHANGE ,
fOnVOLUME_LOCK ,
fOnVOLUME_LOCK_FAILED ,
fOnVOLUME_MOUNT ,
fOnVOLUME_NAME_CHANGE ,
fOnVOLUME_NEED_CHKDSK ,
fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE ,
fOnVOLUME_PREPARING_EJECT ,
fOnVOLUME_UNIQUE_ID_CHANGE ,
fOnVOLUME_UNLOCK ,
fOnVOLUME_WEARING_OUT : TNotifyEvent;
procedure WndProc(var Message: TMessage);
procedure Log(AStr: string);
public
constructor Create;
destructor Destroy; override;
function RegisterThis: Boolean;
property Logger: TMemo read fLogger write fLogger;
published
property OnCDROM_EXCLUSIVE_LOCK : TNotifyEvent read fOnCDROM_EXCLUSIVE_LOCK write fOnCDROM_EXCLUSIVE_LOCK ;
property OnCDROM_EXCLUSIVE_UNLOCK : TNotifyEvent read fOnCDROM_EXCLUSIVE_UNLOCK write fOnCDROM_EXCLUSIVE_UNLOCK ;
property OnDEVICE_BECOMING_READY : TNotifyEvent read fOnDEVICE_BECOMING_READY write fOnDEVICE_BECOMING_READY ;
property OnDEVICE_EXTERNAL_REQUEST : TNotifyEvent read fOnDEVICE_EXTERNAL_REQUEST write fOnDEVICE_EXTERNAL_REQUEST ;
property OnMEDIA_ARRIVAL : TNotifyEvent read fOnMEDIA_ARRIVAL write fOnMEDIA_ARRIVAL ;
property OnMEDIA_EJECT_REQUEST : TNotifyEvent read fOnMEDIA_EJECT_REQUEST write fOnMEDIA_EJECT_REQUEST ;
property OnMEDIA_REMOVAL : TNotifyEvent read fOnMEDIA_REMOVAL write fOnMEDIA_REMOVAL ;
property OnVOLUME_CHANGE : TNotifyEvent read fOnVOLUME_CHANGE write fOnVOLUME_CHANGE ;
property OnVOLUME_CHANGE_SIZE : TNotifyEvent read fOnVOLUME_CHANGE_SIZE write fOnVOLUME_CHANGE_SIZE ;
property OnVOLUME_DISMOUNT : TNotifyEvent read fOnVOLUME_DISMOUNT write fOnVOLUME_DISMOUNT ;
property OnVOLUME_DISMOUNT_FAILED : TNotifyEvent read fOnVOLUME_DISMOUNT_FAILED write fOnVOLUME_DISMOUNT_FAILED ;
property OnVOLUME_FVE_STATUS_CHANGE : TNotifyEvent read fOnVOLUME_FVE_STATUS_CHANGE write fOnVOLUME_FVE_STATUS_CHANGE ;
property OnVOLUME_LOCK : TNotifyEvent read fOnVOLUME_LOCK write fOnVOLUME_LOCK ;
property OnVOLUME_LOCK_FAILED : TNotifyEvent read fOnVOLUME_LOCK_FAILED write fOnVOLUME_LOCK_FAILED ;
property OnVOLUME_MOUNT : TNotifyEvent read fOnVOLUME_MOUNT write fOnVOLUME_MOUNT ;
property OnVOLUME_NAME_CHANGE : TNotifyEvent read fOnVOLUME_NAME_CHANGE write fOnVOLUME_NAME_CHANGE ;
property OnVOLUME_NEED_CHKDSK : TNotifyEvent read fOnVOLUME_NEED_CHKDSK write fOnVOLUME_NEED_CHKDSK ;
property OnVOLUME_PHYSICAL_CONFIGURATION_CHANGE : TNotifyEvent read fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE write fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE;
property OnVOLUME_PREPARING_EJECT : TNotifyEvent read fOnVOLUME_PREPARING_EJECT write fOnVOLUME_PREPARING_EJECT ;
property OnVOLUME_UNIQUE_ID_CHANGE : TNotifyEvent read fOnVOLUME_UNIQUE_ID_CHANGE write fOnVOLUME_UNIQUE_ID_CHANGE ;
property OnVOLUME_UNLOCK : TNotifyEvent read fOnVOLUME_UNLOCK write fOnVOLUME_UNLOCK ;
property OnVOLUME_WEARING_OUT : TNotifyEvent read fOnVOLUME_WEARING_OUT write fOnVOLUME_WEARING_OUT ;
end;
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
end;
var
Form1: TForm1;
dd: TDeviceDetector;
implementation
{$R *.dfm}
type
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
end;
PDevBroadcastHandle = ^DEV_BROADCAST_HANDLE;
DEV_BROADCAST_HANDLE = record
dbch_size : DWORD;
dbch_devicetype : DWORD;
dbch_reserved : DWORD;
dbch_handle : THandle;
dbch_hdevnotify : HDEVNOTIFY;
dbch_eventguid : TGUID;
dbch_nameoffset : LONG;
dbch_data: array [0..0] of Byte;
end;
const
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = [=11=]000004;
DBT_CUSTOMEVENT = 06;
DBT_DEVTYP_HANDLE = [=11=]06;
GUID_IO_CDROM_EXCLUSIVE_LOCK : TGUID = '{bc56c139-7a10-47ee-a294-4c6a38f0149a}';
GUID_IO_CDROM_EXCLUSIVE_UNLOCK : TGUID = '{a3b6d27d-5e35-4885-81e5-ee18c00ed779}';
GUID_IO_DEVICE_BECOMING_READY : TGUID = '{d07433f0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_DEVICE_EXTERNAL_REQUEST : TGUID = '{d07433d0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_ARRIVAL : TGUID = '{d07433c0-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_EJECT_REQUEST : TGUID = '{d07433d1-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_MEDIA_REMOVAL : TGUID = '{d07433c1-a98e-11d2-917a-00a0c9068ff3}';
GUID_IO_VOLUME_CHANGE : TGUID = '{7373654a-812a-11d0-bec7-08002be2092f}';
GUID_IO_VOLUME_CHANGE_SIZE : TGUID = '{3a1625be-ad03-49f1-8ef8-6bbac182d1fd}';
GUID_IO_VOLUME_DISMOUNT : TGUID = '{d16a55e8-1059-11d2-8ffd-00a0c9a06d32}';
GUID_IO_VOLUME_DISMOUNT_FAILED : TGUID = '{E3C5B178-105D-11D2-8FFD-00A0C9A06D32}';
GUID_IO_VOLUME_FVE_STATUS_CHANGE : TGUID = '{062998b2-ee1f-4b6a-b857-e76cbbe9a6da}';
GUID_IO_VOLUME_LOCK : TGUID = '{50708874-c9af-11d1-8fef-00a0c9a06d32}';
GUID_IO_VOLUME_LOCK_FAILED : TGUID = '{ae2eed10-0ba8-11d2-8ffb-00a0c9a06d32}';
GUID_IO_VOLUME_MOUNT : TGUID = '{b5804878-1a96-11d2-8ffd-00a0c9a06d32}';
GUID_IO_VOLUME_NAME_CHANGE : TGUID = '{2de97f83-4c06-11d2-a532-00609713055a}';
GUID_IO_VOLUME_NEED_CHKDSK : TGUID = '{799a0960-0a0b-4e03-ad88-2fa7c6ce748a}';
GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE : TGUID = '{2de97f84-4c06-11d2-a532-00609713055a}';
GUID_IO_VOLUME_PREPARING_EJECT : TGUID = '{c79eb16e-0dac-4e7a-a86c-b25ceeaa88f6}';
GUID_IO_VOLUME_UNIQUE_ID_CHANGE : TGUID = '{af39da42-6622-41f5-970b-139d092fa3d9}';
GUID_IO_VOLUME_UNLOCK : TGUID = '{9a8c3d68-d0cb-11d1-8fef-00a0c9a06d32}';
GUID_IO_VOLUME_WEARING_OUT : TGUID = '{873113ca-1486-4508-82ac-c3b2e5297aaa}';
function WDE_GUID_To_String(AGUID: TGUID): string; //WDE stands for Windows Device Events
begin
if AGUID = GUID_IO_CDROM_EXCLUSIVE_LOCK then result := 'GUID_IO_CDROM_EXCLUSIVE_LOCK' else
if AGUID = GUID_IO_CDROM_EXCLUSIVE_UNLOCK then result := 'GUID_IO_CDROM_EXCLUSIVE_UNLOCK' else
if AGUID = GUID_IO_DEVICE_BECOMING_READY then result := 'GUID_IO_DEVICE_BECOMING_READY' else
if AGUID = GUID_IO_DEVICE_EXTERNAL_REQUEST then result := 'GUID_IO_DEVICE_BECOMING_READY' else
if AGUID = GUID_IO_MEDIA_ARRIVAL then result := 'GUID_IO_MEDIA_ARRIVAL' else
if AGUID = GUID_IO_MEDIA_EJECT_REQUEST then result := 'GUID_IO_MEDIA_EJECT_REQUEST' else
if AGUID = GUID_IO_MEDIA_REMOVAL then result := 'GUID_IO_MEDIA_REMOVAL' else
if AGUID = GUID_IO_VOLUME_CHANGE then result := 'GUID_IO_VOLUME_CHANGE' else
if AGUID = GUID_IO_VOLUME_CHANGE_SIZE then result := 'GUID_IO_VOLUME_CHANGE_SIZE' else
if AGUID = GUID_IO_VOLUME_DISMOUNT then result := 'GUID_IO_VOLUME_DISMOUNT' else
if AGUID = GUID_IO_VOLUME_DISMOUNT_FAILED then result := 'GUID_IO_VOLUME_DISMOUNT_FAILED' else
if AGUID = GUID_IO_VOLUME_FVE_STATUS_CHANGE then result := 'GUID_IO_VOLUME_FVE_STATUS_CHANGE' else
if AGUID = GUID_IO_VOLUME_LOCK then result := 'GUID_IO_VOLUME_LOCK' else
if AGUID = GUID_IO_VOLUME_LOCK_FAILED then result := 'GUID_IO_VOLUME_LOCK_FAILED' else
if AGUID = GUID_IO_VOLUME_MOUNT then result := 'GUID_IO_VOLUME_MOUNT' else
if AGUID = GUID_IO_VOLUME_NAME_CHANGE then result := 'GUID_IO_VOLUME_NAME_CHANGE' else
if AGUID = GUID_IO_VOLUME_NEED_CHKDSK then result := 'GUID_IO_VOLUME_NEED_CHKDSK' else
if AGUID = GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE then result := 'GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE' else
if AGUID = GUID_IO_VOLUME_PREPARING_EJECT then result := 'GUID_IO_VOLUME_PREPARING_EJECT' else
if AGUID = GUID_IO_VOLUME_UNIQUE_ID_CHANGE then result := 'GUID_IO_VOLUME_UNIQUE_ID_CHANGE' else
if AGUID = GUID_IO_VOLUME_UNLOCK then result := 'GUID_IO_VOLUME_UNLOCK' else
if AGUID = GUID_IO_VOLUME_WEARING_OUT then result := 'GUID_IO_VOLUME_WEARING_OUT';
end;
constructor TDeviceDetector.Create;
begin
inherited;
fHandle := AllocateHWnd(WndProc);
end;
destructor TDeviceDetector.Destroy;
begin
DeallocateHWnd(fHandle);
inherited;
end;
function TDeviceDetector.RegisterThis: Boolean;
var
dbv: DEV_BROADCAST_HANDLE;
Size: Integer;
r: Pointer;
begin
Size := SizeOf(DEV_BROADCAST_HANDLE);
ZeroMemory(@dbv, Size);
dbv.dbch_size := Size;
dbv.dbch_devicetype := DBT_DEVTYP_HANDLE;
dbv.dbch_reserved := 0;
dbv.dbch_handle := CreateFileA('\.\E:', GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, 0);
dbv.dbch_hdevnotify := RegisterDeviceNotification(fHandle, @dbv, DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);;
dbv.dbch_nameoffset := 0;
if Assigned(dbv.dbch_hdevnotify) then Result := True;
end;
procedure TDeviceDetector.WndProc(var Message: TMessage);
var data: PDevBroadcastHdr;
data_H: PDevBroadcastHandle;
begin
if Message.wParam = DBT_CUSTOMEVENT then //according to MSDN, DEV_BROADCAST_HANDLE structure is treated as a custom event.
begin
Data := PDevBroadcastHdr(Message.LParam); //we need to treat this custom evend a DEV_BROADCAST_HDR structure first...
if Data^.dbch_devicetype = DBT_DEVTYP_HANDLE then //then we check if the device type is DBT_DEVTYP_HANDLE
begin
data_H := PDevBroadcastHandle(Message.lParam); //if the device type is DBT_DEVTYP_HANDLE, we treat the custom event as a DEV_BROADCAST_HANDLE structure
//final step is to see what GUID the event of the structure DEV_BROADCAST_HANDLE has
Log(WDE_GUID_To_String(data_H^.dbch_eventguid));
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_CDROM_EXCLUSIVE_LOCK) = true then if assigned(fOnCDROM_EXCLUSIVE_LOCK) then fOnCDROM_EXCLUSIVE_LOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_CDROM_EXCLUSIVE_UNLOCK) = true then if assigned(fOnCDROM_EXCLUSIVE_UNLOCK) then fOnCDROM_EXCLUSIVE_UNLOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_DEVICE_BECOMING_READY) = true then if assigned(fOnDEVICE_BECOMING_READY) then fOnDEVICE_BECOMING_READY(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_DEVICE_EXTERNAL_REQUEST) = true then if assigned(fOnDEVICE_EXTERNAL_REQUEST) then fOnDEVICE_EXTERNAL_REQUEST(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_ARRIVAL) = true then if assigned(fOnMEDIA_ARRIVAL) then fOnMEDIA_ARRIVAL(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_EJECT_REQUEST) = true then if assigned(fOnMEDIA_EJECT_REQUEST) then fOnMEDIA_EJECT_REQUEST(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_MEDIA_REMOVAL) = true then if assigned(fOnMEDIA_REMOVAL) then fOnMEDIA_REMOVAL(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_CHANGE) = true then if assigned(fOnVOLUME_CHANGE) then fOnVOLUME_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_CHANGE_SIZE) = true then if assigned(fOnVOLUME_CHANGE_SIZE) then fOnVOLUME_CHANGE_SIZE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_DISMOUNT) = true then if assigned(fOnVOLUME_DISMOUNT) then fOnVOLUME_DISMOUNT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_DISMOUNT_FAILED) = true then if assigned(fOnVOLUME_DISMOUNT_FAILED) then fOnVOLUME_DISMOUNT_FAILED(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_FVE_STATUS_CHANGE) = true then if assigned(fOnVOLUME_FVE_STATUS_CHANGE) then fOnVOLUME_FVE_STATUS_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_LOCK) = true then if assigned(fOnVOLUME_LOCK) then fOnVOLUME_LOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_LOCK_FAILED) = true then if assigned(fOnVOLUME_LOCK_FAILED) then fOnVOLUME_LOCK_FAILED(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_MOUNT) = true then if assigned(fOnVOLUME_MOUNT) then fOnVOLUME_MOUNT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_NAME_CHANGE) = true then if assigned(fOnVOLUME_NAME_CHANGE) then fOnVOLUME_NAME_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_NEED_CHKDSK) = true then if assigned(fOnVOLUME_NEED_CHKDSK) then fOnVOLUME_NEED_CHKDSK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_PHYSICAL_CONFIGURATION_CHANGE) = true then if assigned(fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE) then fOnVOLUME_PHYSICAL_CONFIGURATION_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_PREPARING_EJECT) = true then if assigned(fOnVOLUME_PREPARING_EJECT) then fOnVOLUME_PREPARING_EJECT(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_UNIQUE_ID_CHANGE) = true then if assigned(fOnVOLUME_UNIQUE_ID_CHANGE) then fOnVOLUME_UNIQUE_ID_CHANGE(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_UNLOCK) = true then if assigned(fOnVOLUME_UNLOCK) then fOnVOLUME_UNLOCK(self) else
if IsEqualGUID(data_H^.dbch_eventguid, GUID_IO_VOLUME_WEARING_OUT) = true then if assigned(fOnVOLUME_WEARING_OUT) then fOnVOLUME_WEARING_OUT(self);
end;
end;
end;
procedure TDeviceDetector.Log(AStr: string);
begin
fLogger.Lines.Add(AStr);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
dd := TDeviceDetector.Create;
dd.Logger := Memo1;
if dd.RegisterThis = true then Memo1.Lines.Add('Registered!') else Memo1.Lines.Add('Failed to register!');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
dd.free;
end;
end.