检测何时使用 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.