Bisweilen möchte man das Verhalten bestimmter Programme etwas ändern. In diesem Beispiel geht es darum, für eine scherzhafte Anwendung Bilder aus einer Digitalkamera zu modifizieren oder zu ersetzen.
Diese Webseite wendet sich an Windows-C-Programmierer. Das Beispiel wurde in Visual C++ 6.0 erstellt.
Wie immer bei mir ohne Laufzeitbibliothek.
Die Bilder sollen life geknipst und umgehend auf einem Beamer dargestellt werden. Dazu eignet sich, je nach Kameramodell, bspw. das Programm DSLR Remote Pro. (Nur wenige Digitalkameras sind für diese Art Livebildübertragung geeignet!)
Ein (menschlicher!) Assistent soll nun durch Tastendruck steuern, dass bestimmte Motive nicht klappen (bspw. unscharf oder einfach schwarz werden).
Würde das Wirtsprogramm als Quelltext vorliegen, wäre alles ganz einfach.
Dies ist jedoch unter Windows selten der Fall, und für kommerzielle Software
anscheinend niemals.
Dennoch ist man nicht chancenlos. Dazu gibt es Windows Hooks.
Als erstes wurde mittels Spy++ (beim Visual C++ dazu) untersucht,
welche Art Kindfenster dieses Programm zur Bildausgabe verwendet.
Das Programm verwendet ein Fenster der Klasse "Static"
mit dem
entscheidenden Stilbit SS_IMAGE
.
Dies ist ein Glücksfall, weil die Nachricht zum Setzen des Bildes
bekannt ist: STM_SETIMAGE
.
Diese Nachricht abfangen und ändern ist das Ziel.
Um überhaupt Kode im Kontext des Wirtsprogramms ausführen zu können, muss dieser im Adressraum des Wirtsprogramms erscheinen. Dies erfordert eine DLL.
Es gibt verschiedene Methoden, um eine DLL in einen Wirtsprozess
einzuschleusen. Bei der Verwendung von globalen Windows-Hooks
(SetWindowHookEx()
)
kümmert sich Windows bereits selbst darum, und man hat die wenigste Arbeit.
Solche Hook-DLLs benötigen zwanghaft eine Variable, die über
alle Prozesse sichtbar ist; sie muss sich in einem sog.
shared
-Datensegment befinden.
Die Attribute für das Segment namens .shared
(Name beliebig)
werden mit der Linker-Befehlszeile in den Projekteinstellungen festgelegt:
/section:.shared,rws
Wichtig! Falsche Projekteinstellungen führen zum Windows-Crash!
Um sich beim Versenden von STM_SETIMAGE einzuhängen
(wird ja sicherlich per SendMessage()
oder
SendDlgItemMessage()
verschickt),
wäre der Hooktyp WH_CALLWNDPROC
naheliegend.
Er bewirkt jedoch viel zuviel Filteraufwand.
Wesentlich zielgerichteter ist es, das gewünschte Fenster als Unterklasse
zu erstellen, d.h. die Fensterprozedur anzuzapfen.
Dazu muss man nur das Erzeugen des Fensters abfangen.
Dies erledigt man mit CBT-Hooks (CBT = computer based training,
svw. Lernprogramm). Das heißt, der Hooktyp ist WH_CBT
.
Jetzt muss die Anzapfung nur noch bei dem gewünschten Fenster erfolgen. Die notwendigen Vergleichswerte beschafft man sich mittels Spy++.
Für die Anzapfung der Tastatur wird noch ein prozess-lokaler Hook gesetzt.
Die Anzapfung muss jetzt nichts anderes machen als den Parameter
lParam
zu modifizieren.
Je nach zu ladendem Bild wird mittles LoadImage()
ein Bitmap geladen und der lParam-Parameter ersetzt.
Welche Datei geladen wird, bestimmt sich aus dem letzten Tastendruck,
welcher mit dem Tastatur-Hook (s.u.) erfasst wird.
Da aus der Windows-SDK-Dokumentation nicht hervorgeht, wer das Handle
löschen soll, bin ich davon ausgegangen, dass der Aufrufer von
SendMessage(...,STM_SETIMAGE,...)
ein gültiges Handle solange behalten muss, wie das Bild dargestellt werden soll.
Das heißt, der Aufrufer muss das Bitmap-Handle löschen.
Deshalb wird noch eine entsprechende globale Variable geführt
und das ursprüngliche lParam
-Handle unbeachtet gelassen.
Eigentlich müsste man noch STM_GETIMAGE
filtern und den
ursprünglichen lParam
-Wert zurückgeben.
Die Tastatur-Anzapfung ist simpel.
Wird eine der gewünschten Tasten (numerischer Tastenblock 0..9)
gedrückt, wird eine globale Variable gesetzt und der Tastendruck
„aufgegessen“ – sonst würde das Wirtsprogramm einen Piep ausgeben.
NumLock muss eingeschaltet sein!
DLLs kann man nicht starten!
Vor geraumer Zeit habe ich ein Hilfsprogramm llib.exe
(„LoadLib“)
geschrieben, was eine DLL lädt (oder mehrere) und dann endlos wartet.
llib.exe
kann nur gekillt werden, bspw. mit dem Task-Manager
oder killllib.exe
.
Der Startup-Kode der DLL installiert den Hook; darafhin kriecht die DLL
in jeden laufenden Prozess.
Weil der Shutdown-Kode der DLL beim Killen von llib.exe
aufgerufen wird und dieser den Hook entfernt, zieht sich die DLL umgehend
aus den anderen Prozessen zurück.
Nur den Unterklassen-Kode müsste man von Hand aushängen (fehlt hier).
Ganz ohne DLL-Startprogramm kommt man aus, wenn man der DLL einen Einsprungpunkt verpasst, um diese mit rundll32.exe zu starten. Dies wurde hier nicht bewerkstelligt, sondern bei meiner InpOut32.dll.
Mir ist nicht klar, wie man so ein Programm sinnvoll debugt, außer mit SoftICE.
Denn Visual C++ debuggt ja nur den eigenen Prozess.
Daher habe ich mich darauf verlassen, einigermaßen fehlerfrei zu programmieren,
und habe alle Ausgaben mittels OutputDebugString()
und MessageBeep()
bzw. Beep()
eingebaut.
Um diese Meldungen zu sehen, sollte man das Programm DbgView
(gibt's kostenlos bei Micorosoft) benutzen.
...zum Nachlesen und zum Runterladen. Der gesamte DLL-Quelltext ist nur 130 Zeilen lang.