Detecting when a volume is mounted in Windows with

2019-05-27 10:38发布

问题:

I am trying to understand the documentation at MSDN regarding Device Events and how to trigger a notification whenever a volume has been mounted.

I have managed to do this for USB devices using information presented in the following post: detect usb drive/device using delphi

as well as other information found on the internet, but I have noticed that it would be easier to detect when a volume has been mounted directly.

So my question is: how do I implement Device Events handling in my Delphi app?

I am looking at the following documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363217(v=vs.85).aspx

But I can't really figure out how to get it up and running.

So far I have tried the following code, which compiles properly, but nothing happens, please push me in the right direction:

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 = $0006;
  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;

回答1:

There are quite a lot of problems with what you have so far. Here is what I can see.

Incorrect recipient

You are passing a window handle to RegisterDeviceNotification. However, it's far from clear that your window handle implements a message handler for WM_DEVICECHANGE. I recommend using AllocateHWnd to obtain a window handle, and handle WM_DEVICECHANGE in the window procedure that you supply to AllocateHWnd.

Failure to call UnregisterDeviceNotification

The documentation of RegisterDeviceNotification says:

Device notification handles returned by RegisterDeviceNotification must be closed by calling the UnregisterDeviceNotification function when they are no longer needed.

You fail to do this. You have to hold on to the handle returned by RegisterDeviceNotification and pass it to UnregisterDeviceNotification when you no longer want to receive notifications.

Erroneous packing of records

You declared packed records. This is a mistake. For reasons unclear to me, it seems to be a prevailing mistake for Delphi developers to pack their records. Packing results in poor performance. Even worse, when performing interop with aligned records, packing simply results in an incorrect laying out of the record. These records are not packed.

Furthermore, I don't believe that your record should include a dbch_data member. That's only used for DBT_CUSTOMEVENT and I don't think that applies to you. I would declare the record like this:

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;

Weak error checking

You do check the return value of the call to RegisterDeviceNotification. That's good. But if that call fails then you don't call GetLastError to find out why, as described in the documentation. I'd write the call like this:

var
  DevNotificationHandle: HDEVNOTIFY;
....
DevNotificationHandle := RegisterDeviceNotification(...);
Win32Check(DevNotificationHandle <> 0);

That way any errors will be translated into exceptions with textual error messages representing the Win32 error code.

Likely incorrect value of dbch_devicetype

I think you should be passing DBT_DEVTYP_DEVICEINTERFACE rather than DBT_DEVTYP_HANDLE. If you switch to DBT_DEVTYP_DEVICEINTERFACE and address all the points I raised above, then you will receive notifications. For instance:

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 = $0005;
  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.

Note that a default set of notifications is broadcast to top-level windows. So you may not even need to register because I believe that volume changes are part of the default set.



回答2:

You have to declare your WMDeviceChange method like this to receive message:

procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;

Also, since your WMDeviceChange method is part of the Form you should use Form window Handle to register message.

r := RegisterDeviceNotification(Handle, @dbv, DEVICE_NOTIFY_WINDOW_HANDLE);

Since Handle can be recreated during Form's lifetime you should override Form's CreateWnd method and add registration there.

Or even better, you can encapsulate functionality in another 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;


回答3:

EDIT: Found a component on a forum regarding this. It is called TSHChangeNotify and was written by Elliott Shevinm back in 2000 (!)

See the forum thread here. It contains the sourcecode and a fix. Theres also an article written by Zarko Gajic which explains how it works found here

It works perfectly fine in Delphi XE7 on Windows 8.1 when using VCL only.

UPDATE: I modified the code so it now runs on Delphi XE7 with Firemonkey. The updated code can be found here.

The way I set it up is by setting TSHChangeNotify.HardDriveOnly to FALSE and putting TSHChangeNotify.Execute inside main forms OnCreate procedure.


Thanks to Dalija and David, I received some good information and code which came in handy.

However their answers did not solve my problem.

Like David said in one of the comments, he just wanted to "push me in the right direction" as I myself suggested in my question. Fair enough.

I started Googling for "guid_io_volume_mount c++" to see if I could find some usable code elsewhere that I could port.

I came across this forum post: http://www.mofeel.net/957-microsoft-public-vc-language/2343.aspx

The OP mentioned in his code that CreateFileA should be used in order to basically "monitor" whenever a mount point has changed.

A mount point seems to be the drive letter, for example C:, D:, E: etc...

So basically what happens is that by using the following code

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;

we let the OS know that whenever "C:" has changed its mount state (mount/dismount), the OS will send a message to our WndProc message catcher.

My full source is avaible below, still a bit buggy, but it presents a concept so far.

It can detect when a specified volume is mounted, at least. Dismount is detected when you right click on a volume and select "Eject".

Now remember, this code can do more than just detecting when mount points are changed, 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 = $00000004;

  DBT_CUSTOMEVENT = $8006;
  DBT_DEVTYP_HANDLE = $0006;

  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.