Aaaargh, Farben bei Win32-Oberflächen sind absolut fubar. Man hat keine Chance, das Erscheinungsbild seiner Anwendung mit moderatem Aufwand anzupassen.
Mit jeder halbwegs vernünftigen API kann man die Farben für Buttons, Text, usw setzen und gut ist. Nicht mit Win32. Hier ist das eine globale, also wirklich systemweite Einstellung –
SetSysColors() beeinflusst alle Fenster, die es im System gibt. So viele globale Dinge wurden prozesslokal, als Win16 zu Win32 wurde – aber das halt nicht.
Ich wollte nicht glauben, dass das eine wirkliche Architekturbeschränkung ist, also habe ich mich mal wieder reingesteigert.
1. WM_CTLCOLORSTATIC und Konsorten
Diese Nachricht schicken Controls an ihren Dialog, bevor sie sich zeichnen. Der Dialog kann dann die Hintergrundfarbe, die Schriftart, die Schriftfarbe, und das Hintergrundbild einstellen. Geil, oder? Neee. Was man nämlich
nicht einstellen kann, sind die Farben der Ränder oder die Farben der Hintergrundflächen (z.B. in Listenauswahlflächen oder Tabhintergründen). Damit die Farbe der Anwendung ändert geht mal garnicht, weil man nur eine Handvoll Flächen erreicht, und der Rest bleibt in Standardfarben.
2. GetSysColor() überschreiben
… beeinflusst natürlich nur die eigenen Zeichenvorgänge, weil der Fenster-Renderer weiter seine eigene Version der Funktion sieht.
3. GetSysColor() hooken
… geht in wenigen Zeilen. Tatsächlich erreicht man jetzt viel mehr Flächen als vorher. Allerdings … immernoch bloß einen Bruchteil des Bildschirms. Windows holt sich die Farben noch von wo anders her …
4. GetSysColor() und GetSysColorBrush() hooken
… erreicht mehr Flächen. Aber Scroll Bars, Frames, Titelleisten usw. werden noch immer mit Standardfarben gezeichnet. Grund: Beim Kompilieren von User32 hat der Optimizer Aufrufe an
GetSysColor() mit Inlining optimiert, also geht nicht jede Farbabfrage durch den Funktionsaufruf, den wir abfangen.
5. Die Datenquelle von GetSysColor() anzapfen
Es gibt einen Zeiger
gpsi in
User32.dll, der an einer festen Adresse vorliegt. Er zeigt auf eine Instanz von
PERUSERSERVERINFO (
ReactOS-Analyse hier) mit quasi allen klassischen User-spezifischen Windows-Einstellungen. Darin befindet sich ein Array mit Farben, aus dem sich
GetSysColor() und
SetSysColors() bedienen (ebenso die Brushes). Da wir darauf zugreifen können, liegt es im Adressraum des Prozesses, und wir können es prozesslokal verändern, oder? Nein.
6. gpsi beschreibbar machen
Der Speicher ist Read-only. Klassisch ruft man
VirtualProtect() auf, um ihn beschreibbar zu machen. Geht hier nicht („falscher Parameter“).
VirtualQuery() verrät, dass es sich um ein Mapping handelt. Man kann das nicht deallokieren (weil es nicht aus
VirtualAlloc() stammt). Tatsächlich ist hier ein Block von 1,5 MiB Kernel-Daten in den Adressbereich des Prozesses eingeblendet. Einzelne Pages sind entweder Read-only, Read-Write, oder garnicht zugreifbar. Einzelne Pages eines Mappings kann man nicht entfernen; man kann höchstens das Mapping als Ganzes aus dem Adressraum schmeißen. (1,5 MiB Kernel-Daten aus dem Prozess löschen – was soll schiefgehen?). Eine Kopie machen, löschen, Speicher neu allokieren (diesmal beschreibbar), Kopie aufspielen – die Farben sind jetzt tatsächlich beschreibbar. Dafür funktioniert kaum eine API-Funktion mehr, und der nächste User32-Aufruf crasht mit gewisser Wahrscheinlichkeit das System. Copy-on-Write wäre die Musterlösung gewesen, aber man kann auch das vergessen – es kommt immer nur
STATUS_INVALID_PAGE_PROTECTION zurück. Nach meiner Recherche ist das eine Hardware-Limitierung, weil die selben Pages mit unterschiedlichen Flags gemappt wären (einmal als Read-Write auf Kernel-Seite und als Copy-on-Write auf Prozess-Seite), und keine CPU mit sowas klarkommt.
Es
ist eine tief im Kernel verwurzelte Limitierung. Man kommt nicht drum herum. Man kann unter Windows die Systemfarben nicht auf Prozessebene ändern.
- zfx2.png (6.21 KiB) 3300 mal betrachtet
- mehr als sowas kann man nicht erreichen
- zfx.png (3.15 KiB) 3300 mal betrachtet
7. Subclassing und User Draw
Bleibt also nur eine einzige beschissene Möglichkeit übrig: das etablierte, wohlbekannte Subclassing. Anstatt bloß Farben zu ändern, muss man den Rendering-Code für
alle Arten von Controls, die es in Windows gibt neu schreiben. Wenn man
das geschafft hat, muss man ihn mit jedem neuen Windows-Release an den neuen Look anpassen.
Dann hat man aber zumindest volle Kontrolle über die Darstellung.