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.

_UNICODE versus UNICODE und so manches Eigentümliche

Wann nimmt man eigentlich was?
Jedesmal wenn ich Code ansehe finde ich mal einen

#ifdef _UNICODE

dann mal wieder einen

#ifdef UNICODE

Wann eigentlich was?
Die Frage wurde schon oft beantwortet (siehe The Old New Thing : TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE).

In Kurzform:
UNICODE wird in den Windows Header-Dateien verwendet.
_UNICODE in der C-Runtime (CRT).

Interessanterweise gibt es nur den Define _MBCS aber nicht den Define MBCS. Anhand der Namensgebung kann man sehen, dass _MBCS aus/für die CRT nur Einfluss hat. Die Windows API sagt einfach, wenn nicht Unicode dann ist es MBCS!

Daraus ergeben sich einige interessante Abhängigkeiten: 

_T ist abhängig vom Define _UNICODE genauso wie _TEXT (also der CRT)
TEXT ist wiederum abhängig vom Define UNICODE.

Jetzt wird es aber ganz lustig:
Die Datei tchar.h gehört zur CRT. Und TCHAR ist eigentlich kein Define aus tchar.h und der CRT. Dort wird nämlich eigentlich _TCHAR definiert und sollte TCHAR schon definiert sein (durch die windows.h), dann wird TCHAR nicht mehr umdefiniert.

Ist also nur UNICODE definiert und nicht _UNICODE, dann muss der folgende Code einen Compiler Fehler auslösen, wenn windows.h vor der tchar.h inkludiert wird. Erwartungsgemäß meldet der folgende Code einen Compiler-Fehler, obwohl dieser perfekt und korrekt aussieht:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
 const TCHAR szText[] = „“;
  // Must fail if UNICODE is defined and not _UNICODE, and windows.h included first
 _tcslen(szText);
 return 0;
}

In VC6 gab es noch keine Projekteinstellung für Unicode. Man musste also beide Defines setzen, als Präprozessor Definition. Andernfalls hatte man eine lustige Code Mischung und lief schnell mal in ein Problem wie oben.

Der Schalter in VC-2005 und VC-2003 für

  • „Use Unicode Character Set“ setzt beide Defines _UNICODE und UNICODE
  • „Use Multi-Byte Character Set“ setzt nur _MBCS.
  • „Not set“ setzt Erwartungsgemäß keinen der Defines und man kann das obige Beispiel und den Fehler ausprobieren.

Projektintern verwende ich grundsätzlich immer intern nur den define UNICODE um zu prüfen welche Art von Projekt hier läuft. Und ich achte darauf, dass die windows.h bzw. die afxwin.h Datei immer als erstes inkludiert werden. Dann ist TCHAR entsprechend konform für die Windows API gesetzt. Die CRT ist für mich meistens nur Beiwerk und ich vermeide Ihre Verwendung eigentlich wo ich kann.

An kritischen Stellen baue ich auch gerne den folgenden Test ein um ganz auf Nummer sicher zu gehen:

#if defined(UNICODE) ^ defined(_UNICODE)
#error Inconsitent UNICODE and _UNICODE definition
#endif

Mit dem XOR-Operator wird hier ein Fehler ausgelöst wenn nur einer der beiden Defines gesetzt ist.

Siehe auch die MSDN INFO: UNICODE and _UNICODE Needed to Compile for Unicode

MSI.LIB fehlt in VC-2005 Installation

Das verstehe mal wieder wer will 😕 ich verstehe es auf jeden Fall mal nicht.

Die MSI.H Include-Datei  findet sich in der im $(VCInstallDir)PlatformSDK\include Verzeichnis. Die dazu passende MSI.LIB Datei dagegen ist dagegen in $(VCInstallDir)PlatformSDK\lib nicht zu finden. Da spielt es auch keine Rolle ob man VS-2005 RTM oder SP1 hat.

Man muss sich diese Datei aus einem aktuellen SDK besorgen.

Alle Version ab VC6, über VS.NET 2002+2003 verfügen sowohl über Header als auch Lib-Datei.

Heath Stewart beschreibt das zwar in seinem Blog, aber warum das so ist steht dort nicht.