Mouse - Aktionen systemweit abfangen (MouseHook)



  • OK, ich habe also wieder die Ehre, einen Beitrag für die FAQ zu schreiben. Heute, liebe Kinder, geht es um Maushooks. Damit kann man Mausaktionen systemweit abfangen. Schön, schön. Dazu müssen wir uns zuerst eine DLL erstellen. Keine Angst... ich führe euch. 😉
    Also, "Projekt->Neu->Konsolenexperte" auswählen, denn wir machen eine WinAPI-DLL. Eine solche verbraucht weniger Speicher. Dann links auf "Windows (GUI)", rechts auf "DLL" klicken und die CheckBox für die VCL freilassen. "OK" klicken. Löscht alles, was da im Quelltext-Editor steht und kopiert den folgenden Code hinein:

    //---------------------------------------------------------------------------
    #include <windows.h>
    #pragma hdrstop
    
    // User defined Message-ID definieren
    #define WM_MOUSEHOOK   WM_USER+100
    //---------------------------------------------------------------------------
    
    // Funktionen deklarieren
    extern "C" __declspec(dllexport) __stdcall void            SetHook(HWND);
    extern "C" __declspec(dllexport) __stdcall void            RemoveHook(void);
    extern "C" __declspec(dllexport) __stdcall MOUSEHOOKSTRUCT GetMouseData(void);
    LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam);
    //---------------------------------------------------------------------------
    
    // Typ-Definitionen
    struct HOOKSTRUCT
    {
       HWND             hwnd;
       MOUSEHOOKSTRUCT  MouseHookStruct;
    };
    //---------------------------------------------------------------------------
    
    // Globale Variablen
    HHOOK        ghHook = NULL; // Hook-Handle
    HINSTANCE    ghInst;        // Instanz-Handle
    LPVOID       lpvMem = NULL; // Zeiger auf das shared memory
    HOOKSTRUCT*  pHookStruct = NULL;
    //---------------------------------------------------------------------------
    
    BOOL WINAPI DllEntryPoint(HINSTANCE hinst, DWORD reason, LPVOID)
    {
        HANDLE hMapFile = NULL;  // Ein File-Mapping-Handle
        BOOL   bFirstProcess;
    
        // Das Instanz-Handle der DLL speichern
        // Wird unten in SetHook() benötigt
        ghInst = hinst;
    
        switch(reason)
        {
              // Die DLL wird in den Adressraum eines Prozesses geladen
              case DLL_PROCESS_ATTACH:
              {
                // Ein File-Mapping-Objekt erstellen (mit Name)
                // (wird nicht neu erstellt, wenn schon existent)
                hMapFile = CreateFileMapping(
                    (HANDLE)0xFFFFFFFF,  // Paging File benutzen
                    NULL,                // Keine Security Attributes
                    PAGE_READWRITE,      // read/write access
                    0,                   // Größe des Files (obere 32 Bit): 0
                    sizeof(HOOKSTRUCT),  // Größe des Files (untere 32 Bit)
                    "DLLMemFileMap");    // Ein Name für unser File-Mapping-Objekt
                if(hMapFile == NULL)
                   return FALSE;
    
                // bFirstProcess == true nur im ersten ladenden Prozess
                bFirstProcess = (GetLastError() != ERROR_ALREADY_EXISTS);
    
                if(!bFirstProcess)
                {
                   hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // Voller Zugriff
                                              FALSE,               // Den Namen nicht vererben
                                              "DLLMemFileMap");    // Name des Objektes
                   if(hMapFile == NULL)
                      return FALSE;
                }
    
                // Einen Zeiger auf das file-mapped shared memory bekommen
                lpvMem = MapViewOfFile(
                    hMapFile,            // Das Objekt
                    FILE_MAP_ALL_ACCESS, // Voller Zugriff
                    0,                   // high offset:  Von Anfang
                    0,                   // low offset:   Von Anfang
                    0);                  // default: Das ganze File mappen
                if(lpvMem == NULL)
                   return FALSE;
    
                if(bFirstProcess)
                   // Den Speicher initialisieren
                   memset(lpvMem, '\0', sizeof(HOOKSTRUCT));
    
                // pHookStruct setzen auf das erste Byte im shared Memory
                pHookStruct = (HOOKSTRUCT*)lpvMem;
              }
              break;
    
              // Neuer Thread im Prozess
              case DLL_THREAD_ATTACH:
                 break;
    
              // Beenden eines Threads im Prozess
              case DLL_THREAD_DETACH:
                 break;
    
              // Die DLL wird aus dem Adressraum eines Prozesses freigegeben
              case DLL_PROCESS_DETACH:
              {
                  // Den Speicher aus dem Adressraum des Prozesses "rausmappen"
                  UnmapViewOfFile(lpvMem);
    
                  // Das Handle des Prozesses auf das File-Mapping-Objekt schließen
                  CloseHandle(hMapFile);
              }
              break;
        }
    
        return TRUE;
    }
    //---------------------------------------------------------------------------
    
    void __stdcall SetHook(HWND hwnd)
    {
       // Hier wird hwnd in das shared Memory geschrieben
       HOOKSTRUCT* phs = (HOOKSTRUCT*)lpvMem;
       phs->hwnd = hwnd;
    
       if(!ghHook)
       {
          // Starten des Mouse-Hooks
          ghHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseHookProc, ghInst, 0);
          if(!ghHook)
             MessageBox(NULL, "Hook kann nicht erstellt werden", "ERROR", MB_OK|MB_ICONERROR);
       }
       else
          MessageBox(NULL, "Hook ist bereits erstellt", "MouseHook", MB_OK);
    }
    //---------------------------------------------------------------------------
    
    void __stdcall RemoveHook(void)
    {
       // Beenden des Hooks
       UnhookWindowsHookEx(ghHook);
    }
    //---------------------------------------------------------------------------
    
    MOUSEHOOKSTRUCT __stdcall GetMouseData()
    {
       // Die MOUSEHOOKSTRUCT zurückliefern
       return pHookStruct->MouseHookStruct;
    }
    //---------------------------------------------------------------------------
    
    LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
       if( nCode >= 0 )  // means: nCode==HC_ACTION or nCode==HC_NOREMOVE
       {
          // Hier wird die MouseHookStruct in das shared memory geschrieben
          // damit die aufrufende Anwendung die Daten per GetMouseData()
          // abrufen kann
          MOUSEHOOKSTRUCT mhs = *(MOUSEHOOKSTRUCT*)lParam;
          pHookStruct->MouseHookStruct = mhs;
    
          // Die Message an die Form senden (Msg-ID und Koordinaten)
          LONG lPoint = MAKELONG((WORD)mhs.pt.x, (WORD)mhs.pt.y);
          SendMessage(pHookStruct->hwnd, WM_MOUSEHOOK, wParam, (LPARAM)lPoint);
       }
    
       // Die nächste Hook-Prozedur aufrufen (optional)
       return CallNextHookEx(ghHook, nCode, wParam, lParam);
    }
    //---------------------------------------------------------------------------
    

    Der Code ist ausreichend kommentiert. Ich sage nur noch etwas zum FileMapping. Bei Starten des Hooks schließt sich die DLL an alle Prozesse an, die Mausnachrichten verarbeiten. Alle globalen Variablen werden neu initialisiert. D.h., in jedem Prozess hat die Dll ihre eigenen Variablen. Wenn man also beim ersten Aufruf von SetHook (im eigenen Prozess) eine globale Variable setzt und den Hook startet, dann wird diese in allen anderen Prozessen neu initialisiert und beinhaltet dementsprechend nicht den anfangs angegebenen Wert. Wir müssen also irgendwie Daten im Speicher haben, die global (systemweit) abrufbar sind. Ein File-Mapping-Objekt tut genau das. In unserem Code erstellen wir beim ersten Aufruf der Dll ein solches und speichern darin in SetHook() den Wert des Parameters hWnd und in der HookProc die eingegangenen Message-Daten. Die folgenden Prozesse müssen so nur noch das Objekt öffnen und haben dann Zugriff auf die Daten.
    Kompiliert das Ganze über Projekt->Erstellen im BCB-Menu. Achtet dabei darauf, dass in Projekt->Optionen->Linker die CheckBox "Import-Bibliothek erzeugen" markiert ist. So, die DLL ist fertig.
    Nun ein Beispiel-Projekt. In diesem wollen wir einen MouseHook erstellen und die Koordinaten des Mauscursors abfangen. Bei einem Rechtsklick (irgendwo) wollen wir eine MessageBox ausgeben. Im Builder erstellt ihr ein neues Projekt und bindet die eben erzeugte LIB mit ein (Projekt->Dem Projekt hinzufügen->[Die LIB-Datei auswählen]). Das Formular sieht so aus: Width = 112, Height = 133. Packt darauf 6 Labels:

    //
    // Die Header-Datei
    //
    //---------------------------------------------------------------------------
    #ifndef HookUnitH
    #define HookUnitH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    
    // Userdefined Message
    // Wird beim Eingehen von Mouse-Messages von der DLL gesendet
    // und von der MainForm empfangen (s.u.: MESSAGE_MAP)
    #define WM_MOUSEHOOK   WM_USER+100
    //---------------------------------------------------------------------------
    
    class TMouseHookForm : public TForm
    {
    __published:    // Komponenten, die von der IDE verwaltet werden
        TLabel *Label1;  // Caption: "Label1"
        TLabel *Label2;  // Caption: "Label2"
        TLabel *Label3;  // Caption: "X"
        TLabel *Label4;  // Caption: "Y"
        TLabel *Label5;  // Caption: "Msg-ID:"
        TLabel *Label6;  // Caption: "Label6"
        void __fastcall FormCreate(TObject *Sender);
        void __fastcall FormDestroy(TObject *Sender);
    
    private:    // Benutzerdeklarationen
        void __fastcall wmMessage(TMessage& msg);
    
    protected:
      // Ist dafür zuständig, dass die Message WM_MOUSEHOOK
      // an die Funktion wmMessage weitergeleitet wird
      BEGIN_MESSAGE_MAP
        MESSAGE_HANDLER(WM_MOUSEHOOK, TMessage, wmMessage);
      END_MESSAGE_MAP(TForm);
    
    public:     // Benutzerdeklarationen
        __fastcall TMouseHookForm(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TMouseHookForm *MouseHookForm;
    //---------------------------------------------------------------------------
    #endif
    

    Wir fangen hier die selbstdefinierte Message WM_MOUSEHOOK ab, die Informationen über Mausaktionen an unser Fenster sendet. Wenn ihr noch wenig Ahnung von Windows-Messages habt, dann besucht doch mal die FAQ-Seite Window-Messages abfangen im BCB. Eine WM_MOUSEHOOK-Message beinhaltet:

    WPARAM:         Die ID für die Maus-Message
    LOWORD(LPARAM): Die X - Koordinate des Maus-Cursors
    HIWORD(LPARAM): Die Y - Koordinate des Maus-Cursors
    

    Eine Liste der möglichen IDs bekommt ihr, wenn ihr im BCB-Quelltexteditor z.B. "WM_LBUTTONDOWN" eingebt, dieses einmal anklickt, so dass der Cursor darüber blinkt und dann F1 drückt. Dann klickt ihr im Hilfe-Fenster oben auf "Group" und ihr seht die Liste. Alternativ gibt's hier was: MSDN(unter "Notifications").
    Zusätzlich kann man weitere Informationen über die Mausaktion über die Funktion GetMouseData() erhalten, die in der DLL definiert ist. Zurückgegeben wird eine MOUSEHOOKSTRUCT. Eine solche sieht so aus:

    typedef struct tagMOUSEHOOKSTRUCT 
    { 
        POINT pt; 
        HWND  hwnd; 
        UINT  wHitTestCode; 
        DWORD dwExtraInfo; 
    } MOUSEHOOKSTRUCT;
    

    "pt" enthält die Koordinaten des Cursors und "hwnd" ist das Fenster, an das die Nachricht geschickt werden soll (wir erhalten sie, bevor das Fenster sie erhält). Eine Liste der HitTestCodes findet ihr, wenn ihr die gleiche Prozedur oben zu den IDs durchkaut - nur diesmal nicht mit WM_LBUTTONDOWN sondern mit WM_NCHITTEST und mit der Ausnahme, dass ihr nicht auf "Group" zu klicken braucht. Alternativ: MSDN. Der HitTest beinhaltet, in welchem Teil eines Fensters geklickt wurde - meistens relativ uninteressant. Zu "dwExtraInfo" kann ich leider nichts sagen.
    Soviel zu den Messages. Nun kommen wir zu unserer Anwendung. Diese sieht bei mir so aus:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    
    #include "HookUnit.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    
    //---------------------------------------------------------------------------
    // Die Funktionen aus der dem Projekt hinzugefügten LIB deklarieren
    extern "C" __declspec(dllexport) __stdcall void SetHook(HWND);
    extern "C" __declspec(dllexport) __stdcall void RemoveHook(void);
    extern "C" __declspec(dllexport) __stdcall MOUSEHOOKSTRUCT GetMouseData(void);
    //---------------------------------------------------------------------------
    TMouseHookForm *MouseHookForm;
    //---------------------------------------------------------------------------
    __fastcall TMouseHookForm::TMouseHookForm(TComponent* Owner)
        : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TMouseHookForm::FormCreate(TObject *Sender)
    {
      // Den Hook starten
      SetHook(Handle);
      // Die Form vor alle anderen Fenster setzen, damit die Maus-Koordianten
      // immer präsent sind
      SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TMouseHookForm::FormDestroy(TObject *Sender)
    {
      // Naja, weg mit dem Hook ;)
      RemoveHook();
    }
    //---------------------------------------------------------------------------
    
    // Hier geht die Message WM_MOUSEHOOK ein
    void __fastcall TMouseHookForm::wmMessage(TMessage& msg)
    {
      // Die Labels mit den Koordinaten beschriften
      Label1->Caption = GetMouseData().pt.x;  // x-Koordinate
      Label2->Caption = GetMouseData().pt.y;  // y-Koordinate
    
      // Alternativ: (schönes Wort ;))
      //Label1->Caption = LOWORD(msg.LParam);
      //Label2->Caption = HIWORD(msg.LParam);
    
      // Wenn der rechte Maus-Button gedrückt und losgelassen wurde
      if(msg.WParam == WM_RBUTTONUP)
         // Gib eine MessageBox aus
         MessageBox(Handle, "YOU CLICKED RIGHT", "HUHU",
                    MB_OK|MB_ICONINFORMATION|MB_SYSTEMMODAL);
    
      // Die ID der eingehenden Maus-Message ins Label schreiben
      Label6->Caption = (int)msg.WParam;
    }
    //---------------------------------------------------------------------------
    

    Ich denke, die Kommentierungen im Programm reichen aus. So, jetzt hab ich (endlich 😉 ) nichts mehr zu sagen und kann euch nur noch viel Spaß wünschen beim Hacken... äh... Hooken. 🙂

    [ Dieser Beitrag wurde am 29.01.2003 um 18:17 Uhr von WebFritzi editiert. ]


Anmelden zum Antworten