W
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. ]