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.

ATE beim „Ready for take off“ in Frankfurt vom 19.-21.02.2008

Wer mich mal persönlich kennen lernen will, der kann mich in Frankfurt beim großen Launch “Ready for take off” von Windows Server 2008, SQL Server 2008 und Visual-Studio 2008 vom 19.-21. Februar 2008 treffen.

Ich werde einer der vielen MVPs sein, die als ATEs (Ask the Experts) die Veranstaltung begleiten.

VC-2008: Neues, Breaking changes…

Hier ein kleiner Auszug aus der Liste der Änderungen und Neuigkeiten.
Die Links verweisen in die MSDN wo man alles nachlesen kann.

What’s New in Visual C++ 2008
Da ist nicht sooo viel:

  • Eingeschränkte (read only) Unterstützung des Class Designers.
  • Vista style Guidlines für Dialoge
  • Vista Common Controls in der MFC
  • Die interessanten Sachen wie MFCNext und TR1 kommen noch

Breaking Changes

  • Nachdem in Visual Studio 2005 der Support von Windows  95 entfiel, hat Microsoft nun einen Schlussstrich unter gezogen.
    Im Klartext: Windows 95, Windows 98, Windows ME, und Windows NT werden als Zielplattformen nicht mehr unterstützt.
  • Hier fällt auf, dass die ATL nun zwingend Abhängig von der CRT wird. Wer früher gerne ATL_MIN_CRT verwendet hat um „gar keine“ CRT zu verwenden, der wird feststellen, dass seine Module etwas wachsen.
  • /Wp64 ist deprecated (na endlich)…

Anmerkung:
Wie so oft werden hier die Sachen veröffentlicht, wo man bei Microsoft weiß, dass man Breaking Changes durchgeführt hat. Es gibt oft genug weitere Breaking Changes die man oft erst am eigenen Leib erfahren muss, siehe Attributed ATL.

Weitere Infos zur MFC in VS-2008 und der BCG Pro-Library

Hier noch ein paar Infos zu dem Thema:

Eine Microsoft Bekanntmachung auf den US VC++ Seiten: MFC Update Powered By BCGSoft
http://msdn2.microsoft.com/de-de/visualc/bb892882.aspx

Eine Pressemitteilung von BCG-Soft vom 09.11.2007:
http://www.bcgsoft.com/pressreleases/PR071110.pdf

Fragen und Antworten im BCG-Forum:
http://www.bcgsoft.com/cgi-bin/forum/topic.asp?TOPIC_ID=4476

VS-Tipps & Tricks: Einstellungen für Visual Studio mehreren Arbeitsplätzen gleich halten

Das man Einstellungen von Visual Studio mittlerweile einfach und intelligent ex- und importieren lassen ist ja mittlerweile bekannt. Man findet diese Funktion direkt im Tools Menü. In meinem Blog fand dies auch schon Erwähnung.

Weitaus versteckter in den Einstellungen von Visual Studio findet man unter
Tools -> Environment -> Import and Export Settings
einen Schalter der das heißt: Use team settings file

Hier ist ein Feature verborgen, dass es wirklich einfach macht auch in einem Team identische Projekt und Visual Studio Einstellungen auf jedem Arbeitsplatz einzustellen. Und das mit wirklich geringem administrativen Aufwand.
Gerade die Include und Library Pfade für gemeinsame Bibliotheken und deren Reihenfolge lassen sich direkt vorbelegen. Und sie werden bei jedem Neustart von Visual Studio auf den vorbestimmten Wert zurückgestellt.
Vorbei die Suche warum in aller Welt irgendwelche falschen SDK Header und Libraries verwendet werden. Warum ein Entwickler immer noch eine uralte Libary eines Drittanbieters verwendet und in seinen Modulen ein Bug auftaucht, den alle schon überwunden glaubten.
Voraussetzungen dafür ist natürlich, dass die einzelnen Rechner der Entwickler natürlich ein ähnliches Verzeichnis Layout verwenden, bzw. die gemeinsamen Bibliotheken im Netz liegen.

Im Prinzip lässt sich jede Einstellung von VS auf diesem Wege beeinflussen.

VS-2008 steht für den Download auf MSDN bereit

Es ist wirklich „very soon“ geworden!

MSDN Subscriber Downloads 
Visual Studio Team System 2008 Team Suite (x86 and x64 WoW) – DVD (English)  
        
Date/Time Posted 
2007-11-19 06:46:35 (UTC) 

 Na dann mal los mit dem ausprobieren…

Jetzt ist es soweit VS-2008 kommt nächste Woche als MSDN-Download

Der Termin der ursprünglich genannt wurde für die Veröffentlichung von Visual-Studio 2008 (Orcas) wird wirklich eingehalten.

Im MSDN-Subscriptions Weblog wurde gestern bekannt gegeben, dass VS-2008 ab Anfang nächster Woche veröffentlicht wird.

Damit ist es das erste Produkt für die Launch Veranstaltung im Februar 2008 „Ready for Take Off“, das auch wirklich fertig ist…