C++MFCProgrammierenVS 2008Martin Richter - Mi 25 Feb 2009 20:45

CString nimmt natürlich auch in einem seiner Konstruktoren einen LPCTSTR . Dieser Konstruktor hat aber noch eine versteckt eingebaute Funktion. Man kann CString auch mit MAKEINTRESOURCE(id) verwendet. Aus der Dokumentation geht dies nicht eindeutig hervor, aber zumindest findet man es am Rande erwähnt.
Ist also das High-WORD des LPCTSTR gleich NULL dann wird das LOWORD verwendet um die entsprechende String-Ressource zu laden.
Ist die Ressource nicht vorhanden wird in der Debugausgabe ein entsprechender Trace erzeugt.

Benötigt man also temporär eine String Variable so kann man direkt in einem Statement die entsprechende String-Resource laden:

CString strMyText(MAKEINTRESOURCE(IDS_MY_STRING));
strMyText += strSomeOther;
strMyText += CString(MAKEINTRESOURCE(IDS_MY_TEXT_TO_APPEND));
IDEProgrammierenVS 2008VS-Tipps&TricksMartin Richter - So 08 Feb 2009 18:14

In einem meiner ersten Tipps habe ich das Thema Import und Export von Einstellungen schon behandelt schon behandelt: VS-Tipps & Tricks: Einstellungen für Visual Studio mehreren Arbeitsplätzen gleich halten.

Besonders wichtig ist das für mich immer mit den Tastatureinstellungen, die immer anpasse.

Was ich aber jetzt mit positivem Erstaunen feststellen konnte ist folgendes:
Man kann Einstellungen auch aus VS-2005 nach VS-2008 übertragen und umgekehrt ❗

Die XML Struktur des Exportformates ist nicht versioniert. Man kann direkt Einstellungen aus VS-2005 austauschen. Ideal wenn man Legacy Projekte noch behandeln muss und beide Systeme parallel fährt.

IDEProgrammierenVS 2008VS-Tipps&TricksMartin Richter - So 30 Nov 2008 12:35

Die ganzen netten Tool-Windows (Memory Window , Watch Window, Find Result, Document Outline, Output, Pending Changes, etc.) kann man sich mit einer netten Tastenkombination auf den Monitor holen. Aber wie schließt man sie nun wieder?

Indem man mit der Maus rechts oben auf das rote X klickt…  ❓ 😕

Nein ❗ Es geht auch (zum Glück) über die Tastatur. Mit Umschalt+Escape kann man jedes Tool Window sofort auf Hide setzen und schließen. Der entsprechende Makro Befehl lautet Window.CloseToolWindow.

C++CRTDebuggingProgrammierenWindows APIMartin Richter - Do 27 Nov 2008 19:20

In meine ersten Artikeln über Heap-Bugs habe ich bereits erwähnt, dass die CRT aber auch Windows selbst Speicher unter bestimmten Umständen vorbelegt bzw. beim Freigeben des Speichers mit einem festen Wert löscht.

Für einen Entwickler ist es gut zu wissen welche Werte durch wen gesetzt werden. Zudem erleichtert einem dieses Wissen auch das Debuggen und die Identifikation von Problemen im Zusammenhang mit dem Heap, deshalb habe ich hier mal diese Magic-Bytes, die von Microsoft verwendet werden hier zusammengetragen.

Würde man im Debugger zum Beispiel eine Variable mit dem Wert 0xCCCCCCCC entdecken, dann ist davon auszugehen, dass man diese Variable auf dem Stack nicht initialisiert hat.

  • 0xABABABAB
    Wird von HeapAlloc als Wert für die Guard Bytes („no man’s land“) vor und hinter Speicherblöcken verwendet.
  • 0xBAADF00D
    Wird von LocalAlloc(LMEM_FIXED) verwendet um nicht nicht initialisierten Speicher im Heap zu kennzeichnen.
  • 0xCCCCCCCC
    Wird von der Debug-CRT verwendet um nicht initialisierten Stack zu kennzeichnen.
  • 0xCDCDCDCD
    Wird von der Debug-CRT verwendet um nicht initialisierten Speicher im Heap zu kennzeichnen.
  • 0xDDDDDDDD
    Wird von der Debug-CRT verwendet um freigegebenen Speicher im Heap zu kennzeichnen.
  • 0xFDFDFDFD
    Wird von vom Debug-Heap verwendet für Guard Bytes („no man’s land“), vor und hinter Speicherblöcken.
  • 0xFEEEFEEE
    Wird von HeapFree() verwendet um freigegebenen Speicher zu kennzeichnen.

Siehe auch: http://en.wikipedia.org/wiki/Magic_number_(programming)

ATLC++CRTDebuggingMFCProgrammierenVS-Tipps&TricksWindows APIMartin Richter - Di 04 Nov 2008 20:30

Mancher Bug macht einem nicht den Gefallen und lässt sich in der Debug-Version finden. Ursache ist oft genug eine Variable, die in der Debug-Version initialisiert (0xCC) wird aber in der Release-Version zu einem Crash führt, wenn zufällige Daten auf dem Stack für undefiniertes Verhalten sorgen.

Also macht man sich an das debuggen der Release Version und kann keinen Fehler finden.
Kaum startet man das Programm ohne Debugger dann kracht es wieder. Warum?

Manch einer könnte jetzt denken: Der Debugger verändert das Memory Layout! Das tut er schon, aber entscheidend für ein anderes Verhalten ist der Debug Heap!
Die wenigsten Entwickler wissen überhaupt, dass es ihn gibt. Ich meine hier nicht die Debug Funktionen, die die CRT zur Verfügung stellt, denn mein Thema heute ist ja das Debuggen eines Release-Programms, und die Debug-CRT hat ja bekanntlich in einem Release Programm nichts zu suchen!

Machen wir es praktisch und nehmen wieder mein kleines Crashtest-Programm:

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,104);
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

Wenn wir dieses Programm als Release Version kompilieren und ausführen, dann erhalten wir keine Fehlermeldung ❗ Interessant. Der Heap ist nicht soweit zerstört, dass es zu einer Zugriffsverletzung kommt. Starten wir aber unser Programm mit dem Debugger, dann wird der so genannte Debug Heap des Systems verwendet, der wie die Debug-CRT Guardbytes setzt und kontrolliert.

Ein weiteres Problem entsteht dadurch, dass der Debug Heap den allokierten Speicher auf feste Werte initialisiert genau wie die Debugversion der CRT. Wenn also nicht initialisierter Speicher genutzt wird, dann ist das Verhalten mit dem Debug-Heap deterministisch, ohne Debug Heap eher zufällig.

Das im Debugger alles etwas anders sein kann ist sogar dokumentiert 😉

Processes that the debugger creates (also known as spawned processes) behave slightly differently than processes that the debugger does not create.
Instead of using the standard heap API, processes that the debugger creates use a special debug heap. On Microsoft Windows XP and later versions of Windows, you can force a spawned process to use the standard heap instead of the debug heap by using the _NO_DEBUG_HEAP environment variable or the -hd command-line option.

In diesem Text steht auch, wie man den Debug-Heap ausschalten kann, mit:

SETX _NO_DEBUG_HEAP 1

Diese Environment-Variable sorgt dafür, dass sich auch bei geladenem Debugger, das Programm so verhält wie ohne Debugger (hoffentlich). Führt man mein Testprogramm nun im Debugger aus, wenn die Environment-Variable _NO_DEBUG_HEAP auf 1 gesetzt ist, erhält man keinen Debug-Break mehr. Denn in diesem Fall gibt es keine Guardbytes, die geprüft werden.
Löscht man den Eintrag _NO_DEBUG_HEAP wieder, dann erhält man im Debugger wieder wie erwartet einen Break.

Will man also wirklich realitätsnah eine Release-Version debuggen, dann kommt man um das Ausschalten des Debug-Heaps nicht herum.

PS: Man kann es auch etwas einfacher haben, wenn man sich nachträglich an den Prozess mit dem Debugger attached (wenn das geht). Ideal ist dieses Verfahren auch beim Remote-Debugging (dazu demnächst mehr).

ATLC++CRTDebuggingIDEMFCProgrammierenVS-Tipps&TricksWindows APIMartin Richter - Fr 31 Okt 2008 19:42

Einige Hilfsmittel um einen Heap-Fehler zu finden habe ich in meinem letzten Beitrag ja beschrieben.

Eigentlich wünscht sich der Entwickler nichts mehr, als dass ein falscher Zugriff auf den Heap, sofort einen Break im Debugger auslöst. Die Methoden, die ich bisher gezeigt habe (AfxCheckMemory, _CrtCheckMemory, _CrtSetDbgFlag) können das nicht direkt , aber zumindest helfen sie den Fehler einzukreisen.

Ein unverzichtbarer Helfer, der sofort solch einen Break auslösen kann, ist der Application Verifier, den ich bereits in einem älteren Artikel als Freund und Helfer vorgestellt habe.

Seit Visual Studio 2005 kann man direkt Parameter für den Application Verifier im Projekt einstellen und auch direkt den Debug-Prozess mit dem Application Verifier starten (Umschalt+Alt+F5).
An den Standardeinstellungen im Projekt braucht man hier gar nichts zu ändern:
Conserve Memory – No
Protection Location – Je nach Testfall (man sollte mit beiden Einstellungen mal debuggen)
Alle anderen Einstellungen Verification Layers Settings – auf Enable

Mit dem Application Verifier lässt sich der so genannte Paged Heap nutzen, der Guard Pages anlegt hinter oder vor den allokierten Speicherbereichen (siehe auch GFLAGS.EXE). Der Vorteil: Man erhält sofort eine Access Violation, wenn man den Speicherbereich überschreitet.

Mein kleines Demoproramm

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,106); // -- This will corrupt the heap
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

crashed mit der Nutzung des Application Verifiers sofort und man kann im Call Stack die Zeile 7 ausmachen.
Genial ist besonders, dass der Application Verifier auch mit der Release Version sofort die Zeile 7 als Ursache identifiziert. Gerade wenn man also nicht auf die Debug-CRT zurückgreifen kann, ist der Application Verifier ein super Hilfsmittel.

Der Nachteil: Die Guard Pages liegen nicht exakt und direkt hinter dem allokierten Bereich, sondern auf der nächsten Page Boundary. Deshalb crashed mein Sample auch nicht wenn man den Speicher um nur 1 Byte überschreitet.

Aber der Application Verifier ist zum Testen ein absolutes Muss, weil auch falsche Handles erkannt werden und auch der Lock Verfification Layer für die Qualitätssicherung einfach nützlich zum entwanzen sind. (siehe auch Application Verifier Einstellungen in der MSDN).

Hinweis ❗

Auf Windows XP und Windows Server 2003 erhält man ohne administrative Rechte die folgende Fehlermeldung:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name>. Contact your system administrator for assistance

Unter Windows Vista oder Windows Server 2008 erhält man die flogende Fehlermeldung wenn der Application Verifier nicht elevated gestartet wird:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name> or per user verifier settings should be enabled by the administrator. Please refer to documentation for more information.

Durch einen simplen Eintrag in der Registry lässt sich aber auch als normaler Benutzer, ohne administrative Rechte, der Application Verifier nutzen, man erzeugt einen DWORD Eintrag in der Registry mit dem Wert 1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manger\ImageExecutionOptions
Nach einem Reboot kann man nun einfach den Application Verifier auch non-elevated, als normaler Benutzer nutzen.

AllgemeinATLC++CRTDebuggingMFCProgrammierenVS-Tipps&TricksMartin Richter - Mo 27 Okt 2008 22:47

Probleme finden, die mit dem Heap zusammenhängen ist oft genug eine Sache für sich und für Anfänger nicht selten ein Buch mit sieben Siegeln. Die CRT und der Debugger stellen aber einige Werkzeuge zur Verfügung, die es einem doch mit etwas Geschick und Wissen erlauben auch komplexere versteckte Fehler zu finden, die Heapfehler auslösen.

Jeder C/C++ Entwickler hat schon Meldungen dieser Art beim Testen seiner Programme gesehen:

HEAP[CrashTest.exe]: Heap block at 006D7920 modified at 006D79B0 past requested size of 88
Windows has triggered a breakpoint in CrashTest.exe.
This may be due to a corruption of the heap, which indicates a bug in CrashTest.exe or any of the DLLs it has loaded. 

oder etwas in dieser Art:

Debug Error!
Program: …nts\Visual Studio 2008\Projects\CrashTest\Debug\CrashTest.exe
HEAP CORRUPTION DETECTED: after Normal block (#110) at 0x000D7948.
CRT detected that the application wrote to memory after end of heap buffer.

Ursache ist ein Fehler wie in diesem kleinen Beispielprogramm:

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,106); // -- This will corrupt the heap 
  char *pOther = new char[100]; 
  ZeroMemory(pOther,100); 
  delete [] pOther; 
  delete [] pCorrupt; 
  return 0; 
}

Wenn die Debug-CRT benutzt wird erhält man automatisch einen Break im Debugger wenn der Speicherblock pCorrupt freigeben wird (Zeile 11). Man braucht also nur den Call-Stack oder Stacktrace anzusehen und kann zumindest feststellen, welcher Block defekt ist.

Schwieriger wird es dann schon die Stelle zu finden, an der der Block überschrieben wird. In meinem Beispiel also die Zeile 7. Besonders dann wird es komplex, wenn das Programm größer ist, und der Speicherblock an evtl. sehr vielen Stellen genutzt wird.

Um die Position einzugrenzen und nicht evtl. bis zum Programmende warten zu müssen, wenn man (hoffentlich) alle Objekte freigibt kann man die CRT veranlassen den Heap zu prüfen. Dies geschieht mit _CrtCheckMemory oder AfxCheckMemory.
Streut man also in seinem Code an strategisch guten Stellen das folgende Statement in seinen Code

ASSERT(AfxCheckMemory()); // oder _CrtCheckMemory

kann man relativ gut die Stelle einkreisen die den Fehler verursacht, und das ohne große Performanceverluste. Man erhält sofort einen ASSERT, ab dem Moment ab dem die Integrität des Heaps zerstört wurde und der Check durchgeführt wird.

Noch etwas einfacher ist es, die CRT dazu zu bringen sich sofort zu melden wenn der Heap zerstört wird. Dies kann man erreichen indem man das Debug-Flag _CRTDBG_CHECK_ALWAYS_DF setzt.

Platziert man bei Programmstart die folgende Codezeile in seinem Programm

_CrtSetDbgFlag(_CrtSetDbgFlag(0)|_CRTDBG_CHECK_ALWAYS_DF);

dann unterbricht die CRT das Programm sofort bei der nächsten Allokation eines Speicherbocks, nachdem der Heap zerstört wurde. In meinem Beispiel also direkt bei der nächsten Allokation in Zeile 8!
Nachteil ist, dass bei jeder Allokation der Heap geprüft wird und damit die Performance schon in den Keller gehen kann, wenn das Programm groß ist und der Fehler evtl. selten auftritt.

Man kann also mit den einfachen Bordmitteln der CRT einen Fehler schon relativ leicht eingrenzen.

Soweit für heute. Was man noch alles machen kann um effektiv Heap-Fehler zu finden werde ich demnächst noch in weiteren Artikeln zu diesem Thema beschreiben.

C++IDEProgrammierenVS-Tipps&TricksMartin Richter - So 17 Aug 2008 19:15

Ich habe einige komplexe Projekte in die auch andere Tools eingebunden sind. Diese Tools erzeugen Dateien und Ressourcen erzeugen, die mit in den kompletten Build eines Projektes einfließen. Zum Teil sind es Daten, die auf einen speziellen Weg kompiliert werden oder auch Ressourcen, die durch ein sekundären anderen Compiler erst mit verarbeitet werden müssen.

Teilweise hatte ich diese Prozesse in einen Pre– und Postbuild Step eingebaut. Leider verhält sich Visual Studio oft genug eigentümlich, es merkt nicht das abhängige Dateien während des Build Prozesses verändert werden.

Bei einer Umstellung meiner Projekte stellte ich fest, dass es weitaus einfacher ist für solche speziellen Vorgänge eigene kleine Projekte zu erzeugen vom Typ „Utility„.

In diesen Projekten kann man einfach Ein- und Ausgabedateien definieren und den Befehl, der dazu notwendig ist die Daten eben zu bearbeiten.
Das Ganze wird dann in eine Master Solution kombiniert mit den entsprechenden Abhängigkeiten. Das hat vor allem auch den großen Vorteil, das auch die Projekte parallel im Build durchlaufen werden können.

Utility Projekte sind für manche Aufgaben weitaus besser geeignet als Pre– und Postbuild Steps. Vor allem werden durch die Verwendung von Utility Projekten die einzelnen Vorgänge entzerrt und verständlicher.

IDEProgrammierenVS 2008VS-Tipps&TricksMartin Richter - So 10 Aug 2008 19:40

Wie kann man eigentlich lernen noch schneller mit Visual Studio umzugehen?
Ein Weg ist sicherlich die Maus zu umgehen und mehr mit der Tastatur zu machen. Dazu ist es aber auch notwendig, die entsprechenden Tastaturbefehle zu kennen.

Ein einfaches Hilfsmittel ist es die Tastatur Shortcuts auch in den Buttons der Toolbars anzeigen zu lassen. Das geht einfach über Tools -> Customize -> Show shortcut keys in ScreenTips.

Die Tooltips der Buttons enthalten, dann auch den Shortcut, der diesen Befehl auch auslösen würde. Auf diese Weise erhält man z.B. schnell die Info, dass Strg+D in das Suchfeld springt.

C++MFCProgrammierenMartin Richter - Di 01 Jul 2008 19:23

UIs werden immer komplexer. Und manche UI passt sicher nicht mehr in ein simples und einfaches Frame/Document/View Modell. Oft genug wird hier noch ein Tool-Window gedockt, dort noch ein Ausgabefenster und hier noch ein nicht modaler Dialog mit Status Infos.

Bringt man nun in einem solchen Fenster Buttons oder Toolbars unter, dann wünscht man sich in manchen Fällen, dass ein Command-Handler ausgelöst werden soll, der in einem anderen Objekt (z.B. dem aktiven View) liegt.
Oder man möchte, dass eben auch in solch einem Fenster ein Command-Handler berücksichtigt wird.
Der Ansatz andere Fenster in das Command Routing einzubauen ist die Funktion CCmdTarget::OnCmdMsg. Diese Methode hat zwei Funktionen.

  1. Sie dient dazu, dass in Menüs (auch Kontextmenüs) und auch in Toolbars Menüpunkte und Buttons ein- und auszuschalten. (ON_UPDATE_COMMAND Handler)
  2. Sie leitet die eigentliche WM_COMMAND Nachricht zur Ausführung an den entsprechenden zuständigen Handler weiter. (ON_COMMAND Handler).

Zum besseren Verständnis des Command Routings das die MFC als Standard vorsieht empfehle ich als Lektüre TN021. Um abenteuerliche Konstrukte und üble Verbiegungen der guten Vorgaben in der MFC zu vermeiden sollten die Grundlagen des Routings in Fleisch und Blut übergegangen sein.
Und dann ist es an sich nicht schwer, Anpassungen vorzunehmen, die es wirklich einfach machen auch andere Fenster in das eigene Routing aufzunehmen ohne die MFC zu verbiegen.

Nun zum Eingemachten:
Will man also Handler in anderen assoziierten Fenstern nutzen, dann muss man diese Fenster in die eigene angepasste OnCmdMsg Routing Struktur einbauen. Der beste Punkt dafür ist in vielen Fällen das hostende CMainFrame. Der Grund ist einfach: Das CMainFrame ist in 99% aller Fälle die erste Adresse, an die ein WM_COMMAND ausgeliefert wird.

Eine entsprechende Funktion kann so aussehen:

BOOL CMainFrame::OnCmdMsg(UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
    // Give a special window the first chance to handle the command
    if (m_pSomeWindow->GetSafeHwnd() &&
        m_pSomeWindow->IsWindowVisible() &&
        m_pSomeWindow->IsActiveInSomeWayOrHasFocus())
    {
        if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
            return TRUE;
    }

    // Do the standard routing (View, Frame, Application)
    if (__super::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
        return TRUE;

    // If not handled up to this point just give another window a chance
    if (m_pSomeOtherWindow->GetSafeHwnd() &&
        m_pSomeOtherWindow->IsWindowVisible() &&
        m_pSomeOtherWindow->IsActiveInSomeWayOrHasFocus())
    {
        if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
           return TRUE;
    }

    // not handled
    return FALSE;
}

Anmerkungen:

  • Meistens ist es bei diesen Fällen wichtig, dass Routing nicht immer durchzuführen. Z.B. nur dann, wenn das Fenster sichtbar ist, den Fokus hat oder ähnliches (siehe Beispielcode).
  • Weiterhin ist es für Kontextmenüs wichtig als Parent-Fenster ein von CFrameWnd abgeleitetes Fenster zu verwenden. Geschieht dies nicht, dann wird WM_INITMENUPOPUP nicht behandelt, und die Menüpunkte werden nicht enabled bzw. disabled. Wird das obige Verfahren mit OnCmdMsg korrekt angewendet, kann man als Parent Fenster für Kontextmnüs immer AfxGetMainWnd verwenden.
  • Toolbars funktionieren nur dann korrekt wenn hier der korrekte Owner gesetzt wird. Das geschieht mit CToolBar::SetOwner. Auch hier sollte das äußere Frame Window die WM_COMMAND Nachrichten erhalten, die dann über den normalen weiteren (angepassten) Weg geroutet werden.

Siehe auch Command Routing der MFC bei Kontext Menüs mit TrackPopupMenu  

« Vorhergehende SeiteNächste Seite »