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.
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.
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.
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.
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.
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.
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();
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.
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.
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.
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.
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«).
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.
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.
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.
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.
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"