Die Unsitte immer GetModuleHandle(NULL) für hInstance in CreateWindow und RegisterClass zu verwenden

Carsten hat mich dazu inspiriert noch ein wenig mehr Don Quichotte zu spielen und gegen Windmühlen zu kämpfen: Damit hier mein zweiter Beitrag zum Thema Unsitten.

Der hInstance Parameter in CreateWindow(Ex) und RegisterClass (WNDCLASS) wird oft genug nicht verstanden. Man braucht für diese Funktionen einen HINSTANCE Wert. Den hat aber niemand in der Tasche, wenn man mit der Win32 API pur mal eben so ein Programm schreibt. Die wenigsten kommen auf die Idee den hInstance Wert aus WinMain und DllMain irgendwo global zu speichern und zu verwenden. Globals sind ja irgendwie „böse“, und nicht OOP-like… 😉

Was also tun? Irgendein Unwissender empfiehlt einfach GetModuleHandle(NULL) zu verwenden und seit dem dieser Unwissende diesen Tipp in die Welt gesetzt hat kursiert er durch die Foren… unaufhaltsam…

❗ Das Problem: Es geht… manchmal… aber portabel ist der Code damit nicht und man erlebt eigentümliche Sachen damit an anderer Stelle unter annderen (DLL-)Umständen. Aber warum?

Das Geheimnis steckt darin, dass Fensterklassen nicht einfach so unter ihrem Namen abgelegt werden, sondern unter einem Namen und der hInstance, die bei RegisterClass mit angegeben wird. So hat jeder Prozess seine Liste der Fensterklassen die aus hInstance und dem Namen der Klasse bestehen. Auch die Standard Fensterklassen wie Edit, Button, Static etc. werden mit der hInstance der User32.dll gespeichert.

Wird nun ein Fenster erzeugt mit CreateWindow(Ex), dann benutzt der Windowsmanager die Kombination aus hInstance Wert, der bei CreateWindow(Ex) angegeben wird und dem Namen der Klasse um den entsprechenden Klassen Eintrag zu finden.

Es ist also vollkommen OK wenn DLLs selber Klassen registrieren mit ein und unterschiedliche Module den selben Namen verwenden. Da gibt es keinen Konflikt, denn durch die hInstance Werte bleiben die Klassen eineindeutig. Und auch jede DLL kann damit, dass von Ihr gewünschte Fenster mit der entsprechenden Klasse erzeugen, denn jede DLL hat eine unterschiedliche hInstance.

Einzige Ausnahme ist eine Klasse, die mit dem Klassenstil CS_GLOBALCLASS registriert wird. Bei solch einer Klasse wird der Windowsmanager nur auf den Namen und nicht auf den Wert der hInstance achten. Jedem wird klar sein, dass die Standardfensterklassen der USER32.DLL und auch die Klassen der COMCTL32.DLL mit diesem Stil registriert wurden.
Und klar ist auch, dass man seine eigenen Klassen, die in DLLs liegen mit diesem Flag CS_GLOBALCLASS registrieren muss, wenn man diese Klassen applikationsweit verwenden will. Würde man also ein Dialogtemplate mit einer eigenen Klasse anlegen, die in einer DLL liegt, so kann der Dialogmanager, der wieder hInstance der Applikation verwendet, die entsprechende Klasse nicht finden, wenn diese nicht mit CS_GLOBALCLASS registriert wurde.

Es ist und bleibt eine Unsitte GetModuleHandle(NULL) beim Registrieren der Windowsklasse zu verwenden, denn dieses hInstance, das man erhält ist natürlich das hInstance der Applikation, und nicht das des Modules, welches die Klasse enthält, z.B. eben eine DLL. Es wundert nicht wenn man etwas später seine Klassen in eine DLL auslagert erstaunt feststellt, dass auf einmal die Sachen nicht mehr funktionieren wie sie sollen.

Um solchen Problemen aus dem Weg zu gehen sollte man immer das hInstance verwenden, das zu dem entsprechenden Modul gehört. Diesen Wert erhält man durch WinMain oder DllMain. Am Besten man speichert diesen in einer globalen Variablen. Die MFC hat hierzu eine spezielle Funktion AfxGetInstanceHandle, die dies für das entsprechende Modul erledigt. Aber ein MFC Programmierer würde ja auch AfxRegisterClass verwenden und nicht in diese Probleme laufen, wiederum vorausgesetzt er verwendet auch brav AFX_MANAGE_STATE 🙂

❗ Anmerkung: Es gibt auch Code, der einem erlaubt das hInstance Handle zu einem Code Abschnitt zu bestimmen.
http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx (sofern man einen MS-Linker verwendet)

Auch der folgende etwas trickreichere Code ist einen Blick wert:

HINSTANCE Get_hInstance()
{
 MEMORY_BASIC_INFORMATION mbi; 
 VirtualQuery(Get_hInstance, &mbi, sizeof(mbi)); 
 return reinterpret_cast<HINSTANCE>(mbi.AllocationBase); 
}

Aber bitte diesen Code nicht in eine DLL auslagern :mrgreen:

Die Unsitte PostQuitMessage zum Beenden eines Programmes zu verwenden!

Immer wieder lese ich Postings in http://www.c-plusplus.de/forum die es anpreisen ein Programm mit PostQuitMessage zu beenden, genau so unsinnig wie WM_QUIT zu versenden. 

Das ist natürlich Unfug! Sicherlich wird ein Programm durch PostQuitMessage beendet, aber warum?
Weil die Nachrichtenschleife verlassen wird und letzten Endes WinMain verlassen wird. Dies führt dazu, dass die darunter liegenden CRT Routinen irgendwann ExitProcess ausführen. BTW: Würde hier nur ein der CRT einfacher return erfolgen, dann würde der Prozess weiterleben, wenn noch ein einziger anderer Thread aktiv wäre.

Das brutale Verlassen führt aber letzten Endes auch dazu, dass erst ExitProcess brutal alle Fenster aufräumt. D.h. kein Fenster wird normal zerstört, kein WM_DESTROY bzw. WM_NCDESTROY wird empfangen. D.h. alle normalen Prozesse, die dem Aufräumen und Freigeben von Ressourcen dienen, werden außer Kraft gesetzt.
Ja und sicherlich gibt ExitProcess Speicher frei, die der Prozess alloziert hat, auch einige Handles können freigegeben werden, aber nicht alle (z.B. benamte Mutexe und Semaphoren).

Bei einem Mikey Mouse Win32 API Programm mag dies kein Problem sein, denn hier gibt es keine Ressourcen, die Prozessübergreifend ein Leak verursachen würden, oder eine Ressource blockieren würden.
Aber grundsätzlich würde ich es unterlassen. Jede andere Library, die man verwendet, jedes externe Control, dass man einbindet könnte genau auf dieses entscheiden WM_DESTROY angewiesen sein um Ressourcen freizugeben, die ein System blockieren könnten. Solange man nicht 100%ig weiß wie die benutzen Bibliotheken arbeiten, ja nicht einmal detailliert weiß wie COM und die CRT Handles behandeln würde ich grundsätzlich abraten ein Programm einfach mit ExitProcess zu verlassen, genau so wie ich abrate TerminateProcess zu verwenden.

Der richtige Weg ist und bleibt es das/alle Main Window(s) zu zerstören und entsprechend dann (im WM_DESTROY Handler) PostQuitMessage (AfxPostQuitMessage) auszuführen. Durch das Zerstören des Hauptfensters werden natürlich alle enthaltenen Child-Windows mit zerstört. Alle Fenster bekommen damit die Chance hinter sich aufzuräumen und Ressourcen frei zu geben.

PS: Aber solche Unsitten lassen sich kaum ausmerzen. Genauso wenig wie die Unsitte einen HINSTANCE Wert einfach durch Aufruf von GetModuleHandle(NULL) zu bestimmen… Auch eine Unsitte, die wohl niemand mehr ausmerzen wird.

CreateThread und die CRT

Immer wieder sehe ich Code Snippets die CreateThread verwenden.
Immer wieder antworte ich gleichlautend:

Vermeide CreateThread, wenn Du CRT Funktionen verwendest, das erzeugt Leaks in der CRT.
Verwende deshalb immer nur _beginthread(ex). ❗

Irgendwie glauben viele Programmierer, dass es besser ist eine möglichst OS-nahe Funktion zu verwenden. In diesem Fall ist es es definitiv nicht ratsam, denn droht in diesem Fall ein Speicherleck von um die 530Bytes (in VC-2005) pro beendetem Thread.

In Kürze: Das ganze Problem liegt darin, dass bestimmte CRT Funktionen thread lokalen Speicher benötigen. Dieser wird im TLS (Thread Local Storage) bei Bedarf angelegt und entsorgt wenn die Threadfunktion returniert oder mit _endthread beendet wird.
Gleiches gilt, wenn der Thread mit ExitThread oder TerminateThread beendet wird! (Das TerminateThread sowieso nicht in Frage kommen darf weil es noch übleres tut weiß ja hoffentlich sowieso jeder 😉 )

Hier eine entsprechende Sammlung der entsprechenden Dokumentationen mit den Begründungen: 

Jeffrey Richter „Advanced Windows“ 3rd Edition
Kapitel 4, „Processes, Threads and the C Run-Time Library“ Seite 108 folgende.

CreateThread Doku: http://msdn2.microsoft.com/En-US/library/ms682453.aspx

A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multi-threaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.

ExitThread Doku: http://msdn2.microsoft.com/en-us/library/ms682659.aspx

A thread in an executable that is linked to the static C run-time library (CRT) should use _beginthread and _endthread for thread management rather than CreateThread and ExitThread. Failure to do so results in small memory leaks when the thread calls ExitThread. Another work around is to link the executable to the CRT in a DLL instead of the static CRT. Note that this memory leak only occurs from a DLL if the DLL is linked to the static CRT and a thread calls the DisableThreadLibraryCalls function. Otherwise, it is safe to call CreateThread and ExitThread from a thread in a DLL that links to the static CRT.

KB-Artikel 104641: http://support.microsoft.com/kb/104641/en-us

Threads that are created and terminated with the CreateThread() and ExitThread() Win32 API functions do not have memory that is allocated by the CRT for static data and static buffers cleaned up when the thread terminates. Some examples of this type of memory are static data for errno and _doserrno and the static buffers used by functions such as asctime(), ctime(), localtime(), gmtime(), and mktime(). Using CreateThread() in a program that uses the CRT (for example, links with LIBCMT.LIB) may cause a memory leak of about 70-80 bytes each time a thread is terminated.

To guarantee that all static data and static buffers allocated by the CRT are cleaned up when the thread terminates, _beginthreadex() and _endthreadex() should be used when creating a thread. The _beginthreadex() function includes the same parameters and functionality as CreateThread().

Note It is not possible to terminate a thread with _endthreadex() when it was created with CreateThread(). ❗

❗ Anmerkung: Dieser KB artikel ist leider unvollständig. Hier werden nur ein paar time Funktionen aufgelistet, es trifft aber weitaus mehr Funktionen wie errno, strtok, strerror, tmpname, tmpfile, _ecvt, _fcvt, alle Funktionen die _errno oder _doserrno nutzen etc.

Warum eigentlich CallWindowProc aufrufen, wenn man einen Zeiger auf die alte WndProc hat?

Wenn man mit GetWindowLong(Ptr) und GWL_WNDPROC die Adresse einer Fenster Prozedur ermittelt hat, warum muss man eigentlich CallWindowProc aufrufen und nutzt nicht direkt den Zeiger? Geht das denn überhaupt? 😕

Nein es geht nicht und man sollte es gar nicht versuchen ❗

In Win16 ging das noch. Aber das was durch GWL_WNDPROC geliefert wird ist seit Einführung von Win32 nicht unbedingt ein Funktionszeiger mehr.  Oft ist es eine Struktur.
Warum? Das ganze wurde gemacht um Unicode Kompatibilität zu erreichen.
Fenster sind nicht nur Thread afin, nein, sie sind auch Unicode bzw. MBCS afin (wenn man das so sagen kann). Je nachdem ob es eben mit CreateWindow(Ex)A oder CreateWindow(Ex)W erzeugt wurde.

Wenn also ein Unicode Fenster von einer Nicht-Unicode Fenster-Prozedur gesubclassed wird (oder umgekehrt), dann muss hier eine Konvertierung stattfinden. Seit dem wir Themed Style mit XP bekommen haben, tritt dies übrigens häufig auf. Denn die Fenster im XP-Stil werden meistens intern als Unicode Fenster verwaltet oder angelegt.
Für diese Konvertierungsarbeit wird eine Struktur angelegt und diese Struktur wird dann in GWL_WNDPROC eingetragen. Dann haben wir eben keinen Funktionszeiger mehr, sondern eher ein Handle. Und nur CallWindowProc weiß wie man eben damit umzugehen hat…

Details hier in dem Artikel Safe Subclassing in Win32

Gibt es einen Unterschied zwischen HMODULE und HINSTANCE?

Ja! Wenn man noch Windows 16bit programmiert. 😉

Nein! Wenn es sich um Win32 dreht.

In Kurzfassung:
❗ Seit Win32 ist HMODULE/HINSTANCE nichts anderes als die Ladeadresse des Modules, egal ob es sich hier um eine EXE oder DLL handelt.
HMODULE und HINSTANCE sind austauschbar in jeder Beziehung.

Wer mehr darüber lesen will findet bei The Old New Thing, diesen Artikel der den historischen Aspekt erläutert.

Wie eine Gruppe von Radio-Buttons ein Programm aufhängen können

Auto-Radiobuttons (BS_AUTORADIO) sind als Standard in fast jedem Dialog zu finden. Das diese netten kleinen Buttons ein Programm dazubringen können sich aufzuhängen, ohne das unbedingt eigener Code im Spiel ist, kann man sich kaum vorstellen.
Aber es kann passieren.

Ein Regular in der Gruppe nntp://microsoft.public.de.vc berichtete dieses Problem mehrfach in der Gruppe. Letzten Endes trugen aber alle Ideen und Vorschläge zu keiner Lösung bei.

In einem Thread wurde dann ein Beispielprogramm veröffentlicht, bei dem mit einem simplen Klick auf einen Radiobutton das Programm in einer Endlosschleife gerät und sich aufhängt.

Die Ursache ist trivial. Eine Gruppe von Auto-Radio Buttons war nicht durch ein Control mit dem Stil WS_GROUP abgeschlossen worden. Solch einen Design Fehler im Dialog hat schon jeder mal gemacht und viele werden die folgende Ausgabe im Debug Fenster kennen: „Warning: skipping non-radio button in group.“

In diesem Fall kam nach der Gruppe Radio Buttons ein Tab-Control, dass den Stil WS_EX_CONTROLPARENT hat. Dieser Stil erlaubt es Dialoge und Controls zu schachteln. Gleichfalls führt dieser Stil dazu, das versucht wird die Gruppe von Radio-Buttons in den untergeordneten Feldern weiter zu führen. Leider waren in den entsprechenden Fenstern auch keine Controls mehr mit dem Stil WS_GROUP und irgendwie hat sich die Windows UI-API dann letzten Endes aufgehängt.

Behoben werden konnte dieser Stil einfach;

  • Entweder man führt ein (evtl. sogar unsichtbares) Static Control ein, direkt nach der Gruppe. Static-Controls werden automatisch mit dem WS_GROUP Stil versehen.
  • Oder man änderte die Z-Order, so dass auf die Radio-Button Gruppe ein anderes Control mit WS_GROUP Eigenschaften folgt.
  • Oder eines der Controls im Unterdialog hat den WS_GROUP Stil, was aber oft genug nicht dem eigentlich gewollten Design entspricht, dass sich die Gruppe in den Unterdialog fortsetzt.

Auch so simplen Warnungen in der Debugausgabe wie „Warning: skipping non-radio button in group.“ sollte man gezielt nachgehen. Manchmal haben heftige Probleme trivialste Ursachen ❗

Anmerkung: Das Beispielprogramm ist zwar noch VC6, aber das Problem liegt im Design der geschachtelten Fenster und der nicht abgeschlossenen Radio-Button Gruppe.

SetFocus versus WM_NEXTDLGCTL

Die meisten Entwickler verwenden SetFocus um in einem Dialog gezielt den Eingabefokus zu versetzen. Aber es gibt ein Problem, dem SetFocus nicht gerecht wird: der Default Button.
Der Default Button wird durch WM_SETDEFID bzw. CDialog::SetDefID gesetzt. SetFocus berücksicht das interne Konzept des Default Buttons nicht.

Wenn man mit der Tab-Taste durch einen Dialog springt und einen Button erwischt, dann wird dieser automatisch zum Default Button. Normalerweise ist das der OK-Schalter, er verliert dann den dicken Rahmen. Drückt man die Eingabe-Taste, dann wird nun der neue Schalter ausgelöst und nicht der OK-Schalter.
Landet der Fokus von einem Button dann bei einem Edit Control, dann wird der OK-Schalter wieder der Default Button und man kann mit der Eingabe-Taste den Dialog beenden.

Wenn nun SetFocus verwendet wird durch eine interne Funktion, dann wird dieser Mechanismus des Dialoges umgangen. Der Default-Button wird evtl. nicht korrekt gesetzt. Es kann sogar soweit kommen, dass es zwei Default-Schalter oder gar keinen mehr gibt. SetFocus führt immer zu Problemen wenn das neue Control oder das bisherige Control, welches den Fokus hatte, ein Button ist. Nur wenn beide Controls keine Button sind kann SetFocus gefahrlos verwendet werden.

Korrekt funktioniert das Ganze nur, wenn statt SetFocus, WM_NEXTDLGCTL verwendet wird, oder die entsprechenden MFC Funktionen, CDialog::NextDlgCtrl bzw. CDialog::GotoDlgCtrl verwendet werden.
Die Nachricht WM_NEXTDLGCTL wird auch intern durch die DefDialogProc behandelt und normalerweise durch IsDialogMessage erzeugt.
Gefahrlos ist auch die Verwendung von SetFocus in WM_INITDIALOG bzw. CDialog::OnInitDialog Handlern, die dann normalerweise mit FALSE, verlassen werden. Nach dieser Funktion sorgt der Dialog Handler, für die korrekte Behandlung der Default Buttons.

Fazit: Man sollte also innerhalb von Dialogen ganz auf SetFocus verzichten sondern nur WM_NEXTDLGCTL  bzw.  CDialog::NextDlgCtrl und CDialog::GotoDlgCtrl verwenden. Konsequenterweise sollte man dann auch in OnInitDialog Handlern auf SetFocus verzichten. ❗

… und schließlich entdeckte Kolumbus SendDlgItemMessage

Wer hat nicht oft genug solchen Code geschrieben:

GetDlgItem(IDC_MYITEM)->SendMessage(WM_MYMSG,wParam,lParam);
// – oder-
HWND hWnd = ::GetDlgItem(hDlg,IDC_MYITEM);
if (hWnd)
    ::SendMessage(hWnd,WM_MYMSG,wParam,lParam);

Da programmiert man Jahrzente unter Windows und hat SendDlgItemMessage noch niemals gesehen und damit geht es so einfach:

SendDlgItemMessage(IDC_MYITEM,WM_MYMSG,wParam,lParam);
// – oder-
::SendDlgItemMessage(hDlg,IDC_MYITEM,WM_MYMSG,wParam,lParam);

Man lernt nie aus. 😉

MSDN Links: SendDlgItemMessage, CWnd::SendDlgItemMessage

BTW: Netter Seiteneffekt. Die direkte Verwendung von GetDlgItem(…)->SendMessage(…); ist natürlich äußerst unsicher. Im Extremfall kracht es. Charmant, dass SendDlgItemMessage sich hier äußerst robust und korrekt verhält.

Die Manifest Pest…

Hier noch mal ein paar wirklich nette Sachen, die zeigen warum ich Manifeste mittlerweile  nicht mehr ganz so mag (um es wirklich vorsichtig auszudrücken):

Was passiert eigentlich wenn man ein internes UND ein externes Manifest zu einer DLL oder EXE hinzufügt?

  1. Auf einem XP Rechner wird der Lader das externe Manifest bevorzugen, das interne Manifest wird ignoriert.
  2. Auf einem Windows 2003 oder Vista Rechner wird das interne Manifest bevorzugt und das externe Manifest wird ignoriert.
  3. Handelt es sich um eine DLL die mit LoadLibrary geladen wird, dann wird niemals ein externes Manifest berücksichtigt, selbst wenn kein internes Manifest vorliegt. Hier verhalten sich XP, 2003 und Vista gleich.

Ein Grund mehr zu meiner Empfehlung: Verwende immer nur interne Manifeste!

Konfussion mit einem RTF Control und EM_STREAMIN/EM_STREAMOUT

Beim Umstellen eines größeren Projektes auf Unicode hatte ich beim Testen einen Crash. Nichts ungewöhnliches.

Es betraf das Ein- und Ausstreamen eines RTF Controls. Zum einen wurden RTF Daten bzw. auch Textdaten in einem Stream übergeben bzw. ausgelesen. Erste Überraschung EM_STREAMIN/OUT mit SF_TEXT will nie Unicode und liefert nie Unicode! Und hier war mein Denkfehler.

Ein Test zeigte folgendes Verhalten mit den unterschiedlichen Streammodes:

  • SF_RTF liefert/will immer einen 8bit Daten Stream, den man in einem CStringA Buffer leicht speichern kann.
  • SF_TEXT liefert/will auch immer einen 8bit Datenstream, ganz egal ob das Control CreateWindowW oder mit CreateWindowA erzeugt wurde.
  • SF_TEXT|SF_UNICODE liefert/will auch immer einen Unicode Datenstream, ganz egal ob das Control CreateWindowW oder mit CreateWindowA erzeugt wurde.

Auch etwas irritierend war für mich die Dokumentation, aber durch aus korrekt.
Die EditStreamCallBack Funktion bekommt laut Doku immer eine Byte Zahl für die Buffergröße mitgegeben. Und es sind wirklich immer Bytes! Keine Anzahl von Zeichen/Buchstaben. Wenn man also Daten mit SF_TEXT|SF_UNICODE gestreamed werden, dann bekommen wir die Anzahl der Bytes. Die Anzahl der Buchstaben ist natürlich nur halb so groß wie die Anzahl der übergebenen bzw. zu übergebenden Bytes.
Eine Warnung bzw. ein erklärender Hinweis wäre an dieser Stelle sicherich auch angebracht.