Programmierung > Delphi > Laufwerksänderung

Mehr USB-Nachrichten

  • Beschreibung
  • Erstellung der Klasse
  • Freigabe der Klasse
  • Ereignisse
  • Strukturen
  • Nachrichtenschleife
  • Anwendung
  • Download
  • Links
  • Änderungen
  • Beschreibung

    In den beiden vorigen Kapiteln bekommt man über WM_DEVICECHANGE lediglich Nachrichten über das Entfernen und Hinzufügen von optischen und USB-Medien sowie dem betroffenden Laufwerk. Hier sollen weitere Informationen über die Nachrichten ermittelt werden. Dies geschieht über die Registrierung von Geräteklassen.

    Die Verwendung in der eigenen Klasse erscheint mir hier einfacher.

    Erstellung der Klasse

    Bei der Erstellung der Klasse muss die Geräteklasse für die Benachrichtigungen registriert werden. Dazu wird die Erstellung der Klasse erweitert:

    constructor TDeviceNotification.Create;
    var
      aDBD : TDevBroadcastDeviceInterface;
    begin
      inherited;
      fWindowHandle := AllocateHWnd(WndProc);
      ZeroMemory(@aDBD, SizeOf(aDBD));
      aDBD.dbcc_size       := SizeOf(aDBD);
      aDBD.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
      aDBD.dbcc_classguid  := GUID_DEVINTERFACE_USB_DEVICE;
      fNotificationHandle  := RegisterDeviceNotification(fWindowHandle,
                              @aDBD, DEVICE_NOTIFY_WINDOW_HANDLE);
    end;
    

    Die RegisterDeviceNotification ist eine Windows-Funktion, welche als ~A- und ~W-Version deklariert ist. Da hier Delphi 7 eingesetzt wird, wird die ~A verwendet:

    HDEVNOTIFY RegisterDeviceNotificationA(
      HANDLE hRecipient,
      LPVOID NotificationFilter,
      DWORD  Flags
    );
    
    function RegisterDeviceNotification(hRecipient: THandle;
                                        NotificationFilter: Pointer;
                                        Flags: DWORD)
             : HDEVNOTIFY;
             stdcall;
             external user32 name 'RegisterDeviceNotificationA';
    

    hRecipient - Der Empfänger für die Nachrichten ist die Nachrichtenschleife.

    Flags - Das Flags gibt an, worum es sich bei dem Empfänger handelt. Es gibt zwei mögliche Werte:

    DEVICE_NOTIFY_WINDOW_HANDLE    The hRecipient parameter is a window handle.
    0x00000000
     
    DEVICE_NOTIFY_SERVICE_HANDLE   The hRecipient parameter is a service status handle.
    0x00000001
    
    const
      DEVICE_NOTIFY_WINDOW_HANDLE  = $00000000;
      DEVICE_NOTIFY_SERVICE_HANDLE = $00000001;
    

    Es wird DEVICE_NOTIFY_WINDOW_HANDLE verwendet.

    NotificationFilter - Als Filter wird eine DEV_BROADCAST_HDR structure erwartet. Da hier Geräteinformationen erwünscht werden, wird die DEV_BROADCAST_DEVICEINTERFACE_A structure verwendet. Siehe unten.

    Wenn die Registration erfolgreich war, ist fNotificationHandle ein gültiges Handle. Dieses wird für die Freigabe benötigt. Auf die Prüfung der Gültigkeit der Handle wurde hier verzichtet. Die Variable muss natürlich im Kopf der Klasse als HDEVNOTIFY deklariert werden.

    Freigabe der Klasse

    Hier müssen im Detructor die Benachrichtigungen wieder abbestellt werden:

    destructor TDeviceNotification.Destroy;
    begin
      UnregisterDeviceNotification(fNotificationHandle);
      DeallocateHWnd(fWindowHandle);
      inherited;
    end;
    

    UnRegisterDeviceNotification ist eine Windows-Funktion:

    BOOL UnregisterDeviceNotification(
      HDEVNOTIFY Handle
    );
    function UnregisterDeviceNotification(Handle: HDEVNOTIFY)
             : BOOL;
             stdcall;
             external user32 name 'UnregisterDeviceNotification';
    

    Ereignisse

    Die Ereignisse fOnAdding und fOnRemoval sind bereits deklariert. Nach der Registration der Geräteklasse gibt es auch DBT_DEVNODES_CHANGED-Nachrichten. Diese sagen der Anwendung, dass es seine Geräteliste überprüfen soll. Dafür könnte man ein Ereignis deklarieren.

    type
      TDeviceNotification = class
      private
        fNotificationHandle : HDEVNOTIFY;
        ...
        fOnDevNode : TDeviceNotificationProc,
      public
        ...property OnDevNode : TDeviceNotificationProc read fOnDevNode write fOnDevNode;
      end;
    

    Strukturen

    Die Strukturen DEV_BROADCAST_HDR und DEV_BROADCAST_VOLUME wurden bereits im ersten Kapitel dargestellt. Hier kommt die DEV_BROADCAST_DEVICEINTERFACE_A structure hinzu.

    typedef struct _DEV_BROADCAST_DEVICEINTERFACE_A {
      DWORD dbcc_size;
      DWORD dbcc_devicetype;
      DWORD dbcc_reserved;
      GUID  dbcc_classguid;
      char  dbcc_name[1];
    } DEV_BROADCAST_DEVICEINTERFACE_A, *PDEV_BROADCAST_DEVICEINTERFACE_A;
    
    type
      DEV_BROADCAST_DEVICEINTERFACE = record
        dbcc_size       : Cardinal;
        dbcc_devicetype : Cardinal;
        dbcc_reserved   : Cardinal;
        dbcc_classguid  : TGUID;
        dbcc_name       : Char;
      end;
      TDevBroadcastDeviceInterface = DEV_BROADCAST_DEVICEINTERFACE;
      PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE;
    

    dbcc_size - Die Größe der Struktur.

    dbcc_devicetype - Dieser wird bei der Registrierung auf DBT_DEVTYP_DEVICEINTERFACE gesetzt und muss in der Benachricthtigung den selben Type haben.

    dbcc_classguid - Die GUID ist ein Identifikator für eine bestimmte Geräteschnittstelle, für welche Nachrichten empfangen werden sollen. Hier bieten sich zwei Schnittstellen an. So die Geräteschnittstelle für USB-Geräte GUID_DEVINTERFACE_USB_DEVICE:

    Attribute         Setting
    Identifier        GUID_DEVINTERFACE_USB_DEVICE
    Class GUID        {A5DCBF10-6530-11D2-901F-00C04FB951ED}
    
    const
      GUID_DEVINTERFACE_USB_DEVICE : TGUID = '{A5DCBF10-6530-11D2-901F-00C04FB951ED}';
    

    Und alternativ die Geräteschnittstelle für Datenträger GUID_DEVINTERFACE_VOLUME

    Attribute         Setting
    Identifier        GUID_DEVINTERFACE_VOLUME
    Class GUID        {53F5630D-B6BF-11D0-94F2-00A0C91EFB8B}
    
    const
      GUID_DEVINTERFACE_VOLUME : TGUID = '{53F5630D-B6BF-11D0-94F2-00A0C91EFB8B}';
    

    dbcc_name - Hierin befinden sich die Geräteinformationen, wenn Windows WM_DEVICECHANGE schickt. Diese werden in der Nachrichtenschleife ausgewertet.

    Nachrichtenschleife

    So lange wParam auf die bei WM_DEVICECHANGE möglichen Ereignisse geprüft wird, muss Msg nicht geprüft werden. Hier ist die Nachrichtenschleife etwas umfangreicher als im ersten Kapitel:

    procedure TDeviceNotification.WndProc(var Msg: TMessage);
    begin
      case Msg.WParam of
        DBT_DEVICEARRIVAL:
        begin
          if (PDevBroadcastHeader(Msg.lParam)^.dbch_devicetype = DBT_DEVTYP_VOLUME)
          then begin
            if Assigned(fOnAdding)
            then fOnAdding('DBT_DEVICEARRIVAL: ' +
                            GetDriveLetterFromUnitMask(
                                       PDevBroadcastVolume(Msg.lParam)^.dbcv_unitmask));
          end;
          if (PDevBroadcastHeader(Msg.lParam)^.dbch_devicetype =
                                                             DBT_DEVTYP_DEVICEINTERFACE)
          then begin
            if Assigned(fOnAdding)
            then fOnAdding('DBT_DEVICEARRIVAL: ' +
                           PChar(@PDevBroadcastDeviceInterface(Msg.lParam)^.dbcc_name));
          end;
        end;
        DBT_DEVICEREMOVECOMPLETE:
        begin
          if (PDevBroadcastHeader(Msg.lParam)^.dbch_devicetype = DBT_DEVTYP_VOLUME)
          then begin
            if Assigned(fOnRemoval)
            then fOnRemoval('DBT_DEVICEREMOVECOMPLETE: ' +
                            GetDriveLetterFromUnitMask(
                                       PDevBroadcastVolume(Msg.lParam)^.dbcv_unitmask));
          end;
          if (PDevBroadcastHeader(Msg.lParam)^.dbch_devicetype =
                                                             DBT_DEVTYP_DEVICEINTERFACE)
          then begin
            if Assigned(fOnAdding)
            then fOnAdding('DBT_DEVICEREMOVECOMPLETE: ' +
                           PChar(@PDevBroadcastDeviceInterface(Msg.lParam)^.dbcc_name));
          end;
        end;
        DBT_DEVNODES_CHANGED:
        begin
          if Assigned(fOnDevNode)
          then fOnDevNode('DBT_DEVNODES_CHANGED');
        end;
      end;
      Msg.Result := DefWindowProc(fWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
    end;
    

    In dieser Nachrichtenschleife werden einfach Zeichenketten mit der Ereignisbezeichnung und dem Namen des Laufwerkes erstellt.

    Hinweis: In dieser Nachrichtenschleife kommen zum Beispiel auch Messages WM_ACTIVATEAPP an, wenn beim Anstecken eines USB-Sticks ein Explorer-Fenster geöffnet und dieses wieder geschlossen wird. Diese werden hier nicht abgefangen.

    Anwendung

    In der Anwendung wird die Klasse erstellt und die Ereignisse verbunden. Hier kommt noch die dritte Eigenschaft hinzu. Die von der Klasse beispielhaft erstellten und übergebenenen Zeichenketten werden nur in ein Memo geschrieben. Mit den Laufwerken aus Kapitel 1 sieht es beispielsweise so aus:

    Mit GUID_DEVINTERFACE_USB_DEVICE bekommt man beispeilsweise diesen Name für das USB-Laufwerk (J:):

    \\?\USB#VID_0781&PID_5151#2204501DB6C2F0F1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
    

    Was sagt der String aus?

    \\?\USB                                   Gerätetyp
    #
    VID_0781&PID_5151                         Hardware ID
    #
    2204501DB6C2F0F1                          Instance ID / Seriennummer
    #
    {a5dcbf10-6530-11d2-901f-00c04fb951ed}    GUID_DEVINTERFACE_USB_DEVICE
    

    Die Hardware ID besteht aus zwei Teilen:

    VID_0781                                  VendorID - hier: SanDisk Corp.
    &
    PID_5151                                  DeviceID - hier: Cruzer Micro Flash Drive
    

    Zu den IDs für Vendor (Hersteller) und Device (Gerät/Modell) findet man Informationen bei DEVICE HUNT. Ein Aufruf würde so aussehen:

    https://devicehunt.com/view/type/usb/vendor/0781/device/5151

    Die Verwendung von GUID_DEVINTERFACE_VOLUME ergibt für den selben USB-Stick:

    \\?\STORAGE#Volume#_??_USBSTOR#Disk&Ven_SanDisk&Prod_Cruzer&Rev_8.01#2204501DB6C2F0F1&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}
    

    Dieser Name wird in Uwe Siebers USB Drive Info als DevicePath bezeichnet. Diesen Pfad findet

    \\?\STORAGE                                Gerätetyp Storage/Speicher
    #
    Volume                                     Volume/Datenträger
    #
    _??_USBSTOR                                USB Mass Storage/USB-Massenspeicher
    #
    Disk                                       Disk/Datenträger
    &
    Ven_SanDisk                                Vendor/Hersteller - hier: SanDisk
    &
    Prod_Cruzer                                Product/Produkt - hier: Cruzer
    &
    Rev_8.01                                   Revision - hier: 8.01
    #
    2204501DB6C2F0F1                           Instance ID / Seriennummer?
    &
    0
    #
    {53f56307-b6bf-11d0-94f2-00a0c91efb8b}     GUID_DEVINTERFACE_VOLUME
    #
    {53f5630d-b6bf-11d0-94f2-00a0c91efb8b}     GUID_DEVINTERFACE_VOLUME
    

    Download

    Die Demo zu obigen Beispiel. Erstellt und compiliert mit Delphi 7.

    wmchange3_d7_exe.7z (150 kb) - MD5
    wmchange3_d7_src.7z (2,4 kb) - MD5
    Stand: 7. April 2020

    Änderungen

    07.04.2020Uses der Demo bereinigt.
    06.04.2020Erstellung der Seite.