Für interessierte Quelltext-Leser:

Win32-Programme ohne Laufzeitbibliothek

Fast alle meiner Win32-Programme sind ohne Laufzeitbibliothek geschrieben. Die nachfolgende Übersicht gibt einige Hinweise auf die Unterschiede der Programmierung.

Dem Leser sollte Win32-Programmierung bekannt sein, wenn nicht, dann bitte den „Petzold“ lesen!

Ich bin mit Windows-Programmierung in Pascal groß geworden, und habe Objektbibliotheken zu hassen gelernt, weil sie eben nur so tun, als ob sie Komplexität verbergen könnten. Deswegen wird der Leser bei mir kein einziges Wikipedia MFC-Programm finden. Auch wenn es nach außen so (modern) aussehen möge, schon die Dateigröße verrät, dass es kein MFC sein kann.

Generelle Unterschiede:
mit Laufzeitbibliothekohne Laufzeitbibliothek
Minimale Kodegröße 20 KB 2,5 KB
Kopfdateien
#include <stdafx.h>

oder ähnliches
#define WIN32_LEAN_AND_MEAN
#include <windows.h>	// Windows eben
#include <windowsx.h>	// Nützliche Makros
#include <shlwapi.h>	// String-Funktionen
#include <comdlg32.h>	// Datei-öffnen-Dialog
#include <comctl32.h>	// erweiterte Dialogelemente
...
Eintrittspunkt WinMain WinMainCRTStartup
Kommandozeilen-Argumente Argument von WinMain, Programm-Name entfernt
Unicode-Problem! GetCommandLine wird empfohlen
GetCommandLine benutzen, mit PathGetArgs zerstückeln
Programmende return retval ExitProcess(retval)
Nur return killt nicht die durch GetOpenFileName erzeugten Threads!
Compiler-Schalter Projekt-Einstellungen funktionieren, die vorcompilierten Header machen aber meistens nur Ärger
  • /GZ (Stack-Prüfung) entfernen
  • /GF (Konstanten und konstante Strings ins Kodesegment) setzen
  • Release: Optimierung: Größe minimieren
  • Code-Generierung: _stdcall oder _fastcall, bloß nicht _cdecl
  • C++: keine Ausnahmebehandlung
  • neueres C++: Bereichsüberprüfungen ausschalten
  • Wenn dennoch eine Meldung mit „__chkstk“ kommt: /Gs65536 (Stack-Prüfung ab bspw. 64 Kilobyte) setzen (Zahl je nach Bedarf)
Linker-Schalter Projekt-Einstellungen, auf die OLE-Importbibliotheken kann man getrost verzichten, einige andere Importbibliotheken werden des öfteren benötigt:
  • winmm.lib (Multimedia, bspw. sndPlaySound)
  • comctl32.lib (ListView, TreeView, Buttonleiste, Statuszeile...)
  • shlwapi.lib (diverse Mini-Funktionen)
  • hhctrl.lib (Zugriff auf HtmlHelp())
  • gdiplus.lib (GDI+ = Zeichenfunktionen mit Antialiasing)
  • Bibliotheken wie links angegeben
  • Alle Standardbibliotheken igonorieren (/nodefaultlib)
  • Bei Notwendigkeit von Win32s (Lauffähigkeit unter Windows 3.1x) Relokationstabelle erzeugen (macht die .EXE größer)
  • Den Schalter /opt:nowin98 angeben: die EXE wird kleiner und läuft dennoch unter Win98/Me
  • Den möglichen 3GB Adressraum sollte man immer aktivieren: Schalter /largeaddressaware angeben
  • Release: Warum nicht die Header-Prüfsumme generieren lassen? /release angeben
  • /merge:.rdata=.text, 2 Sections kombinieren, Linker-Warnung ignorieren, oder
    /section:.text,e, Kodesegment auf Execute-Only setzen (mehr Sicherheit vor Bugs, ändert die Programmgröße nicht)
  • Stack-Allokation gleich /Gs-Compilerschalter oder größer setzen
Die Verwendung von #pragma-Anweisungen ist nicht portabel hin zum neueren MSVC9! Deshalb besser im Projekt setzen.
Diese Tabelle spiegelt nur den Unterscheid bei Microsoft Visual C++ (ab Version 6 geprüft) wider. Bei Borland C++ gelten andere Regeln!

Konstanten im Kodesegment (/GF) ersparen initialisierte statische Variablen. (Alle nicht initialisierten statischen Variablen werden vom EXE-Loader stets mit Null initialisiert, muss man wissen. Es ist ohnehin unschöner Programmierstil, mit Nicht-Null initialisierte statische Variablen nicht const zu setzen. Bei der Verwendung von extern unicows.lib bzw. libunicows.lib kommt man um solche Variablen nicht herum.) Den Erfolg der Maßnahme kann man mittels dumpbin /headers prüfen: das Datensegment (.data) muss in der EXE-Datei leer sein.

SECTION HEADER #3
   .data name
     6F4 virtual size
    8000 virtual address (00408000 to 004086F3)
       0 size of raw data
       0 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         Read Write

Ohne Standardbibliothek kann man nicht „portabel“ im Sinne des ANSI-Standards programmieren. Aber kein Windows-Programm ist jemals ANSI-konform. Es gibt kein main, kein printf. (Wohin denn?) Muss ich weiter argumentieren? (Ich rede hier nicht von Konsolen-Programmen.)

Also, Portabibiltät ist etwas für den Elfenbeinturm (Informatiker, Lehre) oder für Unix (Linux - da kommt man nicht herum!). Für Windows: da sage ich einfach Tschüss und winke-winke!
Jetzt muss man nur noch wissen, was man statt der gewohnten Funktionen verwenden muss. Ganz nebenbei wird das Programm unicode-fähig, ohne jedesmal den TCHAR-Präfix (bspw. _tprintf statt printf) verwenden zu müssen (was ja auch nicht ganz portabel ist).

Hier eine kurze Übersicht
mit Laufzeitbibliothekohne Laufzeitbibliothek
Kommandozeilen-Argumente via strchr zerstückeltPathGetArgs einsetzen!
String-Manipulation strlenlstrlen
strcpylstrcpy, besser lstrcpyn
strcatlstrcat, StrCat, besser StrCatBuff
strchr bzw. strrchrStrChr bzw. StrRChr
strstr bzw. strrstrStrStr bzw. StrRStr
strpbrkStrPBrk
strspnStrSpn
strdupStrDup
sprintfwsprintf, besser wnsprintf (kümmerlich, kein Gleitkomma)
vsprintfwvsprintf, besser wvnsprintf (kümmerlich, kein Gleitkomma)
sscanfkein Ersatz! Leider. Mit StrToInt ersetzen
itoawsprintf
atoiStrToInt, StrToIntEx (für Hexadezimalzahlen ein "0x" davorsetzen!)
shlwapi.h bietet noch eine Reihe weiterer nützlicher String-Funktionen, insbesondere zur Behandlung von Dateinamen.
(Leider arbeitet PathFindExtension bei Leerzeichen sowie bei Unix-Dateinamen wie .htaccess fehlerhaft: selber schreiben!)
Speicher malloc, calloc, newLocalAlloc (C++-Operator kann überladen werden)
free, deleteLocalFree (C++-Operator kann überladen werden)
memset, ZeroMemory, FillMemorySelber schreiben:
void _declspec(naked) _fastcall FillMemB(size_t l,void*d,BYTE fill){ _asm{
	mov	eax,[esp+4]
	xchg	edi,edx		// Zeiger in EDX
	rep	stosb		// Länge in ECX, keine Aktion bei ECX=0
	mov	edi,edx		// EDI restaurieren
	ret	4
}}
#define FillMemory(d,l,f) FillMemB(l,d,f)
#define ZeroMemory(d,l) FillMemB(l,d,0)
oder Windows-Funktionen verwenden (lassen):
#undef RtlZeroMemory
EXTERN_C void _declspec(dllimport) WINAPI RtlZeroMemory(PVOID,SIZE_T);
#undef RtlFillMemory
EXTERN_C void _declspec(dllimport) WINAPI RtlFillMemory(PVOID,SIZE_T,BYTE);
memcpy, CopyMemory,
memmove, MoveMemory
Selber schreiben:
void _declspec(naked) _fastcall MoveMemB(size_t l, void*d,const void*s){ _asm{
	mov	eax,[esp+4]	// Quelle auf Stack
	xchg	esi,eax
	xchg	edi,edx		// Ziel in EDX
	cmp	esi,edi
	jnc	l1
	lea	esi,[esi+ecx-1]
	lea	edi,[edi+ecx-1]
	std			// abwärts
l1:	rep	movsb		// Länge in ECX, keine Aktion bei ECX=0
	cld
	mov	edi,edx
	mov	esi,eax		// Register restaurieren
	ret	4
}}
#define CopyMemory(d,s,l) MoveMemB(l,d,s)
#define MoveMemory(d,s,l) MoveMemB(l,d,s)
oder Windows-Funktionen verwenden (lassen):
#undef RtlMoveMemory
EXTERN_C void _declspec(dllimport) WINAPI RtlMoveMemory(PVOID,const VOID*,SIZE_T);
#undef CopyMemory
#define CopyMemory MoveMemory
Hinweis: Einige Funktionen mehr sind in der kernl32p.lib verfügbar, statt der standardmäßigen kernel32.lib.
oder „inlinen“ lassen:
#pragma inline(memcpy,memset)
memchr, wmemchrSelber schreiben:
BYTE* _declspec(naked) _fastcall ScanMemB(size_t l,const BYTE*s,BYTE c){ _asm{
	mov	eax,[esp+4]
	xchg	edi,edx
	repne	scasb
	lea	eax,[edi-1]
	mov	edi,edx		// EDI restaurieren
	jz	l1
	xor	eax,eax		// NULL liefern wenn nicht gefunden
l1:	ret	4
}}
#define memchr(s,c,l) ScanMemB(l,s,c)
memcmp, wmemcmpSelber schreiben:
int _declspec(naked) _fastcall CmpMemB(size_t l,const BYTE*d,const BYTE*s){ _asm{
	jecxz	l1		// undefiniertes Ergebnis bei Länge 0
	mov	eax,[esp+4]
	xchg	esi,eax
	xchg	edi,edx
	repe	cmpsb
	movsx	edi,byte ptr [edi-1]
	movsx	esi,byte ptr [esi-1]
	sub	esi,edi
	mov	edi,edx
	xchg	esi,eax
l1:	ret	4
}}
#define memcmp(d,s,l) CmpMemB(l,d,s)

Die ähnliche Kernel-Funktion RtlCompareMemory ist bei Win32 nicht in kernel32.lib enthalten.
Formatierte Datei-Ein/Ausgabe fprintfzusammensetzen aus wvnsprintf, CharToOemBuff und WriteFile (oder WriteConsole)
Niemals printf mit Umlauten oder sonstigen lokalisierbaren Strings verwenden!
fscanfReadFile benutzen
Gleitkomma sin, cos ...Selber schreiben: natürlich Koprozessor nutzen!
Irgendwo braucht es im Programm ein leeres struct{}_fltused (C++: extern "C" int _fltused=0), um den Linker ruhig zu stellen.

Hier könnte man noch beliebig fortsetzen.
Bei Einsatz von Gleitkomma bei Ein- und Ausgabe ist es besser, ein funktionierendes sprintf() und sscanf() zu haben, diese beschafft man sich wie folgt:

EXTERN_C int (_cdecl*_stprintf)(PTSTR,int,PTSTR,...);
EXTERN_C int (_cdecl*_stscanf)(PTSTR,PTSTR,...);
#ifdef UNICODE
# define __IMP__STPRINTF "_snwprintf"
# define __IMP__STSCANF "swscanf"
#else
# define __IMP__STPRINTF "_snprintf"
# define __IMP__STSCANF "sscanf"
#endif

// irgendwo in der Programminitialisierung:
 HINSTANCE hLibMSVCRT=LoadLibraryA("MSVCRT.DLL");
 (FARPROC)_stprintf=GetProcAddress(hLibMSVCRT,__IMP__STPRINTF);
 (FARPROC)_stscanf=GetProcAddress(hLibMSVCRT,__IMP__STSCANF);

oder, noch einfacher, durch eine spezielle Importbibliothek [Einsicht]. Denn die MSVCRT.DLL ist seit Windows 95 standardmäßig installiert, die braucht man weder mitzuliefern noch deren Funktionen statisch einzubinden.

Oftmals will der Linker eine Funktion __ftol oder __ftol2_sse. Siehe hier:

long _declspec(naked) _cdecl _ftol(void){ _asm{
	push	eax
 	fistp	dword ptr [esp]
	fwait
	pop	eax
	ret
}}

Meine immer wiederkehrenden Unterprogramme

Manchmal habe ich den Eindruck, dass Microsoft einige immer wieder benötigte Funktionen und Makros verschläft zu implementieren. Die 1997 aufkommende shlwapi.dll war da schon ein kleiner Durchbruch, lieferte diese doch endlich die fehlenden String-Funktionen nach.

Immer noch fehlen Funktionen für Gleitkomma-Ein/Ausgabe, obwohl dieses Zahlenformat ins Win32-GDI eingezogen ist (siehe SetWorldTransform) und bei GDI+ für exakte Positionierung kantengeglätteter Figuren äußerst nützlich ist.

Kopfdateien und defines
WIN32_LEAN_AND_MEANlässt einigen unnötigen Windows-3.0-Ramsch weg. Nicht verwenden bei Verwendung von GDIplus!
STRICTSchaltet die strenge Typprüfung auch bei Win16 ein.
Wichtig für den klassischen Bug bei ReleaseDC: Man konnte HWND und HDC vertauschen, und irgendwann (viel später) krachte es im GDI, weil die Handles alle waren.
<windowsx.h>Nützliche Makros, ersparen eine Unmenge SendMessage, und man kann kombinierte Win16/Win32-Quelltexte schreiben
Makros
T("string")wie TEXT("string"), erzeugt je nach UNICODE "string" oder L"string"
elemof(array)liefert Anzahl der Elemente eines Arrays (Feldes) bekannter Länge, bspw. die Anzahl der Zeichen eines TCHAR-Feldes
nobreakexpandiert zu nichts und dient zur Kennzeichnung absichtlich „vergessener“ break bei switch + case
Funktionen
InitStructFüllt eine Struktur mit Nullen, aber das erste UINT mit der Längenangabe. Zweckmäßig für eine große Zahl von Windows-Funktionen.
EnableDlgItemVermählung von EnableWindow und GetDlgItem
ShowDlgItemVermählung von ShowWindow und GetDlgItem
GetRadioCheckDas Gegenteil von CheckDlgButton
SetCheckboxGroupBitfeld in eine Reihe von Auswahlfeldern setzen
GetCheckboxGroupBitfeld aus einer Reihe von Auswahlfeldern lesen
SetEditFocussollte aufgerufen werden, wenn bei WM_COMMAND - IDOK ein fehlerhaftes Eingabefeld vorgefunden wird: der Text wird markiert
MoveRectIntoRect
MoveRectIntoFullScreen
(Fenster-)Rechteck in ein anderes (Bildschirm-)Rechteck schieben, zur Verhinderung deplatzierter Fenster.
(Wer kennt nicht die Programme mit klassischen Bugs? Win3.1 CLOCK.EXE, um mal anzufangen, WinAmp…)
LimitInteger-Zahl zwischen zwei Grenzen eingrenzen
iitrafoInteger-Zahl „transformieren“, bspw. Messwert nach Zweipunktabgleich
bsf,bsr„bit scan forward“, „bit scan backward“, so heißen die Assemblerbefehle, die hier nachgebildet werden: nieder- bzw. höchstwertigstes Bit suchen
vMBox, MBoxMessageBox mit String aus Resource und statischem Titel
GetFileNameExtZeiger auf Datei-Erweiterung liefern, funktioniert auch bei Unix-Dateien wie .login (keine Erweiterung!)
PathFindExtension() ist leider unbrauchbar.

Das fehlende typeof-Makro kann ich leider nicht nachliefern…

Ressourcen - ein ewiges Drama mit MSVC

MSVC enthält eher einen RC-Zerstörer als einen -Editor.

Verglichen mit Borland Resource Workshop, den es aber nur bis BC5 gab. Den Quelltext kann man jedenfalls nach Angriff mit MSVC keinem Menschen anbieten.

Deshalb ein [Wikipedia] AWK-Skript, diese Dateien wieder menschenlesbar zu machen. Für meine Web-Seiten, und für kleinere .RC-Dateien: nice_rc.zip [Einsicht]

Mehrsprachen-Ressourcen scheinen nie ordentlich zu funktionieren. So beschweren sich Amerikaner, dass meine zweisprachigen (englisch+deutsch) Programme selbst unter XP überraschend in Deutsch erscheinen. Ich habe hierzu keine gute Lösung. Vielleicht Windows Commander zum Vorbild nehmen und nur Texte übersetzen? Das Problem hierbei sind Dialoge im Rechts-nach-Links-Stil (arabisch).

Manifest-Dateien und -Ressourcen

Marx hat das Manifest erfunden, dachte ich so(?) - und Microsoft erfindet es einfach noch einmal. In Form von verballhorntem XML.

Damit hat es Microsoft wieder mal geschafft, gestandene Programmierer so richtig zu ärgern. Und alte Programme alt aussehen zu lassen.
Wie damals schon beim Übergang von 16-bit-Programmen nach Windows95. Als Heerscharen von Programmieren damit beschäftigt waren, OFN_LONGNAMES – äh, 0x200000L – in die Flags bei GetOpenFileName nachzureichen. Und DS_3DLOOK – äh, 0x0004 – in jede Dialogresource. Und so weiter.

Für alle, die nicht wissen, worum es geht: Gewöhnliche, nicht speziell für Windows XP geschriebene Programme bekommen bei aktivierter „Teletubbie-Optik“ zwar einen schicken Fensterrahmen (über Geschmack lässt sich streiten), aber das Dialog-Innere sieht altbacken aus. Es sei denn, man legt neben die Echse eine „dateiname.exe.manifest“-Datei mit magischem Inhalt. Oder steckt diese in die Ressource: Typ: RT_MANIFEST = 24 (nicht als Text stehen lassen!), ID: CREATEPROCESS_MANIFEST_RESOURCE_ID = 1. Womit die Echse zwangsläufig größer wird. Eigentlich nur für ein einziges Bit: „Mach mich schick.“

Zeile in projektname.RC-Datei
1 24 projektname.manifest

Der Inhalt der Manifest-Datei ist immer der gleiche:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
 <dependentAssembly>
  <assemblyIdentity
   type="win32"
   name="Microsoft.Windows.Common-Controls"
   version="6.0.0.0"
   processorArchitecture="*"
   publicKeyToken="6595b64144ccf1df"
   language="*"
  />
 </dependentAssembly>
</dependency>
</assembly>
Den sonst üblichen Programmnamen und die Programmbeschreibung kann man weglassen. Mit processorArchitecture="*" hält man sich 64-bit-Probleme vom Hals.

Was auch immer Microsoft mit Manifest-Dateien eigentlich wollte (irgendwas mit C# und Microsoft Forms, ich habe es nicht verstanden), ich stecke jetzt immer eine Manifest-Datei ins Projekt und stecke sie in die Ressource. Damit sind die Echsen etwas größer, sehen aber modern aus. Für jemanden, der Teletubbie-Optik nie eingeschaltet hat (oder wie ich Windows 2000 oder noch älter benutzt), nur sinnlose Luft. Besonders unverständlich für mich ist die Auswirkung der Namespace-Elemente (xmlns). Kann man den Quatsch vielleicht weglassen, oder spinnt dann irgendeins der Microsoft-Betriebssysteme?

Besonderheiten ab Vista

Ab Windows Vista und 7 sind noch weitere must-have-Einträge hinzu gekommen. Macht zusammen 1 Kilo Manifest trotz dieser dichten Packung. Für gesittete Programme (die keine Admin-Rechte benötigen) ist am besten folgendes (habe ich teilweise vom Total Commander abgeguckt):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/></application></compatibility>
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"><asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"><dpiAware>true</dpiAware></asmv3:windowsSettings></asmv3:application>
<dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency></assembly>
Der beliebte Registry-Schlüssel HKEY_LOCAL_MACHINE ist praktisch tabu. Dieser Schlüssel wird für Programme mit normalen Benutzerrechten weg-virtualisiert, und die Daten gelangen an eine unschöne, schwer aufzufindende Stelle. Für Ordnung in der Registrierung sollte man daher alle Einstellungen schön brav nach HKEY_CURRENT_USER speichern. Maschinenspezifische Einstellungen wie etwa die Portadresse einer Schnittstellenkarte lassen sich faktisch nur sehr umständlich (in einer INI-Datei; das Programme-Verzeichnis kann aber schreibgeschützt sein) speichern, für Ordnung und Einfachheit tue ich's ebenfalls zu HKEY_CURRENT_USER.
Diese Einstellungen sind „umziehend“, was bspw. für Portadressen Quatsch ist. Hier hilft eventuell das (missbräuchliche?) Speichern nach HKEY_CLASSES_ROOT als benutzerspezifische, aber nicht „umziehende“ Daten.

Manifest-Ressourcen für DLLs, genauer: Eigenschaftsseiten-Lieferanten („Property Sheet Provider“)

Im konkreten Fall ging es darum, eine Erweiterung für MMC.EXE, genauer, dem Gerätemanager (Aufruf einfach mit devmgmt.msc) modern aussehen zu lassen. Laut Microsoft-Dokumentation muss man wie folgt vorgehen: Lässt man auch nur einen Schritt weg, bleibt alles altbacken. Warum eigentlich dieses Mehr an Aufwand? Ist mir total unverständlich.

[Screenshot] [Screenshot]
Alt und neu zusammen in einem Fenster sieht eben nicht toll aus! Man beachte vor allem die unterschiedlichen Knöpfe

Das ISOLATION_AWARE_ENABLED bewirkt ein Aufblähen des Kodes und den Zwang zur Laufzeitbibliothek! Dies wird seitens Microsoft über *.inl-Dateien erreicht, das sind Kopfdateien mit einer Menge Inline-Code. Dieser Inline-Code ist absichtlich kryptisch gehalten und damit kaum lesbar! Außerdem werden so unsinnige Sachen gemacht wie UNICOWS.DLL zu laden.
UNICOWS.DLL ist die Hauptdatei für Unicode-Unterstützung unter Windows 98/Me, aber diese Systeme haben mit Manifest-Ressourcen eh' nichts am Hut!

Der Schalter ISOLATION_AWARE_ENABLED ist also für mich völlig inakzeptabel, und so habe ich mich so lange durch die kryptischen *.inl-Dateien gewurstelt, bis nur noch der wirklich benötigte Kode übrig blieb. Das ist nun erfreulich wenig.

Einschalten der Teletubbie-Optik

Irgendwie braucht man dazu immer einen Aktivierungskontext hActCtx.
Beschaffen des Aktivierungskontextes
HANDLE hActCtx;		// „Aktivierungskontext“, das Ding mit der Teletubbie-Optik

BOOL APIENTRY _DllMainCRTStartup(HANDLE hModule, DWORD reason, LPVOID lpReserved) {
 switch (reason) {
  case DLL_PROCESS_ATTACH: {
   ACTIVATION_CONTEXT_BASIC_INFORMATION actCtxBasicInfo;
   BOOL (WINAPI*pQueryActCtxW)(DWORD,HANDLE,PVOID,ULONG,PVOID,SIZE_T,SIZE_T*);
   HANDLE hKernel;
   hInst=(HINSTANCE)hModule;
   DisableThreadLibraryCalls(hModule);
   InitCommonControls();
   hKernel=GetModuleHandle(T("KERNEL32.dll"));
   (FARPROC)pQueryActCtxW=GetProcAddress(hKernel,"QueryActCtxW");
   if (pQueryActCtxW
   && pQueryActCtxW(	// ziemlich undokumentiert
     QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS|QUERY_ACTCTX_FLAG_NO_ADDREF,
     &hActCtx,		// irgendeine Adresse
     NULL,
     ActivationContextBasicInformation,
     &actCtxBasicInfo,
     sizeof(actCtxBasicInfo),
     NULL)
   && actCtxBasicInfo.hActCtx!=INVALID_HANDLE_VALUE)
     hActCtx=actCtxBasicInfo.hActCtx;	// <hActCtx> nie freigeben, da ohne AddRef!
  }break;
 }
 return TRUE;
}
Der dynamische Einsprungpunkt via GetProcAddress() wird benötigt, damit die DLL auch unter Windows 2000 ladbar ist.
Der Trick bei Win16, mit UndefDynLink zur Laufzeit zur vergleichen, klappt bei Win32 nicht.
Wo (und ob?) InitCommonControls() aufgerufen wird, ist irrelevant. Beim Debuggen sieht man, dass InitCommonControls() zu nichts weiter als einem ret-Befehl führt.

Bei einer 64-bit-DLL kann man sich das Beschaffen des Einsprungpunktes zur Laufzeit ersparen, da es 64-bit-Editionen von Windows erst ab Windows XP gibt.
(Ja, gibt es! Wenn auch nur in englisch. Die meisten kennen das erst ab Windows Vista.)

Aktivieren der Teletubbie-Optik
Normalerweise kann man die Aktivierung mittels ActivateActCtx() während des Programmlaufs bewirken. Das klappt aber nicht mit Eigenschaftsseiten („Property Sheets“)! Da verreckt irgendwo MMC.exe, der Gerätemanager.

Eigenschaftsseiten verlangen ein besonderes Vorgehen:

 HPROPSHEETPAGE hpage;
 PROPSHEETPAGE page;
 …				// die normale Initialisierung
 page.dwFlags = … |PSP_USEFUSIONCONTEXT;
 page.hActCtx = hActCtx;	// Die erweiterte PROPSHEETPAGE-Struktur steckt in der Kopfdatei des XP-SDKs oder -DDKs
 hpage = CreatePropertySheetPage(&page);
Die übrigen Fenster, wie Unter-Dialoge oder Messageboxen bekommen automatisch Teletubbie-Optik, da braucht man sich nicht mehr zu kümmern. Keine Wrapper mit ActivateActCtx() und DeactivateActCtx(). Und, das ist sehr nett, keine weiteren dynamischen Einsprungpunkte.

Zu bedenken ist, dass das Hervorheben bestimmter Dialogelemente durch Farbe, insbesondere bei Checkboxen, nicht mehr mittels SetTextColor() innerhalb von WM_CTLCOLORSTATIC möglich ist. Die einfachste Lösung ist es, derartige Dialogelemente mittels SetWindowTheme() in ihr altes Aussehen zurückzuverwandeln.

Inzwischen ist mir der Sinn von Manifests (zumindest der <dependency>-Abschnitt) etwas klar geworden:


Henrik Haftmann, 09.03.2012