vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Tag D

Ausnahmebehandlung

Ausnahmen

Eine Ausnahme (englisch Exception) ist ein Objekt, das Einzelheiten über fehlerhafte Operationen speichert. Das Raffinierte der Ausnahmebehandlung besteht darin, daß man bei einem Fehler in einer Funktion auf niederer Ebene eine Ausnahme erzeugen und automatisch nach oben bis zu einer aufrufenden Funktion durchreichen lassen kann, wo sich dann ein spezieller Codeabschnitt mit allen derartigen Ausnahmen an einer zentralen Stelle beschäftigt.

Code ausführen und Fehler abfangen

Das System erkennt automatisch bestimmte Fehlerbedingungen und generiert dafür Ausnahmen. Wenn Sie sich in Ihrer Anwendung nicht selbst darum kümmern, »klettern« die Ausnahmen aus Ihrem Code heraus und werden von den Ausnahmebehandlungsmechanismen des Betriebssystems Windows abgefangen. Um sich davon ein Bild zu machen, fügen Sie einfach die folgenden beiden Zeilen in einen beliebigen Codeabschnitt ein und führen ihn aus:

CDC* pNullDC = 0;
pNullDC->SetPixel(0, 0, 0);

Die erste Zeile deklariert einen Gerätekontextzeiger pNullDC und setzt ihn auf die Speicheradresse null (was für ein Objekt ein denkbar ungeeigneter Platz ist). Auch wenn es offensichtlich an dieser Adresse kein gültiges Objekt gibt, versucht das System beim darauffolgenden Aufruf der Funktion SetPixel, das Objekt an der Adresse null zu finden. Die Hardware und Software zur Speicherverwaltung wissen, daß das Programm kein Recht hat, an dieser Adresse etwas zu unternehmen, und lösen eine Zugriffsverletzung für den Speicher aus.

Wenn Sie diese Codezeilen außerhalb des Visual C++-Debuggers ausführen, erscheint ein Dialogfeld, das allen Windows-Benutzern vertraut sein dürfte und in Abbildung D.1 zu sehen ist.

Abbildung D.1:
Das bekannte Dialogfeld bei Zugriffsverletzungen des Speichers

Wenn Sie dagegen die Anwendung über den Visual C++-Debugger ausführen, fängt der Debugger zuerst die Ausnahme für Sie ab und zeigt statt dessen das Dialogfeld des Visual Studios (siehe Abbildung D.2).

Abbildung D.2:
Die vom Visual Studio behandelte Ausnahme der Zugriffsverletzung

Zugriffsverletzungen des Speichers sind sehr schwerwiegende Ausnahmen, die Ihr Programm zum Absturz bringen, ohne daß eine Chance besteht, sie abzufangen. Es gibt viele weniger schwere Ausnahmen, wie etwa die Ausnahme CFileException für die Arbeit mit Dateien. Diese Ausnahme wird ausgelöst, wenn fehlerhafte Dateioperationen auftreten, beispielsweise das Suchen nach dem Beginn einer nicht geöffneten Datei:

CFile fileNotOpen;
fileNotOpen.SeekToBegin();

In diesem Fall erscheint ein vom System generiertes Dialogfeld (siehe Abbildung D.3). Wenn Sie auf OK klicken, setzt Ihr Programm wie üblich fort.

Abbildung D.3:
Das Windows-Standarddialogfeld für eine Dateiausnahme

Statt dem System alles zu überlassen, können Sie die Ausnahme selbst abfangen und in einer weit sanfteren Weise behandeln. Zu diesem Zweck stehen in C++ die Schlüsselwörter try und catch zur Verfügung. Beide spielen zusammen: zuerst definieren Sie einen Codeblock mit try und dann, wenn eine spezifizierte Ausnahme auftritt, wird die im catch-Block definierte Aktion ausgeführt (siehe Listing D.1).

Listing D.1: CFileExceptions mit einem try- und einem catch-Block abfangen

1: // Einen Codeblock probieren (try)
2: try
3: {
4: CFile fileNotOpen;
5: fileNotOpen.SeekToBegin();
6: }
7: catch(CFileException* e) // Dateiausnahmen abfangen (catch)
8: {
9: // Ursache der Ausnahme untersuchen
10: if (e->m_cause == CFileException::fileNotFound)
11: AfxMessageBox("Oje, vergessen, die Datei zu öffnen!");
12: e->Delete();
13: }

In Listing D.1 sind die Dateioperationen der Zeilen 4 und 5 in einen try-Block eingeschlossen. Wenn in diesen Zeilen keine Ausnahme auftritt, setzt der Code ganz normal fort. Wird allerdings eine CFileException-Ausnahme ausgelöst, fängt sie das Schlüsselwort catch in Zeile 7 ab, und die Variable e zeigt dann auf die neue Ausnahme. Zum CFileException-Objekt gehört ein m_cause-Code (Ursachencode), der genau definiert, warum die Ausnahme aufgetreten ist. Die Prüfung des Codes findet in Zeile 10 statt, und wenn es sich um den Code CFileException::fileNotFound handelt, bringt Zeile 11 das Meldungsfeld auf den Bildschirm.

Beachten Sie, daß die Member-Funktion Delete der Klasse CException (der Basisklasse von CFileException) die Ausnahme in Zeile 12 für Sie löscht. Es ist zu gewährleisten, daß Ausnahmen immer gelöscht werden, wenn die Arbeit damit beendet ist.

Der try-Block kann Aufrufe an andere Funktionen enthalten und dazu verwendet werden, alle spezifizierten Ausnahmen in einem großen Codeabschnitt der Anwendung abzufangen, wie es Listing D.2 zeigt.

Listing D.2: Ein try-Block kann viele Funktionsaufrufe und Aufrufe aus diesen Funktionen umfassen.

1: try
2: {
3: // ... Eine Menge Code
4: DoLotsOfFileHandling();
5: // ... Mehr Code
6: EvenMoreFileHandling();
7: // ... Und noch mehr Code
8: }
9: catch(CFileException* e) // Dateiausnahmen abfangen
10: {
11: // Ursache der Ausnahme untersuchen
12: if (e->m_cause == CFileException::fileNotFound)
13: AfxMessageBox("Oje, vergessen, die Datei zu öffnen!!");
14: e->Delete();
15: }

In Listing D.2 könnte die Funktion DoLotsOfFileHandling in Zeile 4 bestimmte Dateibehandlungen selbst erledigen und auch andere Funktionen aufrufen, wie es Zeile 6 mit EvenMoreFileHandling demonstriert. Sollte eine Dateiausnahme bei einer dieser Dateioperationen auftreten, wandert die Ausnahme nach oben, so daß derselbe catch-Block in den Zeilen 9 bis 13 ausgeführt wird, wobei e auf das CFileException- Objekt zeigt. Schließlich löscht Zeile 14 die Ausnahme.

Wenn Sie zwei unterschiedliche Ausnahmen vom try-Block heraus abfangen möchten, können Sie catch-Blöcke hinzufügen, um die einzelnen Ausnahmen getrennt zu behandeln, wie es Listing D.3 verdeutlicht.

Listing D.3: Zwei verschiedene Ausnahmen mit zwei spezialisierten catch-Blökken behandeln.

1: try
2: {
3: // Diese Dateioperation ist OK.
4: CMemFile fileMemFile;
5: fileMemFile.SeekToBegin();
6:
7: // Man kann aber keine zwei verschiedenen
8: // Systemressourcen mit demselben Namen haben.
9: CMutex mutex1(0,"Derselbe Name");
10: CSemaphore semaphore1(1,1,"Derselbe Name");
11: }
12: catch(CFileException* e) // Dateiausnahmen abfangen
13: {
14: if (e->m_cause == CFileException::fileNotFound)
15: AfxMessageBox("Oje, vergessen, die Datei zu öffnen!");
16: e->Delete();
17: }
18: catch(CResourceException* e) // Ressourcenausnahmen abfangen
19: {
20: // Ausnahmefehler der Ressourcen melden
21: AfxMessageBox("Oha, doppelter Ressourcenname ");
22: e->Delete();
23: }

Listing D.3 erzeugt die Datei in Zeile 4 automatisch, so daß Zeile 5 keine Dateiausnahme auslöst. Da jedoch zwei unterschiedliche Systemressourcen (ein Mutex und ein Semaphor) denselben Namen erhalten, bewirkt das eine CResourceException-Ausnahme in Zeile 10, der sich dann der zweite catch-Block in Zeile 18 widmet und das Meldungsfeld in Zeile 21 anzeigt. Wenn Sie diesen Code ausprobieren, denken Sie daran, die Anweisung #include <afxmt.h> für die Definitionen von CMutex und CSemaphore vorzusehen.

Wenn Sie Ausnahmen mit einem Rundumschlag abfangen wollen, brauchen Sie nicht für jeden Typ von Ausnahme einen eigenen catch-Block vorzusehen, sondern können die Ausnahme der Basisklasse CException abfangen. Von dieser Basisklasse sind alle anderen speziellen Ausnahmeklassen abgeleitet (siehe Listing D.4).

Listing D.4: Mit einem catch-Block alle Arten von Ausnahmen abfangen

1: // Codeblock ausprobieren
2: try
3: {
4: // Eine Menge Code...
5: }
6: catch(CException* e)
7: {
8: // Allgemeine Fehlermeldung, Einzelheiten in e
9: AfxMessageBox("Irgendwas ist schiefgegangen!");
10: e->Delete();
11: }

In Zeile 6 kommt die Basisklasse CException statt einer speziellen Ausnahme wie CFileException oder CResourceException zum Einsatz. Den Typ der Anwendung können Sie mit der Funktion IsKindOf innerhalb des catch-Blocks testen. Mit den folgenden Zeilen läßt sich zum Beispiel herausfinden, ob eine Dateiausnahme ausgelöst wurde:

if (e->IsKindOf(RUNTIME_CLASS(CFileException)))
AfxMessageBox("File Exception");

Da Ausnahmen von CObject abgeleitet sind, unterstützen sie die Informationen der MFC-Laufzeitklassen. Mit Hilfe von DECLARE_DYNAMIC und IMPLEMENT_DYNAMIC werden die Klasseninformationen im abgeleiteten Ausnahmeobjekt zusammengefaßt, so daß man mit der Funktion IsKindOf auf einen speziellen Klassentyp prüfen kann. Das Makro RUNTIME_CLASS verwandelt Klassennamen in einen Zeiger auf ein CRuntimeClass -Objekt für das angegebene Objekt. Die Member-Funktion IsKindOf gibt dann TRUE zurück, wenn das aufgerufene Objekt zu dieser Laufzeitklasse gehört.

Der Abschnitt »MFC-Ausnahmetypen« weiter hinten in diesem Anhang geht darauf ein, wie man ausnahmespezifische Informationen von allen MFC-Ausnahmetypen in einem catch-Block abfangen kann.

Systemressourcen freigeben

Dieses Prinzip zum Abfangen von Ausnahmen erweist sich als nützlich, wenn man Fehler aus umfangreichen Codeabschnitten erkennen und behandeln will. Man kann sich dadurch eine Menge Zeilen für individuelle Fehlerbehandlungsroutinen sparen, aber man muß dennoch alle Systemressourcen freigeben, die man in den Zeilen vor dem Auftreten der Ausnahme zugewiesen hat.

Ausnahmen auslösen

Wenn man einen bestimmten Codeabschnitt in einen try-Block einschließt, kann man Ausnahmen abfangen, die in diesem Codeabschnitt entstehen. Der zugehörige catch- Block behandelt dann die Ausnahme. Man kann auch die Ausnahme innerhalb eines catch-Abschnitts erneut zu einem höhergelegenen catch-Abschnitt, der den ersten einschließt, auslösen.

Verschiedene AfxThrow...-Funktionen generieren automatisch verschiedene Arten von MFC-Ausnahmen und lösen sie bis zur nächsten catch-Ebene aus, beispielsweise AfxThrowFileException oder AfxThrowMemoryException. Darauf geht der Abschnitt »MFC-Ausnahmetypen« näher ein. Allerdings erzeugen diese Funktionen - mit dem Schlüsselwort new von C++ - eine neue Instanz eines speziellen, von CException abgeleiteten Objekts. Die Ausnahme wird dann mit dem Schlüsselwort throw ausgelöst, wie es das Codefragment in Listing D.5 zeigt.

Listing D.5: Eine Ausnahme mit dem Schlüsselwort throw auslösen

1: try
2: {
3: DoSomeFileHandling();
4: }
5: catch(CFileException* e)
6: {
7: e->ReportError();
8: e->Delete();
9: }
10:
11: return TRUE;
12: }
13:
14: BOOL bSomeThingWentWrong = TRUE;
15:
16: void CExceptionalDlg::DoSomeFileHandling()
17: {
18: // Funktionen zur Dateibehandlung
19: if (bSomeThingWentWrong == TRUE)
20: {
21: CFileException* pException =
22: new CFileException(CFileException::generic);
23: throw(pException);
24: }
25:
26: // Weitere Funktionen zur Dateibehandlung
27: }

In Listing D.5 umschließt der try-Block einen Aufruf der Funktion DoSomeFileHandling in Zeile 16. Diese Funktion kann bestimmte Prozeduren zur Dateibehandlung implementieren und eine Ausnahme auslösen, wenn die Fehlerbedingung in Zeile 19 den Wert TRUE ergibt. Zeile 22 erzeugt ein neues CFileException-Objekt mit Übergabe des Flags CFileException::generic an den Konstruktor und löst dann in Zeile 23 das neue Objekt aus, das der catch-Abschnitt in Zeile 5 abfangen soll.

Das Erzeugen eines neuen, von CException abgeleiteten Objekts und die darauffolgende Verwendung des Schlüsselwortes throw bildet die Grundlage des Auslösemechanismus für Ausnahmen. Die speziellen Details, die die Ursache des Fehlers kennzeichnen, können dem CException-Objekt zugeordnet werden, oder man kann zusätzliche Informationen hinzufügen, indem man eine Klasse von der Basisklasse CException ableitet und zusätzliche Variablen zum Speichern weiterer Informationen aufnimmt.

Der catch-Block kann dann entscheiden, ob der Fehler zu schwerwiegend ist, um auf dieser Ebene behandelt zu werden. In diesem Fall läßt sich die Ausnahme zu einer höheren Ebene eines umschließenden catch-Blocks erneut auslösen. Man kann das Schlüsselwort throw (ohne Parameter) aus einem catch-Block heraus verwenden, um die Ausnahme erneut auszulösen, bevor man die Ausnahme löscht. Beispielsweise läßt sich in Listing D.5 das Schlüsselwort throw einbauen, um die Ausnahme zu einem catch-Block auf höherer Ebene neu auszulösen statt die Ausnahme zu löschen:

e->ReportError();
throw;

Nach der Meldung des Fehlers wird dann die Ausnahme erneut für einen umschließenden try-Block zum Abfangen ausgelöst. Wenn Sie diese Verschachtelung nicht implementiert haben, fängt die globale MFC außerhalb des catch-Blocks die Ausnahme ab. Mit diesem Verschachtelungsmechanismus kann man die Schwere des Fehlers bestimmen und geeignete Aktionen zur Wiederherstellung auf verschiedenen Hierarchieebenen des Programms implementieren.

Ausnahmen löschen

Wie Sie gesehen haben, sind Sie grundsätzlich dafür verantwortlich, neue Ausnahmen zu erzeugen, und Sie müssen auch diese Objekte löschen, wenn Sie die Ausnahmen behandelt haben. Wenn Sie eine der MFC-Ausnahmen löschen, sollten Sie nicht das normale C++-Schlüsselwort delete verwenden (wie bereits gezeigt), da die Ausnahme ein globales Objekt oder ein Objekt auf dem Heap sein kann. Zu diesem Zweck verfügt die Basisklasse CException über eine Delete-Funktion, die zuerst prüft, ob die Ausnahme zu löschen ist. Der Erzeuger der Ausnahme kann festlegen, ob die Ausnahme zu löschen ist, indem er TRUE als (einzigem) Parameter b_AutoDelete an den Konstruktor der Klasse CException übergibt.

MFC-Ausnahmetypen

Die Microsoft Foundation Classes verfügen über verschiedene vordefinierte, von CException abgeleitete Klassen, die während der verschiedenen Arten von MFC-Operationen zum Einsatz kommen. Davon haben Sie bereits CFileException und CResourceException kennengelernt. Der folgende Abschnitt geht näher auf diese verschiedenen Klassen und die Auslösung der Ausnahmen ein. Jede Klasse basiert auf der Klasse CException und erweitert die Funktionalität von CException für unterschiedliche Arten der Ausnahmebehandlung. Sie können auch eigene Ausnahmeklassen von CException ableiten. Eine allgemeine CUserException ist für benutzerorientierte Anwendungsausnahmen vorgesehen.

Die Basisklasse CException

CException selbst hat einen Konstruktor, der das bereits erwähnte Flag AutoDelete übernimmt und wie folgt definiert ist:

CException(BOOL b_AutoDelete);

Wenn man eine Instanz von CException oder einer abgeleiteten Klasse mit new erzeugt, sollte man sicherstellen, daß das Flag auf TRUE gesetzt ist, damit die Ausnahme mit dem C++-Schlüsselwort delete gelöscht wird. Andernfalls sollte eine globale oder stackbasierte Ausnahme TRUE übergeben, so daß sie nur dann gelöscht wird, wenn sie den Gültigkeitsbereich verliert (am Ende einer Funktion oder eines Programms, wo die Klasse deklariert ist).

Die Basisklasse enthält die Funktion Delete und zwei Funktionen zur Fehlermeldung. Mit der Funktion GetErrrorMessage kann man die Fehlermeldung in einem vordefinierten Puffer speichern und die ID einer Hilfsmeldung festlegen, die dem Benutzer die zum Fehler gehörende kontextabhängige Hilfe anzeigt. Der erste Parameter der Funktion ist die Adresse eines Zielpuffers, der die zugeordnete Fehlermeldung aufnimmt. Der zweite Parameter legt die Maximalgröße des Puffers fest, damit die im Puffer gespeicherten Meldungen nicht den Pufferbereich überschreiten. Mit dem dritten, optionalen Parameter können Sie die ID der kontextabhängigen Hilfe als UINT- Wert spezifizieren.

Mit Hilfe dieser Funktion läßt sich eine Fehlermeldung formatieren, die sich mehr auf die Anwendung bezieht:

char msg[512];
e->GetErrorMessage(msg,sizeof(msg));
CString strMsg;
strMsg.Format("In MyApp ist folgender Fehler aufgetreten: %s",msg);
AfxMessageBox(strMsg);

Der C++-Operator sizeof in der Funktion GetErrrorMessage liefert die Größe eines Arrays oder einer Variablen zurück. Wenn Sie also das msg-Array ändern, brauchen Sie keine weiteren Änderungen im Code vornehmen. Die Meldung wird dann in das CString-Objekt strMsg formatiert und in einem Meldungsfeld angezeigt.

Die Funktion ReportError zeigt den Meldungstext direkt im bekannten Meldungsfeld für Ausnahmen an und wird vom catch-Block verwendet:

e->ReportError();

Speicherausnahmen

Die Ausnahme CMemoryException wird automatisch ausgelöst, wenn die Ausführung einer Operation mit dem C++-Schlüsselwort new scheitert. Diese Ausnahme können Sie auch selbst mit Hilfe der Funktion AfxThowMemoryException auslösen. Die Bedeutung dieser Ausnahme bezieht sich ausschließlich darauf, daß Windows keinen weiteren Hauptspeicher über GlobalAlloc oder andere Funktionen zur Speicherreservierung bereitstellen kann. Das ist für jedes Programm eine höchst unerfreuliche Situation. Gewöhnlich behandelt man diese Ausnahme so, daß das Programm in gepflegter Manier beendet wird, wobei man alle Speicher- und Systemressourcen freigibt. In seltenen Fällen kann man eine Wiederherstellung versuchen, wenn man einen großen reservierten Speicherblock zur Verfügung hat und ohne Schaden für die Aktivitäten des Benutzers freigeben kann.

Aufgrund dieser einzigartigen Ausnahmeform erweitern keinerlei Ursachenattribute oder spezielle Funktionen die Funktionalität der Klasse CException.

Die automatische Auslösung der Ausnahme CMemoryException bei einer Zuweisung mit new können Sie mit den folgenden Zeilen nachvollziehen:

MEMORYSTATUS mem;
GlobalMemoryStatus(&mem);
BYTE* pBig = new BYTE[mem.dwAvailVirtual+1];

Das Strukturelement mem.dwAvailVirtual von MEMORYSTATUS nimmt den gesamten verfügbaren Speicher auf, nachdem die Funktion GlobalMemoryStatus die Details abgerufen hat. Das new auf der nächsten Zeile fordert ein Byte mehr als möglich an und löst damit die Ausnahme aus.

Ressourcenausnahmen

Die Ausnahme CResourceException wird an vielen Stellen ausgelöst, wo Systemressourcen knapp werden, wie Sie im Beispiel mit Mutex und Semaphoren in Listing D.3 gesehen haben. Wenn Sie diese Ausnahmen selbst auslösen möchten, verwenden Sie die korrespondierende Funktion AfxThrowResourceException.

Windows kann die angeforderte Ressource nicht finden oder bereitstellen und gibt keine weitere Hilfestellung. Daher hat die Klasse keine weiteren Funktionen oder Attribute.

Datei- und Archivausnahmen

In Listing D.5 haben Sie bereits CFileException kennengelernt. Das ist wahrscheinlich eine der komplizierteren MFC-Ausnahmen, da beim Dateizugriff eine ganze Menge schief gehen kann. Die Ausnahme können Sie selbst mit der Funktion AfxThrowFileException auslösen. Die Funktion übernimmt drei Parameter, einen erforderlichen und zwei optionale. Der erste, erforderliche Parameter, cause, gibt den Ursachencode für die Ausnahme an. Dieser Code wird in die Member-Variable m_cause der Dateiausnahme gestellt, damit man darauf in einem catch-Block zurückgreifen kann.

Tabelle D.1 zeigt eine Liste der verschiedenen Ursachencodes. Mit dem zweiten Parameter, lOsError, kann man einen Betriebssystemfehlercode spezifizieren, um ihn in der Elementvariablen m_lOsError der Ausnahme ablegen zu lassen. Dieser long-Wert kann bei der detaillierten Aufklärung eines Fehlers hilfreich sein, indem man die Liste der Dateizugriffsfehler des Betriebssystems selbst heranzieht. Der dritte Parameter, strFileName, wird in die Member-Variable vom Typ String der Ausnahme übertragen, um den Namen der Datei zu kennzeichnen, auf die der Zugriff beim Auftreten des Fehlers erfolgte.

Tabelle D.1: Die m_cause-Codes von CFileException

Ursachencode (Cause Code)

Bedeutung

CFileException::none

Kein Fehler.

CFileException::generic

Kein Fehlercode angegeben.

CFileException::tooManyOpenFiles

Zu viele Dateien gleichzeitig geöffnet.

CFileException::fileNotFound

Kann die angegebene Datei nicht finden.

CFileException::badPath

Der angegebene Pfadname ist ungültig.

CFileException::invalidFile

Versuch, einen ungültigen Datei-Handle zu verwenden.

CFileException::badSeek

Suchen-Operation gescheitert.

CFileException::endOfFile

Ende der Datei erreicht.

CFileException::diskFull

Kein Platz mehr auf dem Datenträger.

CFileException::hardIO

Hardwarefehler aufgetreten.

CFileException::accessDenied

Erlaubnis für den Zugriff auf die Datei verweigert.

CFileException::directoryFull

Das Verzeichnis enthält zu viele Dateien und kann keine weitere aufnehmen.

CFileException::removeCurrentDir

Kann das aktuelle Arbeitsverzeichnis nicht löschen.

CFileException::lockViolation

Kann keinen bereits gesperrten Bereich der Datei noch einmal sperren.

CFileException::sharingViolation

Ein gemeinsam genutzter Bereich ist gesperrt oder läßt sich nicht gemeinsam nutzen.

Weiterhin ist die statische Elementfunktion ThrowOsError verfügbar, die eine Dateiausnahme auf der Basis eines Fehlercodes des Betriebssystems auslöst und konfiguriert. Der Funktion ThrowOsError übergeben Sie den Fehlercode des Betriebssystems als ersten Parameter und einen optionalen Dateinamen als zweiten Parameter. Eine weitere Elementfunktion, ThrowErrno, bewirkt das gleiche, verwendet aber die errno- Fehlercodes im Stil von UNIX als einzigen Parameter (aus der Header-Datei Errno.h). Da es sich um statische Funktionen handelt, setzen Sie sie mit statischem Gültigkeitsbereich ein, um Ausnahmen mit Zeilen wie den folgenden auszulösen:

CFileException::ThrowOsError(ERROR_BAD_PATHNAME); // Ungültiger Pfad
CFileException::ThrowErrno (ENOSPC); // Datenträger voll

Eine weitere statische Elementfunktion, OsErrorToException, konvertiert automatisch zwischen den Fehlercodes des Betriebssystems und den Ursachencodes von CFileException . Man übergibt den Fehlercode des Betriebssystems, und die Funktion liefert den entsprechenden Ursachencode zurück. Die korrespondierende Funktion ErrnoToException führt das gleiche aus, wenn man einen errno-Fehlercode übergibt.

Wenn man Archive mit der Klasse CArchive nutzt, behandelt man normalerweise die Fälle CFileExceptions und CArchiveException in Verbindung: Viele der Operationen von CArchive sind mit den zugrundeliegenden Datei- und Dateizugriffsfunktionen verbunden. CArchiveExcpetion hat eine eigene Elementvariable m_cause, um die für das Archiv spezifischen Ursachencodes aufzunehmen, wie sie Tabelle D.2 zeigt. Man kann Archivausnahmen in eigener Regie über die Funktion AfxThrowArchiveException auslösen. Die Funktion erfordert als Parameter einen Ursachencode und einen lpszArchiveName-Stringzeiger für das Archivobjekt, das die Ausnahme auslöst.

Tabelle D.2: Die m_cause-Codewerte von CArchiveException

Ursachencode

Bedeutung

CArchiveException::none

Kein Fehler.

CArchiveException::generic

Die spezielle Ursache wurde nicht angegeben.

CArchiveException::badSchema

Die falsche Version eines Objekts wurde gelesen.

CArchiveException::badClass

Die Klasse des zu lesenden Objekts wurde nicht erwartet.

CArchiveException::badIndex

Ungültiges Dateiformat.

CArchiveException::readOnly

Versuch, in ein zum Lesen geöffneten Archiv zu schreiben.

CArchiveException::writeOnly

Versuch, in einem zum Speichern geöffneten Archiv zu lesen.

CArchiveException::endOfFile

Das Ende der Datei wurde beim Lesen unerwartet erreicht.

Datenbankausnahmen

Für Datenbanken gibt es zwei Ausnahmeklassen: CDBException dient dem ODBC-basierten Datenbankzugriff, und CDAOException kommt beim DAO-basierten Datenbankzugriff zum Einsatz. Mit der Funktion AfxThrowDBException kann man diese Ausnahmen selbst auslösen. Die Funktion erfordert drei Parameter. Der erste, nRetCode, spezifiziert einen der zahlreichen Rückgabecodes aus der Datenbank, um den Fehlertyp zu definieren (diese Codes finden Sie in der ODBC-Dokumentation). Der zweite Parameter, pDB, ist ein Zeiger auf die Datenbank, die mit der Ausnahme verbunden ist, und der dritte Parameter, hstmt, ist ein ODBC-Handle für das SQL-Anweisungsobjekt, dessen Ausführung die Ausnahme ausgelöst hat.

Der Typ RETCODE ist vom CDBException-Objekt über dessen Elementvariable m_nRetCode verfügbar. Man kann auch auf einen besser verständlichen Fehlertext im Elementstring m_strError und den vom ODBC-Treiber selbst im Element m_strStateNativeOrigin zurückgegebenen Fehlertext zugreifen.

Die Klasse CDAOException hat eine korrespondierende Funktion AfxThrowDaoException , die die DAO-Ausnahmeobjekte auslösen kann. Die Funktion übernimmt lediglich zwei optionale Parameter. Der erste, nAfxDaoError, ist ein DAO-spezifischer Fehlercode, der auf Probleme mit DAO selbst hinweist (siehe Tabelle D.3). Der zweite Parameter ist ein OLE-SCODE-Wert, der den Rückgabecode aus einem DAO-basierten OLE-Aufruf darstellt (siehe dazu den Abschnitt »OLE-Ausnahmen«).

Tabelle D.3: DAO-spezifische Fehlercodes von nAfxDaoError

Fehlercode

Bedeutung

NO_AFX_DAO_ERROR

Die Ausnahme geht auf ein DAO-spezifisches Problem zurück: Sie sollten das bereitgestellte CDaoErrorInfo-Objekt und den SCODE-Wert überprüfen.

AFX_DAO_ERROR_ENGINE_INITIALIZATION

Das Microsoft Jet-Datenbankmodul ist bei der Initialisierung gescheitert.

AFX_DAO_ERROR_DFX_BIND

Eine Adresse des DAO-Datensatzfeldaustauschs (DFX) ist ungültig.

AFX_DAO_ERROR_OBJECT_NOT_OPEN

Die abgefragte Tabelle ist nicht geöffnet.

Die CDAOException-Klasse hat drei Elementattribute: m_scode, das einen zugeordneten OLE-SCODE-Wert mit der versuchten Operation aufnimmt, oder S_OK, wenn die OLE-Operation erfolgreich war. Das Element m_nAfxDaoError nimmt einen der DAO- spezifischen Werte aus Tabelle D.3 auf. Der Wert m_pErrorInfo ist ein Zeiger auf eine CDaoErrorInfo-Struktur, die einen Fehlercode, beschreibende Fehlerstrings und eine Hilfekontext-ID speichert und folgendermaßen definiert ist:

struct CDaoErrorInfo
{
long m_lErrorCode;
CString m_strSource;
CString m_strDescription;
CString m_strHelpFile;
long m_lHelpContext;
};

Über diese Struktur lassen sich die meisten Details der speziellen Datenbankfehler finden, die sich auf DAO-Ausnahmen beziehen.

DAO-Ausnahmen können mehrere Fehler gleichzeitig beschreiben, so daß man mit der Funktion GetErrorCount ermitteln kann, wie viele Fehler referenziert werden. Die anderen Fehler lassen sich dann erhalten, indem man der Funktion GetErrorInfo einen Null-basierten Index auf den speziellen Fehler übergibt. Nach dem Aufruf von GetErrorInfo mit einem bestimmten Index in dem Bereich, den die Funktion GetErrorCount zurückgegeben hat, wird m_pErrorInfo aktualisiert, um auf das betreffende Objekt zu zeigen, so daß man diese Werte abrufen kann.

OLE-Ausnahmen

Die zwei Typen von OLE-Ausnahmen werden durch zwei Klassen repräsentiert: die Klasse COleException ist normalerweise für Server-seitige OLE-spezifische Operationen vorgesehen, die Klasse COleDispatchException wird bei Client-seitigen IDispatch -basierten Operationen wie etwa dem Aufruf von ActiveX-Objektfunktionen verwendet.

Die einfachere der beiden ist die Klasse COleException, die sich durch Aufruf der Funktion AfxThrowOleException generieren läßt, wobei man der Funktion einen OLE-SCODE-Wert (Statuscode) übergibt. Ein OLE-SCODE-Wert ist ein 32-Bit-Fehlercode, der alle Arten von Fehlern repräsentiert, die aus einer OLE-Funktion herrühren.

Dieser Wert stammt wahrscheinlich aus dem Rückgabecode einer Funktion, die auf einer der Schnittstellen eines OLE-Objekts aufgerufen wird. Der SCODE-Wert wird dann im Element m_sc gespeichert, um ihn innerhalb eines catch-Blocks untersuchen zu können.

Die statische Member-Funktion Process übernimmt ein Ausnahmeobjekt und überführt diese Ausnahme in einen SCODE-Wert, um die Ausnahme zu repräsentieren.

Die COleDispatchException-Klasse verwendet man in Verbindung mit der OLE- Schnittstelle IDispatch. Die Klasse wird durch die Funktion AfxThrowOleDispatchException ausgelöst. Die Funktion hat zwei Formen, die beide mit zwei erforderlichen und einem optionalen Parameter arbeiten. Der erste Parameter für beide Formen ist ein wCode-Wert vom Typ WORD, der einen anwendungsspezifischen Fehlercode darstellt. Der zweite Parameter ist in der ersten Form ein lpszDescription-Stringzeiger auf eine verbale Beschreibung des Fehlers und in der zweiten Form ein nDescriptionID -Wert vom Typ UINT für eine Ressourcen-ID der verbalen Fehlerbeschreibung. Der letzte, optionale Parameter ist eine Hilfekontext-ID.

Diese Werte sind dann im Objekt COleDispatchException als Member-Variablen m_wCode, m_strDescription und m_dwHelpContext verfügbar. Wenn man einen Hilfekontext spezifiziert und eine Hilfedatei verfügbar ist, füllt das Programmgerüst einen m_strHelpFile-String, der die Hilfedatei kennzeichnet. Der Name der Anwendung, die den Fehler produziert hat, läßt sich ebenfalls über das Attribut m_strSource ermitteln.

Wenn man eine Ausnahme aus einem OLE-Objekt auslöst, beispielsweise einem ActiveX-Steuerelement, zeigt Visual Basic oder eine andere Anwendung, die das Steuerelement oder das Objekt verwendet, die Ausnahmedetails an.

Nicht unterstützte Ausnahmen

Die von der Klasse CNotSupportedException repräsentierten Ausnahmeobjekte werden generiert, wenn man nicht unterstützte Funktionsmerkmale der MFC, des Betriebssystems oder anwendungsspezifischer Funktionen anfordert. Wenn Sie diese Ausnahme selbst auslösen wollen, verwenden Sie die Funktion AfxThrowNotSupportedException , die keinerlei Parameter übernimmt. Darüber hinaus gibt es bei dieser Ausnahme keine erweiterten Elemente oder Funktionen - genau deshalb heißt es nicht unterstützt.

Benutzerausnahmen

Mit der Klasse CUserException können Sie anwendungsspezifische Ausnahmeobjekte generieren. Beispielsweise läßt sich damit ein Vorgang anhalten, wenn der Benutzer in einer Anwendung eine bestimmte Option wählt. Wenn Sie zum Beispiel mit dem Anwendungs-Assistenten arbeiten, können Sie jederzeit <Esc> drücken, um den ganzen Prozeß abzubrechen. Microsoft hat wahrscheinlich mit CUserException gearbeitet, um die <Esc>-Taste zu erkennen und dann ein Objekt für Benutzerausnahmen auszulösen.

Diese Ausnahme läßt sich durch einen Aufruf der Funktion AfxThrowUserException auslösen und dann in den üblichen try- und catch-Blöcken auffangen. In der MFC gibt es verschiedene Stellen, wo diese Ausnahme ausgelöst wird, beispielsweise während der Gültigkeitsprüfung eines Dialogfelds oder wenn eine Datei zu groß ist, um sie im Editor anzuzeigen.

Eigene Ausnahmeklassen generieren

Ihre eigenen Ausnahmeklassen können Sie von CException ableiten und spezielle Erweiterungen der Funktionalität hinzufügen. Listing D.6 zeigt die Klassendefinition für eine derartige Ausnahmeklasse, die die normale Funktionalität um eine CString-Variable m_strMessage erweitert und damit ermöglicht, daß Sie Ihre eigene Meldung spezifizieren können, wenn Sie die Ausnahme konstruieren.

Listing D.6: In CustomException.h implementierte Klassendefinition für CCustomException

1: // Datei: CustomException.h
2: // Header-Datei für CCustomException
3:
4: class CCustomException : public CException
5: {
6: DECLARE_DYNAMIC(CCustomException);
7:
8: public:
9: CCustomException(CString strMessage);
10:
11: CString m_strMessage;
12: };

In Listing D.6 ist die Klasse in ihrer eigenen Header-Datei CustomException.h implementiert und in Zeile 4 von CException abgeleitet. Das Makro DECLARE_DYNAMIC in Zeile 6 stellt die Laufzeitinformationen der von CObject abgeleiteten MFC-Klassen bereit, die erforderlich sind, um den Ausnahmetyp in einem catch-Block für alle Ausnahmen bestimmen zu können. Die Konstruktordefinition in Zeile 9 übernimmt den Parameter strMessage vom Typ CString, damit Sie die benutzerdefinierte Ausnahme mit der Nachricht, die in der auf Zeile 11 deklarierten CString-Variablen gespeichert wird, erzeugen können.

Die entsprechende Implementierung der Klasse CCustomException ist in Listing D.7 zu sehen.

Listing D.7: Implementierung der Klasse CCustomException

1: // CustomException.cpp
2: // Implementierung für CCustomException-Ausnahme
3:
4: #include "stdafx.h"
5: #include "CustomException.h"
6:
7: IMPLEMENT_DYNAMIC(CCustomException,CException);
8:
9: CCustomException::CCustomException(CString strMessage)
10: : m_strMessage(strMessage)
11: {
12: }

In Listing D.7 sind die üblichen Header-Dateien eingebunden, und das Makro IMPLEMENT_DYNAMIC in Zeile 7 implementiert die Funktionen für die Laufzeitinformationen der MFC-Klassen. Der Konstruktor in Zeile 9 übernimmt den strMessage-Parameter und initialisiert in Zeile 10 die Elementvariable m_strMessage mit diesem Stringwert.

Die benutzerdefinierte Ausnahmeklasse können Sie dann in Ihrer Anwendung einsetzen, wie es Listing D.8 zeigt.

Listing D.8: Einsatz der neuen Klasse CCustomException

1: try
2: {
3: // Irgendwas geht schief
4: CCustomException* pCustomEx =
5: new CCustomException("Benutzerdefinierter Fehler aufgetreten ");
6: throw(pCustomEx);
7: }
8: catch(CCustomException* e)
9: {
10: // Auf erweiterten m_strMessage-String zugreifen
11: AfxMessageBox(e->m_strMessage);
12: e->Delete();
13: }

In Listing D.8 wird ein neues CCustomException-Objekt mit dem anwendungsspezifischen Fehlertext in den Zeilen 4 und 5 erzeugt und in Zeile 6 ausgelöst. Zeile 8 fängt die Ausnahme mit dem Schlüsselwort catch ab, und Zeile 11 zeigt die verwendeten benutzerdefinierten Informationen in einem Meldungsfeld an. Die Ausnahme wird dann in Zeile 12 gelöscht.

Wenn Sie diesen Code ausprobieren, denken Sie daran, daß der Implementierungscode eine #include-Anweisung für die Header-Datei CustomException.h enthalten muß, um die Klassendefinition abzurufen:

#include "CustomException.h"



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1