Home | Kontakt | Sitemap

Start

Über mich

Kontakt

Sitemap

Lizenz

Anleitungen

DVD, miniDVD

SVCD

Audio, Audio-CD (CD-DA)

AVI

Software

Von Freunden und Bekannten

Eigene Programme

Programmierung

Delphi

Lazarus

Delphi/Lazarus

Projekte

MPEG-1/2 Video

Optische Laufwerke

Audio-CD (CDDA)

Raspberry Pi Dashcam

Verschiedenes

MPEG 2 Schnitt

Project X

VCD Easy

Hardlinks

Windows

Links

Software

Programmierung Delphi - Laufwerksänderung - In eigener Class

Laufwerksänderung (1)

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

    Es soll zumindest auf Änderungen von CD/DVD/BD-Laufwerken und USB-Sticks reagiert werden. Dazu wird die Windows-Message WM_DEVIVECHANGE Projekten wollte ich auf das Einlegen einer CD reagieren. In einem jedoch auf das Anstecken eines USB-Gerätes. Die Windows-Nachricht WM_DeviceChange liefert verschiedene Informationen. Für das Wechseln einer CD oder DVD findet man "viele" Lösungen, welche im Prinzip gleich ist und den Buchstaben des entsprechenden Laufwerkes liefert. Diese Lösungen sollen bei USB-Laufwerkes angeblich nicht funktionieren. An einer Stelle fand ich dann die Lösung, bei welcher die Änderung über eine Laufwerksliste ermittelt wurde. Eine oft in Variationen vorkommende Lösung ermittelt einen DevicePath; jedoch bekommt man dabei keinen Laufwerksbuchstaben.

    Die Klassendeklaration

    type
      TDeviceNotification = class
      private
        fWindowHandle : HWND;
        fOnAdding     : TDeviceNotificationProc;
        fOnRemoval    : TDeviceNotificationProc;
        procedure WndProc(var Msg: TMessage);
        function  GetDriveLetterFromUnitMask(dwUnitMask: Cardinal): String;
      public
        constructor Create;
        property OnAdding  : TDeviceNotificationProc read fOnAdding write fOnAdding;
        property OnRemoval : TDeviceNotificationProc read fOnRemoval write fOnRemoval;
        destructor Destroy;
      end;
    

    fWindowHandle ist das Handle auf das Fenster, welches die Nachrichtenschleife benötigt.

    fOnAdding und fOnRemoval sind die Variablen für die verbundenen Ereignisse. OnAdding und OnRemoval die entsprechenden Eigenschaften.

    WndProc ist die Nachrichtenschleife und GetDriveLetterFromUnitMask ein Helferlein, um die Laufwerksbuchstaben zu ermitteln.

    Erstellung der Klasse

    constructor TDeviceNotification.Create;
    begin
      inherited;
      fWindowHandle := AllocateHWnd(WndProc);
    end;
    

    Windowsnachrichten werden nur an Fenster gesendet. Deshalb wird für die Nachrichtenschleife WndProc ein Fensterhandle fWindowHandle erstellt. Um das Fenster am Ende wieder freigeben zu können, wird das Handle in einer Variable gespeichert.

    Das Fensterhandle wird mit der Delphi-Funktion AllocateHWnd aus der Unit Classes erstellt.

    Freigabe der Klasse

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

    Beim Beenden wird das Fenster für die Nachrichtenschleife freigegeben werden.

    Ereignisse

    In der Klasse wurden die Ereignisse

    type
      fOnAdding  : TDeviceNotificationProc;
      fOnRemoval : TDeviceNotificationProc;
    

    deklariert. Die Typdeklaration dazu sieht so aus:

    type
      TDeviceBNotificationProc = procedure(const DeviceName: String) of Object;
    

    Man könnte dem Ereignis auch einen Sender mitgeben. Darauf wurde hier verzichtet.

    type
      TDeviceNotificationProc = procedure(Sender: TObject; const DeviceName: String) of Object;
    

    Die Ereignisse werden in dieser Klasse in der Nachrichtenschleife verwendet.

    Strukturen

    Für die Nachrichtenschleife wird zunächst der Device Broadcast Header benötigt.

    DEV_BROADCAST_HDR structure

    typedef struct _DEV_BROADCAST_HDR {
      DWORD dbch_size;
      DWORD dbch_devicetype;
      DWORD dbch_reserved;
    } DEV_BROADCAST_HDR;
    
    type
      _DEV_BROADCAST_HDR = record
        dbch_size       : Cardinal;
        dbch_devicetype : Cardinal;
        dbch_reserved   : Cardinal;
      end;
      TDevBroadcastHeader = _DEV_BROADCAST_HDR;
      PDevBroadcastHeader = ^_DEV_BROADCAST_HDR;
    

    Es handelt sich um den Header für fünf Strukturen:

    DBT_DEVTYP_DEVICEINTERFACE              Class of devices. This structure is a
    0x00000005                              DEV_BROADCAST_DEVICEINTERFACE structure.
     
    DBT_DEVTYP_HANDLE                       File system handle. This structure is a
    0x00000006                              DEV_BROADCAST_HANDLE structure.
     
    DBT_DEVTYP_OEM                          OEM- or IHV-defined device type. This
    0x00000000                              structure is a DEV_BROADCAST_OEM structure.
     
    DBT_DEVTYP_PORT                         Port device (serial or parallel). This
    0x00000003                              structure is a DEV_BROADCAST_PORT structure.
     
    DBT_DEVTYP_VOLUME                       Logical volume. This structure is a
    0x00000002                              DEV_BROADCAST_VOLUME structure.
    
    const
      DBT_DEVTYP_DEVICEINTERFACE = $00000005;
      DBT_DEVTYP_HANDLE          = $00000006;
      DBT_DEVTYP_OEM             = $00000000;
      DBT_DEVTYP_PORT            = $00000003;
      DBT_DEVTYP_VOLUME          = $00000002;
    

    Da der Laufwerksbuchstabe interessiert wird hier die DEV_BROADCAST_VOLUME structure verwendet:

    typedef struct _DEV_BROADCAST_VOLUME {
      DWORD dbcv_size;
      DWORD dbcv_devicetype;
      DWORD dbcv_reserved;
      DWORD dbcv_unitmask;
      WORD  dbcv_flags;
    } DEV_BROADCAST_VOLUME;
    
    type
      _DEV_BROADCAST_VOLUME = record
        dbcv_size       : Cardinal;
        dbcv_devicetype : Cardinal;
        dbcv_reserved   : Cardinal;
        dbcv_unitmask   : Cardinal;
        dbcv_flags      : Word;
      end;
      TDevBroadcastVolume = _DEV_BROADCAST_VOLUME;
      PDevBroadcastVolume = ^_DEV_BROADCAST_VOLUME;
    

    dbcv_size - Die Größe der Struktur.

    dbcv_devicetype - Ist hier DBT_DEVTYP_VOLUME.

    dbcv_unitmask - Die Maske enthält bitweise die betroffenden logischen Laufwerke. Bit 0 beginnt mit Laufwerk A.

    dbcv_flags - Es gibt zwei Flags:

    Value         Meaning
    DBTF_MEDIA    Change affects media in drive. If not set, change affects physical
    0x0001        device or drive.
     
    DBTF_NET      Indicated logical volume is a network volume.
    0x0002
    
    const
      DBTF_MEDIA = $0001;  // Gibt an, ob Medien im Gerät betroffen sind.
      DBTF_NET   = $0002;  // Gibt an, ob es ein Netzlaufwerk ist.
    

    Helferlein

    Für die Abfrage des Laufwerkes bei CD-Wechseln gibt es einige Code-Beispiele. Sie haben jedoch zwei Mängel. Sie ermitteln nur ein Laufwerk und dass auch nur wenn das Flag DBTF_MEDIA gesetzt ist. Da dieses Flag zum Beispiel bei USB-Sticks nicht gesetzt ist, können diese Beispiele für USB-Sticks keinen Laufwerksbuchstaben liefern.

    function TDeviceNotification.GetDriveLetterFromUnitMask(dwUnitMask: DWORD): String;
    var
      i: Integer;
    begin
      Result := '';
      for i := 0 to 31
      do begin
        if ((dwUnitMask shr i) and 1 <> 0)
        then begin
          Result := Result + Chr($41 + i);
        end;
      end;
    end;
    

    Dieses Helferlein ist nicht von mir. Es ist aus dem Beispielprogramm aus dem Anhang von Olli in diesem Beitrag in der Delphipraxis. Ich habe es nur an meine Schreibweise angepasst.

    Nachrichtenschleife

    Die Funktion der Nachrichtenschleife:

    LRESULT CALLBACK WindowProc(HWND   hwnd,     // handle to window
                                UINT   uMsg,     // WM_DEVICECHANGE
                                WPARAM wParam,   // device-change event
                                LPARAM lParam ); // event-specific data
    

    Das hwnd ist das Fenster der Nachrichtenschleife. Es interessiert nur die Nachricht WM_DEVICECHANGE. Eine Prüfung von uMsg auf WM_DEVICECHANGE ist anscheinend nicht notwendig wenn wParam auf die möglichen Ereignisse geprüft wirdt. Die möglichen Werte sind:

    Value                        Meaning
    DBT_CONFIGCHANGECANCELED     A request to change the current configuration (dock or
    0x0019                       undock) has been canceled.
     
    DBT_CONFIGCHANGED            The current configuration has changed, due to a dock
    0x0018                       or undock.
     
    DBT_CUSTOMEVENT              A custom event has occurred.
    0x8006
     
    DBT_DEVICEARRIVAL            A device or piece of media has been inserted and is now
    0x8000                       available.
     
    DBT_DEVICEQUERYREMOVE        Permission is requested to remove a device or piece of
    0x8001                       media. Any application can deny this request and cancel
                                 the removal.
     
    DBT_DEVICEQUERYREMOVEFAILED  A request to remove a device or piece of media has been
    0x8002                       canceled.
     
    DBT_DEVICEREMOVECOMPLETE     A device or piece of media has been removed.
    0x8004
     
    DBT_DEVICEREMOVEPENDING      A device or piece of media is about to be removed.
    0x8003                       Cannot be denied.
     
    DBT_DEVICETYPESPECIFIC       A device-specific event has occurred.
    0x8005
     
    DBT_DEVNODES_CHANGED         A device has been added to or removed from the system.
    0x0007
     
    DBT_QUERYCHANGECONFIG        Permission is requested to change the current
    0x0017                       configuration (dock or undock).
     
    DBT_USERDEFINED              The meaning of this message is user-defined.
    0xFFFF
    const
      DBT_CONFIGCHANGECANCELED    = $0019;
      DBT_CONFIGCHANGED           = $0018;
      DBT_CUSTOMEVENT             = $8006;
      DBT_DEVICEARRIVAL           = $8000;
      DBT_DEVICEQUERYREMOVE       = $8001;
      DBT_DEVICEQUERYREMOVEFAILED = $8002;
      DBT_DEVICEREMOVECOMPLETE    = $8004;
      DBT_DEVICEREMOVEPENDING     = $8003;
      DBT_DEVICETYPESPECIFIC      = $8005;
      DBT_DEVNODES_CHANGED        = $0007;
      DBT_QUERYCHANGECONFIG       = $0017;
      DBT_USERDEFINED             = $FFFF;
    

    Interessant sind hier wohl nur die Ereignisse:

    DBT_DEVICEARRIVAL - Ein Gerät/Medium wurde eingelegt und ist verfügbar - und
    DBT_DEVICEREMOVECOMPLETE - Ein Gerät/Medium wurde entfernt.

    Dann können die Daten aus lParam ausgewertet werden. Entsprechend dem Ereignis werden die verbundenen Ereignisse aufgerufen. DefWindowProcA gibt die Nachricht weiter, falls sie hier nicht abgearbeitet wurde.

    LRESULT LRESULT DefWindowProcA(
      HWND   hWnd,
      UINT   Msg,
      WPARAM wParam,
      LPARAM lParam
    );

    Die Funktion ist in der Unit Windows deklariert.

    Die Procedure sieht dann so aus:

    procedure TDeviceNotification.WndProc(var Msg: TMessage);
    begin
      if (Msg.wParam = DBT_DEVICEARRIVAL) or (Msg.wParam = DBT_DEVICEREMOVECOMPLETE)
      then try
        if (PDevBroadcastHeader(Msg.lParam)^.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE)
        then begin
          if (Msg.wParam = DBT_DEVICEARRIVAL)
          then begin
            if Assigned(fOnAdding)
            then fOnAdding(GetDriveLetterFromUnitMask(
                                        PDevBroadcastVolume(Msg.lParam)^.dbcv_unitmask));
          end
          else begin
            if Assigned(fOnRemoval)
            then fOnRemoval(GetDriveLetterFromUnitMask(
                                        PDevBroadcastVolume(Msg.lParam)^.dbcv_unitmask));
          end;
        end;
      except
        Msg.Result := DefWindowProc(fWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
      end
      else begin
        Msg.Result := DefWindowProc(fWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
      end;
    end;
    

    Anwendung

    In der Anwendung kann die Klasse erstellt und die Ereignisse verbunden werden.

    Zum Beispiel eine einfache Ausgabe in ein Memo:

    uses
      ..., uNotification;
     
    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
      private
        DeviceNotification : TDeviceNotification;
        procedure DeviceAdding(const DeviceLetter: String);
        procedure DeviceRemoval(const DeviceLetter: String);
      end;
    
    procedure TForm1.DeviceAdding(const DeviceLetter: String);
    begin
      Memo1.Lines.Add('Hinzu:    ' + DeviceLetter);
    end;
     
    procedure TForm1.DeviceRemoval(const DeviceLetter: String);
    begin
      Memo1.Lines.Add('Entfernt: ' + DeviceLetter);
    end;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Memo1.Lines.Clear;
     
      DeviceNotification := TDeviceNotification.Create;
      DeviceNotification.OnAdding  := DeviceAdding;
      DeviceNotification.OnRemoval := DeviceRemoval;
    end;
    

    Das Ergebnis, welches im Memo ausgegeben wird, ist der Laufwerksbuchstabe des betroffenen Laufwerkes.

    G ist ein CD/DVD-Laufwerk. Nachrichten gib es nur wenn ein Medium ausgeworfen oder eingelegt wird. HI ist U3-USB-Stick. H ist der "CD"-Teil und I der USB-Stick dazu. J ist ein USB-Stick.

    Download

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

    wmchange1_d7_exe.7z (150 kb) - MD5
    wmchange1_d7_src.7z (2,0 kb) - MD5
    Stand: 4. April 2020

    Änderungen

    04.04.2020Komplette Überarbeitung.
    23.03.2020Ergänzung bei den Ergebnisstrings.
    21.03.2020Erstellung der Seite.