Ein großer Teil der Anwendungsentwicklung besteht eigentlich aus der Fehlersuche im Programm. Die gesamte Softwareentwicklung ist ein straffer Zyklus von Anwendungsentwurf, Implementierung und Fehlersuche.
Visual C++ bietet eine umfangreiche Umgebung für die Fehlersuche und zahlreiche Debugging-Werkzeuge, die bei der Programmentwicklung unentbehrlich sind. Man kann damit Probleme schnell erkennen, den Inhalt von Variablen überwachen und den Programmfluß durch den eigenen und den MFC-Code verfolgen.
Werkzeuge wie das Programm Spy++ zeigen, welche Nachrichten Windows an die Anwendung übergibt, und man kann auch untersuchen, mit welchen Steuerelementen der Benutzeroberfläche und welchen Windows-Stilen die Anwendungen arbeiten.
Der Compiler unterscheidet zwei Hauptmodi, die man für das Erstellen der Anwendung konfigurieren kann: Debug und Release. Diese Modi lassen sich ändern, indem man im Menü Projekt den Befehl Einstellungen wählt oder (Alt)+(F7) drückt. Es erscheint das Dialogfeld Projekteinstellungen (siehe Abbildung E.1). Die Haupteinstellungen des Projekts sind auf oberster Ebene zu sehen und lassen sich ändern, indem man eine der im Kombinationsfeld aufgeführten Optionen markiert. Ist eine Einstellung markiert, beziehen sich alle Änderungen, die Sie an den Optionen auf den Registerkarten im rechten Teil des Dialogfelds vornehmen, auf diese Konfiguration. Wenn Sie die Anwendung erstellen, gelten dabei die aktuellen Konfigurationseinstellungen. Haben Sie Alle Konfigurationen gewählt, wirken die Änderungen für alle Konfigurationen gleichzeitig.
Abbildung E.1:
Die Registerkarte C/C++ des Dialogfelds Projekteinstellungen
Sobald Sie ein neues Projekt anlegen, stehen sowohl die Release- als auch die Debug- Konfiguration zur Verfügung. Die beiden Modi erzeugen unterschiedlichen Objektcode. In der Konfiguration für den Debug-Modus entsteht ein großes und relativ langsames ausführbares Programm. Das hängt damit zusammen, daß eine Unmenge von Debugging-Informationen in das Programm eingebunden werden und alle Optimierungen des Compilers deaktiviert sind.
Wenn Sie dasselbe Programm im Release-Modus erneut kompilieren, erhalten Sie ein kleines und schnell laufendes Programm, können aber nicht den Quellcode im Schrittbetrieb durchgehen oder irgendwelche Debugging-Meldungen erhalten.
Während der Entwicklungsphase einer Anwendung läßt man den Compiler normalerweise im Debug-Modus, damit man Probleme im Code schnell und einfach einkreisen und beseitigen kann. Haben Sie die Anwendung fertiggestellt und bereiten sie für den Vertrieb vor, können Sie die Konfiguration auf Release-Modus stellen und ein kleines, schnelles Programm für die Benutzer produzieren.
Auf der Registerkarte C/C++ des Dialogfelds Projekteinstellungen lassen sich verschiedene Optionen und Stufen für das Debuggen einstellen. Diese Dialogseite erreichen Sie über den Befehl Einstellungen des Menüs Projekt (oder durch Drücken von (Alt)+(F7)). Im Dialogfeld Projekteinstellungen aktivieren Sie dann die Registerkarte C/C++.
In der Kategorie Allgemein sind folgende Elemente verfügbar:
Auf dieser Stufe führt sogar der vom Microsoft-eigenen Anwendungs-Assistenten generierte Code zu Warnmeldungen (auch wenn es sich nur um nicht genutzte Funktionsparameter handelt, die man getrost ignorieren kann).
#ifdef
, #else
und #endif
einsetzen,
um Codeabschnitte in bestimmten Konfigurationen zu kompilieren. Die
Definition _DEBUG
ist im Debug-Modus per Voreinstellung gesetzt. Damit kann
man Code, der nur im Debug-Modus auszuführen ist, kompilieren, wie etwa im
folgenden Beispiel:
int a = b * c / d + e;
#ifdef _DEBUG
CString strMessage;
strMessage.Format("Zwischenergebnis (Summe): %d", a);
AfxMessageBox(strMessage);
#endif
Mit dem Hilfsprogramm Quellcode-Browser können Sie Ihren Quellcode im Detail inspizieren. Dieses Werkzeug ist unschätzbar, wenn Sie den Code eines anderen Programmierers untersuchen oder zu Ihrem eigenen Werk zurückkehren, mit dem Sie eine ganze Zeitlang nichts zu tun hatten.
Um das Programm zu starten, drücken Sie (Alt)+(F12) oder wählen im Menü Extras den Befehl Quellcode-Browser. (Wenn Sie das erste Mal dieses Programm aufrufen, erscheint die Frage, ob Sie das Projekt neu erstellen möchten, um die Browse-Informationen zu generieren.)
Das erste Dialogfeld des Quellcode-Browsers fragt nach einem Bezeichner, nach dem zu suchen ist (wie es Abbildung E.2 zeigt). Dabei kann es sich um den Namen einer Klasse, einer Struktur, einer Funktion oder globalen oder lokalen Variablen in Ihrer Anwendung handeln. Nachdem Sie einen Bezeichner eingegeben haben, ist die Schaltfläche OK aktiviert, und Sie können nach Einzelheiten zu diesem Bezeichner fahnden.
Abbildung E.2:
Das Dialogfeld Durchsuchen fordert ein zu suchendes Symbol an.
Im Listenfeld Abfrage auswählen finden Sie verschiedene Optionen bezüglich der Details, die zum gewählten Symbol gehören. Es stehen folgende Optionen zur Wahl:
Abbildung E.3:
Der Quellcode-Browser zeigt hier Definitionen und Referenzen an.
Abbildung E.4:
Die Ansicht der Dateigliederung des Quellcode-Browsers
Abbildung E.5:
Die Ansicht der Basisklassen und Elemente des Quellcode-Browsers
CWnd
zeigt.
Abbildung E.6:
Die Ansicht der abgeleiteten Klassen und Elemente des Quellcode-Browsers zeigt Klassen, die von CWnd abgeleitet sind.
Zum Debugger gehören Werkzeuge, mit denen man ein laufendes Programm auf einem
Remote-Computer (sogar über das Internet via TCP/IP) debuggen kann. Das ist
hilfreich, wenn man die Anwendung in einer anderen Umgebung als der Entwicklungsmaschine
testen möchte. Dazu müssen Sie über genau die gleichen Versionen
der .dll
- und .exe
-Dateien auf beiden Maschinen verfügen. Nach dem Laden des
Projekts, können Sie es über ein gemeinsam genutztes Verzeichnis von der Remote-
Maschine aus debuggen, indem Sie im Dialogfeld Projekteinstellungen auf der Registerkarte
Debug in das Eingabefeld Ausführbares Programm für Debug-Sitzung
den Pfad und Dateinnamen der lokalen .exe
-Datei eintragen.
Der Pfad zur .exe
-Datei ist auch in das Eingabefeld Pfad und Name des ausführbaren
Remote-Programms im unteren Teil der Registerkarte Debug einzutragen. Das
Eingabefeld Arbeitsverzeichnis lassen Sie dabei leer. Dann können Sie den Monitor
des Remote-Debuggers auf dem Remote-Computer starten, indem Sie das Programm
MSVCMON.EXE
aufrufen. Eine Verbindung zum Remote-Computer stellen Sie über den
Befehl Remote-Verbindung des Debuggers im Menü Erstellen her.
Im Dialogfeld Remote-Verbindung können Sie als Verbindung Lokal für eine Sitzung mit gemeinsam genutztem Verzeichnis oder Netzwerk (TCP/IP) beim Debuggen über eine TCP/IP-Verbindung wählen. (Um die Adresse festzulegen, klicken Sie auf die Schaltfläche Einstellungen.) Damit wird eine Verbindung zum Remote-Monitor hergestellt, der die Remote-Debugging-Sitzung startet.
Beim Just-In-Time-Debugging können Sie ein Programm debuggen, das normal (nicht über den Debugger) läuft und dann ein Problem verursacht. Wenn Sie Visual C++ auf einem Computer installiert und diese Option aktiviert haben, wird jedes Programm, das einen Fehler verursacht, in eine neue Sitzung des Visual Studios zum Debuggen geladen und zeigt den Code, der den Absturz verursacht hat.
Lustig wird es, wenn Visual Studio selbst abstürzt, daraufhin eine weitere Sitzung von sich selbst lädt und eine Assemblercode-Ansicht zeigt, wo der Absturz im Original stattgefunden hat, damit Sie sich auf die Fehlersuche machen können. Es kann sehr hilfreich sein, die eigenen Anwendungen auf Fehler zu untersuchen, wenn sie unerwartet abstürzen (normalerweise bei einer Vorführung für Ihren Chef). Um diese Option einzuschalten, wählen Sie aus dem Menü Extras den Befehl Optionen. Im Dialogfeld Optionen gehen Sie auf die Registerkarte Debug und schalten das Kontrollkästchen Just-In-Time-Debugging ein.
Das Kontrollkästchen OLE RPC-Debugging auf dieser Registerkarte ist ebenfalls sehr hilfreich, wenn Sie COM- und DCOM-Anwendungen entwickeln, weil der Debugger damit in einen prozeßexternen Funktionsaufruf von Programmen oder DLLs springen und eine zweite Instanz des Debuggers starten (oder aktivieren) kann, um den anderen Prozeß zu verarbeiten. Die Steuerung kehrt dann zurück, wenn die Rückkehr aus der Remote-Funktion stattfindet. Das Ganze funktioniert in Netzwerken und mit verschiedenen Computern.
Eines der nützlichsten Features der Debugging-Umgebung von Visual C++ ist der interaktive Einzelschrittbetrieb. Damit läßt sich der Code zeilenweise ausführen und der Inhalt der Variablen untersuchen. Außerdem kann man Haltepunkte setzen, so daß das Programm bis zu diesem Haltepunkt läuft und dort stoppt. Von hier aus kann man im Einzelschrittbetrieb arbeiten, bis man das Programm normal fortsetzen möchte.
Trace- und Assertion-Anweisungen unterstützen ebenfalls die Suche nach Programmfehlern. Mit Trace-Anweisungen kann man Meldungen und Variablen aus dem Programm heraus im Ausgabefenster anzeigen, während das Programm durch diese Trace-Anweisungen läuft. Assertions erlauben es, das Programm anzuhalten, wenn eine Bedingung nicht erfüllt ist, obwohl sie eigentlich erfüllt sein müßte.
TRACE
-Makros können Sie an den verschiedensten Stellen in Ihr Programm einbauen,
um etwa zu kennzeichnen, daß bestimmte Teile des Codes ausgeführt wurden, oder
um den Inhalt von Variablen an diesen Punkten anzuzeigen. Die TRACE
-Makros werden
in der Debug-Konfiguration in den Code kompiliert und im Ausgabefenster auf
der Registerkarte Debug angezeigt, wenn Sie das Programm über den Debugger ausführen.
Die TRACE
-Makros brauchen Sie für die Release-Version des Programms nicht zu entfernen,
da sie der Compiler beim Erstellen automatisch aus dem Zielobjekt ausschließt.
Mit einem Formatstring als erstem Parameter an das TRACE
-Makro können Sie einfache
Meldungen anzeigen oder den Inhalt von Variablen ausgeben. Der Formatstring
entspricht im Aufbau völlig den Strings, die man an die Funktionen printf
oder
CString::Format
übergibt. Es lassen sich verschiedene spezielle Formatcodes festlegen
wie %d
zur dezimalen Anzeige einer Zahl, %x
zur hexadezimalen Anzeige einer
Zahl oder %s
zur Anzeige von Strings. Die darauffolgenden Parameter müssen dann in
der entsprechenden Reihenfolge der Formatcodes stehen. Beispielsweise bewirkt der
Code
int nMyNum = 60;
char* szMyString = "Mein String";
TRACE("Zahl = %d, oder %x (hex). Der String lautet: %s\n",
nMyNum, szMyString);
die Ausgabe der folgenden Trace-Zeile:
Zahl = 60, oder 3c (hex). Der String lautet: Mein String
Listing E.1 demonstriert, wie man mit dem Makro TRACE
den Inhalt eines Arrays vor
und nach dem Sortieren durch einen zwar nicht sehr effizienten aber einfachen Sortieralgorithmus
anzeigt.
Wenn Sie den in Listing E.1 dargestellten Code ausprobieren möchten, erstellen Sie
mit dem Anwendungs-Assistenten das Gerüst einer einfachen SDI-Anwendung. Fügen
Sie den Code über der Elementfunktion OnNewDocument
der Dokumentklasse hinzu,
und rufen Sie ihn über DoSort()
in Ihrer Funktion OnNewDocument
auf.
Die Anwendung können Sie über den Debugger ausführen, um die Ausgaben zu verfolgen. (Wählen Sie Erstellen / Debug starten / Ausführen.)
Stellen Sie sicher, daß das Ausgabefenster sichtbar ist (beispielsweise durch Wahl von Ansicht / Ausgabe), und aktivieren Sie hier die Registerkarte Debug.
Listing E.1: DebTestDoc.cpp - Eine einfache Sortierroutine, die die Verfahren zum Debuggen demonstriert
1: void Swap(CUIntArray* pdwNumbers,int i)
2: {
3: UINT uVal = pdwNumbers->GetAt(i);
4: pdwNumbers->SetAt(i, pdwNumbers->GetAt(i+1));
5: pdwNumbers->SetAt(i+1,uVal);
6: }
7:
8: void DoSort()
9: {
10: CUIntArray arNumbers;
11: for(int i=0;i<10;i++) arNumbers.Add(1+rand()%100);
12:
13: TRACE("Vor dem Sortieren\n");
14: for(i=0;i<arNumbers.GetSize();i++)
15: TRACE("[%d] = %d\n",i+1,arNumbers[i]);
16:
17: BOOL bSorted;
18: do
19: {
20: bSorted = TRUE;
21: for(i=0;i<arNumbers.GetSize()-1;i++)
22: {
23: if (arNumbers[i] > arNumbers[i+1])
24: {
25: Swap(&arNumbers,i);
26: bSorted = FALSE;
27: }
28: }
29: } while(!bSorted);
30:
31: TRACE("Nach dem Sortieren\n");
32: for(i=0;i<arNumbers.GetSize();i++)
33: TRACE("[%d] = %d\n",i+1,arNumbers[i]);
34: }
Listing E.1 sortiert ein Array von Zufallszahlen (zwischen 1 und 100), die in Zeile 11
generiert werden. Die Zeilen 13 bis 15 geben den Inhalt des Arrays vor dem Sortieren
mit Hilfe von TRACE
-Anweisungen aus. Die Zeilen 17 bis 29 sortieren das Array, indem
paarweise aufeinanderfolgende Zahlen ausgetauscht werden, wenn sie nicht der
geforderten Sortierrichtung entsprechen (und zwar durch Aufruf der Funktion Swap
in
Zeile 25). Die Funktion Swap
in den Zeilen 1 bis 6 übernimmt einen Zeiger auf das Array
sowie die aktuelle Position und vertauscht dann die beiden Zahlen an dieser Position.
Nach dem Sortieren wird der Inhalt des Arrays erneut im Ausgabefenster durch die
TRACE
-Anweisungen in den Zeilen 31 bis 33 angezeigt.
Die Trace-Ausgabe des Programms erscheint im Ausgabefenster des Visual Studios, wie es Tabelle E.3 angibt.
Mit dem Makro ASSERT
kann man sich vergewissern, ob Bedingungen TRUE
sind. Als
Parameter übergibt man an ASSERT
einen Ausdruck, der entweder TRUE
oder FALSE
liefert.
Wenn der Ausdruck gleich TRUE
ist, läuft alles bestens. Liefert der Ausdruck FALSE
, stoppt das Programm, und es erscheint das Dialogfeld Debug Assertion Failed!
(siehe Abbildung E.7). Das Dialogfeld bietet die Optionen Beenden des Programms,
Wiederholen des Codes oder Ignorieren der Annahme. Außerdem zeigt es das Programm,
die Quellcodedatei und Zeilennummer an, wo die Annahme (Assertion) fehlerhaft
war. Wenn Sie Beenden wählen, wird die Sitzung beendet. Wiederholen ist
die wahrscheinlich nützlichste Option, weil der Compiler dann den Code zeigt, wo das
Makro ASSERT
gescheitert ist und Sie die Ursache herausfinden können. Wenn Sie bereits
die Annahme kennen oder sich nicht darum kümmern möchten, wählen Sie
Ignorieren und setzen das Programm fort, was dann allerdings zu einem schlimmeren
Fehler führen kann.
Abbildung E.7:
Das Dialogfeld Debug Assertion Failed! hilft beim Aufspüren von Bugs
Mit ASSERT
prüft man oftmals, ob Eingabeparameter an Funktionen korrekt sind. Beispielsweise
können Sie die (in Listing E.1 dargestellte) Funktion Swap
robuster gestalten,
indem Sie deren Eingabeparameter überprüfen. Fügen Sie dazu ASSERT
-Makros
am Beginn der Funktion Swap
wie folgt hinzu:
ASSERT(pdwNumbers);
ASSERT(i>=0 && i<10);
Damit ist sichergestellt, daß der Zeiger auf das Zahlenarray ungleich null ist und daß
die Position zum Vertauschen zwischen 0 und 9 liegt. Wenn eine dieser Bedingungen
nicht korrekt ist, wird das Dialogfeld Debug Assertion Failed! angezeigt. Mit dieser
Technik lassen sich Fehler aufspüren, die durch die Übergabe falscher Parameter an
Funktionen entstehen. Es empfiehlt sich, mit dem Makro ASSERT
zu prüfen, ob die
übergebenen Werte an die einzelnen Funktionen mit den eigenen Erwartungen übereinstimmen.
Ein anderes Makro, ASSERT_VALID
, kann man bei von CObject
abgeleiteten Klassen -
wie bei den meisten MFC-Klassen - verwenden. Dieses Makro führt einen umfangreichen
Test des Objekts und seines Inhalts aus, um sicherzustellen, daß das gesamte Objekt
in einem korrekten und gültigen Zustand ist. Auf das zu überprüfende Objekt
übergibt man einen Zeiger wie im folgenden Beispiel:
ASSERT_VALID(pdwNumbers);
Das Makro ASSERT_KINDOF
prüft bei von CObject
abgeleiteten Klassen, ob der Klassentyp
korrekt ist. Beispielsweise kann man testen, ob ein Zeiger auf das Ansichtsobjekt
von der richtigen Ansichtsklasse ist:
ASSERT_KINDOF(CYourSpecialView, pYView);
Das Dialogfeld Debug Assertion Failed! erscheint, wenn das Objekt nicht vom korrekten Klassentyp oder aller davon abgeleiteten Typen ist.
Achten Sie darauf, keinerlei Code, der für die normale Programmausführung erforderlich
ist, in ASSERT
-Makros unterzubringen, da sie in der Release-Version ausgeschlossen
werden. Eine allgemeine Quelle für Fehler bei Anwendungen im Release-Modus,
die man schwer auffinden kann, ist etwa folgender Code:
int a = 0;
ASSERT(++a > 0);
if (a > 0) MyFunc();
Im Debug-Modus inkrementiert dieser Code die Integer-Zahl a
in der ASSERT
-Zeile und
ruft dann in der folgenden Zeile die Funktion MyFunc
auf, weil a
größer als 0
ist. Angenommen,
Ihr Verkaufsteam ist darauf versessen, Ihre neue Anwendung vorzuführen.
Da es im Debug-Modus keinerlei Probleme gegeben hat, gehen Sie davon aus, daß alles
in Ordnung ist. Also kompilieren Sie die Anwendung ruhigen Gewissens im Release-Modus
und geben sie an die Verkaufsabteilung weiter. Diese zeigt die Anwendung
dem Kunden, und die Anwendung stürzt ab! Alles nur deshalb, weil das ++a
nicht
ausgeführt wird - der Release-Modus schließt die ASSERT
-Zeilen aus.
Das Makro VERIFY
unterstützt Sie bei diesem Problem. VERIFY
funktioniert wie ASSERT
und bringt im Debug-Modus das gleiche Dialogfeld Debug Assertion Failed! auf den
Bildschirm, wenn der Ausdruck 0 ist. Im Release-Modus wird der Ausdruck allerdings
trotzdem ausgeführt, wobei aber das Ergebnis 0
nicht zur Anzeige des Dialogfelds Debug
Assertion Failed! führt. Vielleicht neigen Sie nun dazu, VERIFY
immer zu verwenden,
wenn Sie einen Ausdruck auswerten, und ASSERT
, wenn Sie nur beim Debuggen
eine Überprüfung vornehmen wollen. Wenn Sie im vorherigen Beispiel ASSERT
durch VERIFY
ersetzen, läuft der Code auch im Release-Modus ordnungsgemäß:
VERIFY(++a > 0);
Höchstwahrscheinlich setzen Sie aber VERIFY ein, um Rückgabecodes aus Funktionen zu prüfen:
VERIFY(MyFunc() != FALSE);
Ein Großteil der Probleme läßt sich wahrscheinlich am effektivsten mit den Debugging-Werkzeugen für den Einzelschrittbetrieb und das Setzen von Haltepunkten lösen. Die Unterstützung für verschiedene Typen von Haltepunkten und die verfügbaren Informationen im Einzelschrittbetrieb sind in Visual C++ sehr ausgeklügelt. Hier können wir nur eine Vorstellung davon vermitteln, wie leistungsfähig dieses Werkzeug zur Fehlersuche ist.
Den Schlüssel für den Einzelschrittbetrieb bilden die Haltepunkte. Man kann einen Haltepunkt an jeder beliebigen Stelle im Code setzen und dann das Programm über den Debugger starten. Ist der Haltepunkt erreicht, erscheint der Code im Editorfenster an der Position des Haltepunkts. Ab hier können Sie die Programmausführung im Einzelschrittbetrieb oder im normalen Ausführungsmodus fortsetzen.
Um einen Haltepunkt zu setzen, markieren Sie die betreffende Codezeile (indem Sie mit dem Bearbeitungscursor im Editorfenster in dieser Zeile klicken) und klicken dann auf das Symbol Haltepunkt in der Minileiste erstellen (siehe Abbildung E.8) oder drücken (F9). Noch komfortabler lassen sich Haltepunkte setzen oder entfernen, indem Sie im Menü Bearbeiten den Befehl Haltepunkte wählen, um das Dialogfeld Haltepunkte zu öffnen (siehe Abbildung E.9). Ein gesetzter Haltepunkt ist durch einen roten Kreis neben der spezifizierten Zeile gekennzeichnet. Haltepunkte kann man nur für gültige Codezeilen festlegen, so daß Visual Studio manchmal einen von Ihnen gesetzten Haltepunkt automatisch auf die am nächsten liegende gültige Codezeile verschiebt.
Abbildung E.8:
Einen Haltepunkt fügen Sie in den Code über die Minileiste erstellen oder durch Drücken von (F9) ein.
Abbildung E.9:
Einen Haltepunkt über das Dialogfeld Haltepunkte hinzufügen.
Haltepunkte schaltet man ein oder aus, indem man auf das Symbol Haltepunkt (mit dem Bild einer Hand) klickt. Haltepunkte lassen sich auch über das Dialogfeld Haltepunkte entfernen. Dafür stehen die Schaltflächen Entfernen und Alle entf.(ernen) zur Verfügung. Haltepunkte kann man auch beibehalten, aber deaktivieren. Dazu schaltet man im Dialogfeld Haltepunkte das Kontrollkästchen neben dem im Listenfeld aufgeführten Haltepunkt aus. Ein erneutes Klicken auf das Kontrollkästchen schaltet es ein und aktiviert den Haltepunkt wieder.
Nachdem Sie den oder die Haltepunkt(e) gesetzt haben, können Sie den Code über den Debugger mit Erstellen / Debug starten / Ausführen ablaufen lassen. Alternativ klicken Sie auf das Symbol Ausführen (links neben dem Symbol Haltepunkt auf der Minileiste erstellen - siehe Abbildung E.8) oder drücken die Taste (F5).
Das Programm läuft dann normal, bis es den Haltepunkt erreicht. Hier stoppt die Programmausführung, und ein Pfeil zeigt auf die Zeile mit dem Haltepunkt. Jetzt können Sie über die Symbolleiste Debug im Einzelschrittbetrieb weiterarbeiten, wie es Abbildung E.10 zeigt.
Abbildung E.10:
Der Debugger hat am Haltepunkt gestoppt und erlaubt die Ausführung des Programms im Einzelschrittbetrieb über die Symbolleiste Debug.
Wenn der Debugger gestoppt hat, läßt sich der Inhalt der meisten Variablen anzeigen, indem man einfach den Cursor über die Variable im Editorfenster führt. Der Inhalt der Variablen erscheint dann in einer Art QuickInfo an der Cursorposition. Nähere Angaben zum Inhalt kann man erhalten, wenn man die Variable in das Überwachungsfenster zieht, wie es der nächste Abschnitt erläutert.
Um den Code in Einzelschritten zu durchlaufen, verwenden Sie die vier Schaltflächen mit den geschweiften Klammern auf der Symbolleiste Debug oder wählen im Menü Debug einen der Schrittbefehle aus. Die verfügbaren Schrittoptionen zeigt Tabelle E.4. Die Befehle sind sowohl im Menü Debug als auch auf der Symbolleiste Debug enthalten.
Mit diesen Befehlen können Sie den Programmablauf überwachen und den Inhalt der Variablen verfolgen, wie sie der Code manipuliert. Der gelbe Pfeil im Editorfenster zeigt immer auf die als nächstes auszuführende Anweisung.
Der nächste Abschnitt beschäftigt sich näher mit den Debug-Fenstern, die Sie bei angehaltenem Debugger verwenden können.
Ein wesentliches neues Merkmal von Visual C++ 6 ist die Fähigkeit, den Code zu bearbeiten und dann fortzusetzen. Das bedeutet, daß man den Code ändern oder bearbeiten kann, während der Debugger angehalten hat. Nach der Bearbeitung wird der Befehl Code-Änderungen zuweisen im Menü Debug (sowie das entsprechende Symbol auf der Symbolleiste Debug) aktiviert. Sie können dann den Befehl (oder die entsprechende Schaltfläche) Code-Änderungen zuweisen wählen, um die vorgenommenen Änderungen am Code zu kompilieren und dann das Debuggen mit dem geänderten Code fortzusetzen. Auf diese Weise lassen sich während des Debuggens Programmfehler beseitigen, wobei man mit der Fehlersuche an der gleichen Stelle des Codes und vor allem mit den gleichen Variableneinstellungen fortsetzen kann. Insbesondere beim Debuggen größerer und komplexer Programme kommen die Vorteile dieses neuen Funktionsmerkmals voll zur Geltung.
Abbildung E.11 zeigt die Debug-Fenster Überwachung und Variablen. Die Fenster zeigen den Inhalt von Variablen an, wenn der Debugger gestoppt hat. Um die Fenster einzusehen, wählen Sie aus dem Menü Ansicht den Befehl Debug-Fenster und anschließend den jeweiligen Eintrag des aufklappenden Untermenüs oder klicken auf die Symbole der Symbolleiste.
Abbildung E.11:
Das Überwachungsfenster zeigt den Inhalt von Variablen während des Debuggens an.
Das Variablenfenster zeigt immer die lokalen Variablen der im Kombinationsfeld Kontext am oberen Rand des Fensters aufgeführten Funktion an. Um zur aktuellen Funktion zu gelangen, können Sie die Liste des Kombinationsfelds öffnen. Es erscheinen dann alle Funktionen, die nacheinander aufgerufen wurden. Es handelt sich hierbei um den sogenannten Aufruf-Stack. Er zeigt den aktuellen Kontext innerhalb des Programms mit einer Liste von Funktionen, die aufgerufen wurden, um zur momentan ausgeführten Funktion des Programms zu gelangen, wo der Debugger gestoppt hat. Wenn Sie eine andere Funktion wählen, werden die relevanten lokalen Variablen für diese Funktionsebene angezeigt.
Die Objektzeiger kann man erweitern, indem man auf das Plussymbol neben dem Zeigernamen
klickt. Der spezielle C++-Zeiger this
wird immer für Elementfunktionen
von Klassen angezeigt und läßt sich öffnen, um alle Elementvariablen für das aktuelle
Objekt anzuzeigen.
In das Überwachungsfenster kann man Variablennamen über die Tastatur eintragen oder Variablen aus dem Editorfenster ziehen (nachdem man sie mit dem Mauszeiger markiert und invertiert hat). Die in den angezeigten Variablen gespeicherten Werte sind solange zu sehen, bis die Variablen den Gültigkeitsbereich verlassen (das heißt, nicht mehr für die momentan untersuchte Funktion relevant sind).
Es lassen sich auch einfache Typumwandlungen und Array-Indizes im Überwachungsfenster eingeben, um die zugehörigen Werte anzuzeigen. Klicken mit der rechten Maustaste schaltet die angezeigten numerischen Werte zwischen hexadezimaler und dezimaler Darstellung um. Bei der schrittweisen Untersuchung des Programms werden die im Überwachungsfenster angezeigten Variablen jeweils aktualisiert, so daß man verfolgen kann, wie das Programm die Variablen ändert.
Weitere Fenster des Debuggers sind über Ansicht / Debug-Fenster , das Menü Debug bzw. über die Symbole auf der Symbolleiste Debug erreichbar. Zu diesen Fenstern gehören:
KERNEL32! bff88f75()
Neben den integrierten Debugger-Werkzeugen gibt es verschiedene eigenständige und nützliche Hilfsprogramme. Diese lassen sich über das Menü Extras und den jeweiligen Menübefehl starten.
Die zusätzlichen Programme dienen vor allem dazu, spezielle Elemente des Betriebssystems wie Windows-Nachrichten, laufende Prozesse und registrierte OLE-Objekte zu verfolgen, um die verfügbaren Informationen beim Debuggen einer Anwendung zu erweitern.
Spy++ gehört zweifellos zu den nützlichsten dieser Werkzeuge. Mit Spy++ kann man die hierarchischen Beziehungen von übergeordneten zu untergeordneten Fenstern, die Position und Flageinstellungen für Fenster und die Basisklassen der Fenster sehen. Außerdem lassen sich die Nachrichten überwachen, die Windows an ein Fenster schickt.
Beim Start von Spy++ erscheinen alle Fenster auf dem aktuellen Desktop, deren gleichgeordnete Fenster und die Basisklassen der Fenster für jedes Objekt (siehe Abbildung E.12). In der in Abbildung E.12 dargestellten Ansicht wurde ein Bildlauf nach unten bis zur CD-Wiedergabe von Microsoft Windows durchgeführt. Spy++ zeigt alle Schaltflächen und Kombinationsfelder, die wiederum selbständige Fenster als untergeordnete Fenster zum Hauptfenster der CD-Wiedergabe sind.
Abbildung E.12:
Das Fenster von Spy++ mit dem Abschnitt CD-Wiedergabe des Windows-Desktop.
Das Menü Spy enthält folgende Befehle:
Abbildung E.13:
Mit den Nachrichtenoptionen von Spy++ lassen sich Fenster lokalisieren.
WM_LBUTTONUP
mit den zugehörigen Positionsparametern zu erkennen.
Abbildung E.14:
Windows-Nachrichten für eine Symbolleiste, die durch Spy++ angemeldet ist.
Spy++ läßt sich aufgrund der umfangreichen Funktionen an dieser Stelle nicht erschöpfend behandeln. Das Programm ist aber als Werkzeug unübertroffen, wenn Sie sich näher mit der Struktur der Hierarchie und der Nachrichtenverarbeitung unter Windows beschäftigen möchten. Mit Spy++ können Sie eine Menge Kenntnisse sammeln, indem Sie sich einfach einmal kommerzielle Anwendungen ansehen. Spy++ ist auch ein hervorragendes Hilfsmittel, um Probleme in der eigenen Anwendung mit der Nachrichtenverarbeitung aufzuspüren, um sich zu vergewissern, daß die Fenster die korrekten Nachrichten erhalten, und um zu sehen, wie diese Nachrichten aufeinanderfolgen.
Mit der Anwendung Prozess-Viewer (PView95.exe
) lassen sich alle Prozesse detaillierter
als mit Spy++ anzeigen. Um das Programm zu starten, wählen Sie Start / Programme
/ Microsoft Visual Studio 6.0 / Microsoft Visual Studio 6.0-Dienstprogramme
/ Prozess-Viewer. Die Anwendung listet die Prozesse auf, die auf Ihrem
Computer laufen. Um die Liste zu sortieren, klicken Sie auf den jeweiligen Spaltenkopf.
Markieren Sie dann einen Prozeß, um alle damit verbundenen Threads anzuzeigen.
Abbildung E.15 zeigt die Prozeßanzeige, die mit der ausgewählten Anwendung
Developer Studio (MSDEV.EXE
) läuft und die Vielzahl der zugehörigen Threads anzeigt.
Abbildung E.15:
Prozeßanzeige zeigt MSDEV.EXE und dessen Threads.
Das Dienstprogramm OLE/COM-Objektkatalog zeigt alle in Ihrem System registrierten OLE/COM-Objekte einschließlich der ActiveX-Steuerelemente, Typenbibliotheken, eingebetteten Objekte, Automatisierungsobjekte und vieler anderer Kategorien.
Man kann sogar Instanzen der verschiedenen Objekte erzeugen und deren Schnittstellen im Detail ansehen. Der OLE/COM-Objektkatalog ist hilfreich, wenn man OLE/ COM-Anwendungen entwickelt oder nach einem undefinierten ActiveX-Steuerelement sucht.
Mit dem Dienstprogramm MFC-Ablaufverfolgung (siehe Abbildung E.16) kann man die normale Verfolgung stoppen oder spezielle Ablaufverfolgungen in die Protokollierung aufnehmen. Wenn Sie dieses Werkzeug wählen, erscheint eine Gruppe von Kontrollkästchen, über die Sie die Optionen zur Ablaufverfolgung einstellen können.
Abbildung E.16:
Die Optionen der MFC-Ablaufverfolgung
Es lassen sich Windows-Nachrichten, Datenbanknachrichten, OLE-Nachrichten und viele andere Stufen der Ablaufverfolgung hinzufügen, um ausgewählten Problemen auf die Spur zu kommen. Diese Nachrichten werden dann durch den MFC-Code für die verschiedenen ausgewählten Flags generiert.
Sie können sogar die normale Ablaufverfolgung, die von Ihrer Anwendung generiert wird, unterdrücken, wenn Sie das Kontrollkästchen Ablaufverfolgung aktivieren ausschalten.