Freigeben über


Gewusst wie: Schnittstelle zwischen Ausnahmem und Nicht-Ausnahmem Code

In diesem Artikel wird beschrieben, wie einheitliche Ausnahmebehandlung in Ein C++-Modul und auch implementiert, wie diese Ausnahmen zu und von Fehlercodes an den Ausnahmegrenzen übersetzt.

Manchmal muss Ein C++-Modul an Code herstellen, der keine Ausnahmen verwendet (nicht-Ausnahmen Code).Eine solche Schnittstelle wird als Ausnahmegrenze.Beispielsweise können Sie die Win32-Funktion CreateFile im C++-Programm aufrufen.CreateFile löst keine Ausnahmen aus; stattdessen legt er Fehlercodes fest, die von der Funktion GetLastError abgerufen werden können.Wenn das C++-Programm nicht trivial ist, dann in ihm ziehen Sie es vor, eine konsistente Ausnahme-basierte Fehlerbehandlungscode Richtlinie zu haben.Die möchten Sie wahrscheinlich keine Ausnahmen abbrechen, weil Sie an nicht-Ausnahmen Code herstellen, und keine möchten Sie die Ausnahme-basierten und nicht-Ausnahme-basierten Fehlerrichtlinien im C++-Modul kombinieren.

Aufrufen von Nicht-Ausnahme Funktionen von C++

Wenn Sie eine nicht-Ausnahme Funktion von C++ aufrufen, ist die sich um die Funktion in Funktion in C++, die alle Fehler erkennt und dann möglicherweise eine Ausnahme auslöst.Wenn Sie eine solche Wrapperfunktion entwerfen, entscheiden Sie sich zuerst, die den Typ der Ausnahmegarantie bereitzustellen: NO-THROW, stark oder grundlegend.Zweitens entwerfen Sie die Funktion, damit alle Ressourcen beispielsweise Dateihandles, ordnungsgemäß freigegeben werden, wenn eine Ausnahme ausgelöst wird.In der Regel bedeutet dies, dass Sie intelligenten Zeiger oder ähnliche Ressourcen-Manager zu eigenem die Ressourcen verwenden.Weitere Informationen zu Entwurfsüberlegungen, finden Sie unter Gewusst wie: Entwurf zur Ausnahmesicherheit.

Hh279691.collapse_all(de-de,VS.110).gifBeispiel

Im folgenden Beispiel wird C++-Funktionen an, die Win32 CreateFile verwenden und ReadFile intern arbeitet, um zwei Dateien zu öffnen und zu lesen.Die - Klasse ist eine File Ressourcendatenerfassung ist Wrapper der Initialisierung RAII () für die Dateihandles.Sein Konstruktor erkennt eine gefundene" Zustand "der Datei nicht und löst eine Ausnahme aus, um den Fehler durch die Aufrufliste des C++-Moduls weiterzugeben.Wenn eine Ausnahme ausgelöst wird, nachdem ein File-Objekt vollständig erstellt wurde, wird der Destruktor automatisch CloseHandle auf, um das Dateihandle freizugeben.(Wenn Sie es vorziehen, können Sie die Klasse (Active Template Library) CHandle zu diesem Zweck gleichen oder unique_ptr zusammen mit einem benutzerdefinierten Deleter verwenden.) Die DiffHandles-Funktion erkennt Lesefehler und löst dann C++-Ausnahmen aus.Die Funktion DiffFiles weder löst aus noch fängt alle Ausnahmen ab, bei ist es ausnahmesich.Es können nur allen Ausnahmen, um die Aufrufliste weitergegeben.Alle Funktionen bieten eine starke Ausnahmegarantie; Wenn eine Ausnahme an jeder Stelle in diesen Funktionen ausgelöst wird, werden keine Ressourcen Verlust und kein Programmzustand wird geändert.

#include <Windows.h>
#include <iostream>
#include <string>
#include <stdexcept>

using namespace std;


class Win32Exception : public runtime_error
{    
    DWORD err;
    static const int BUF_SIZE = 1024;
    string msg;
    string localMsg;

public:

    Win32Exception(DWORD error, string msg): runtime_error(string("Win32Exception")), err(error), localMsg(msg) {}

    // Generic message output function.
    const char* what()
    {
        char buf[BUF_SIZE];
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, (LPSTR) &buf, BUF_SIZE - 1, 0); 
        msg = string(buf) + ":" + localMsg;
        return msg.c_str();       
    }


    const DWORD GetErrorCode() {return err;}
};

void ThrowLastErrorIf(bool expression, string msg) 
{ 
    if (expression) 
    { 
        throw Win32Exception(GetLastError(), msg); 
    } 
} 

bool DiffHandles(HANDLE file1, HANDLE file2)
{
    const int BUFFERLENGTH = 1024;

    char buffer1[BUFFERLENGTH] = {'\0'};
    char buffer2[BUFFERLENGTH] = {'\0'};
    DWORD bytesRead = 0;

    BOOL result = ReadFile(file1, buffer1, BUFFERLENGTH - 1, &bytesRead, NULL);
    ThrowLastErrorIf(result == FALSE, string("File1"));

    result = ReadFile(file2, buffer2, BUFFERLENGTH - 1,&bytesRead, NULL);
    ThrowLastErrorIf(result == FALSE, string("File2"));

    string s1(buffer1);
    string s2(buffer2);
    return s1 == s2;
} 

class File
{
private:
    HANDLE handle;

    // Declared but not defined, to avoid double closing.
    File& operator=(const File&);
    File(File&);
public:
    File(const wchar_t* file)
    {
        handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, 
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
        ThrowLastErrorIf(handle == INVALID_HANDLE_VALUE, GetFileName(file));
    }

    HANDLE Get()
    {
        return handle;
    }

    string GetFileName(const wchar_t* f)
    {
        char buf[1024] = {'\0'};
        wcstombs(buf, f, 1024 -1);
        return string(buf);
    }

    ~File()
    {
        CloseHandle(handle);
    }
};

bool DiffFiles(const wchar_t* file1, const wchar_t* file2) 
{ 

    File f1(file1); 
    File f2(file2); 
    bool result = DiffHandles(f1.Get(), f2.Get()); 
    return result; 
} 


int main()
{
    try
    {
        bool result = DiffFiles(L"file1.txt",
            L"file2.txt");
        if (!result)
        {
            cout << "Files do not match." << "\n";
        }
        else
        {
            cout<< "Files match." << "\n";
        }
    }

    catch(Win32Exception& e)
    {        
        cout << e.what() << "\n";
    }

    cout << "Press any key" << "\n";
    char c;
    cin >> c;
}

Aufrufen von Ausnahmer Code vom Nicht-Ausnahmen Code

C++-Funktionen, die als extern "C" deklariert werden, können durch C-Programme aufgerufen werden.C++-COM-Server können vom Code genutzt werden, der in anderen Sprachen geschrieben wurde.Wenn Sie Ausnahme-bewusste Funktionen öffentlicher in durch implementieren nicht-Ausnahmen Code aufgerufen werden, C++, ermöglicht die C++-Funktion keinen Ausnahmen ermöglichen, wieder an den Aufrufer weitergegeben.Daher muss die C++-Funktion jede Ausnahme speziell abfangen, der sie behandeln kann und, sofern zutreffend, die Ausnahme zu einem Fehlercode konvertiert, die der Aufrufer ist.Wenn nicht alle möglichen Ausnahmen bekannt ist, sollte die C++-Funktion einen catch(…)-Block als letzten Handler verfügen.In einem solchen Fall zu melden empfiehlt sich, ein schwer wiegender Fehler den Aufrufer, da das Programm sich in einem unbekannten Zustand ist.

Das folgende Beispiel veranschaulicht eine Funktion, die davon ausgeht, dass möglicherweise eine Ausnahme, die ausgelöst wird, entweder ein Win32Exception oder ein Ausnahmetyp, der von std::exception abgeleitet ist.Die Funktion wird jede Ausnahme dieser Typen ab und gibt die Fehlerinformationen als Win32-Fehlercode an den Aufrufer.

BOOL DiffFiles2(const wchar_t* file1, const wchar_t* file2) 
{ 
    try 
    { 
        File f1(file1); 
        File f2(file2); 
        if (!DiffHandles(f1.Get(), f2.Get())) 
        { 
            SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH); 
            return FALSE; 
        } 
        return TRUE; 
    } 
    catch(Win32Exception& e) 
    { 
        SetLastError(e.GetErrorCode()); 
    }

    catch(std::exception& e) 
    { 
        SetLastError(MY_APPLICATION_GENERAL_ERROR); 
    } 
    return FALSE; 
} 

Wenn Sie von Ausnahmen zu Fehlercodes konvertieren, ist ein potenzielles Problem, dass Fehlercodes häufig nicht den Reichtum von Informationen enthalten, die eine Ausnahme speichern kann.Um dies zu behandeln, können Sie einen catch-Block für jeden spezifischen Ausnahmetyp bereitstellen der möglicherweise ausgelöst würde und führen Protokollierung aus, um die Details der Ausnahme zu erfassen bevor sie einem Fehlercode konvertiert wird.Dieser Ansatz kann viel Codewiederholung erstellen, wenn mehrere verwenden alle funktioniert, die denselben Satz von catch blockiert.Eine gute Möglichkeit, Codewiederholung vermeiden, indem sie diese Blöcke in einer privaten Dienstprogrammfunktion umgestaltet, die die try und catch Blöcke implementiert und akzeptiert ein Funktionsobjekt, das im try-Block aufgerufen wird.In jeder öffentlichen Funktion führen Sie den Code zur Dienstprogrammfunktion als Lambda-Ausdruck.

template<typename Func> 
bool Win32ExceptionBoundary(Func&& f) 
{ 
    try 
    { 
        return f(); 
    } 
    catch(Win32Exception& e) 
    { 
        SetLastError(e.GetErrorCode()); 
    } 
    catch(const std::exception& e) 
    { 
        SetLastError(MY_APPLICATION_GENERAL_ERROR); 
    } 
    return false; 
} 

Das folgende Beispiel zeigt, wie der Lambda-Ausdruck schreibt, der das - Funktionselement definiert.Wenn ein Funktionselement "inline" definiert ist, mit einem Lambda-Ausdruck verwendet, ist es oft leichter lesbar, als es wäre, wenn es als benanntes Funktionsobjekt geschrieben wurde.

bool DiffFiles3(const wchar_t* file1, const wchar_t* file2) 
{ 
    return Win32ExceptionBoundary([&]() -> bool
    { 
        File f1(file1); 
        File f2(file2); 
        if (!DiffHandles(f1.Get(), f2.Get())) 
        { 
            SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH); 
            return false; 
        } 
        return true; 
    }); 
}

Weitere Informationen zu Lambdaausdrücken finden Sie unter Lambda-Ausdrücke in C++.

Siehe auch

Konzepte

Fehler und Ausnahmebehandlung modernes (C++)

Gewusst wie: Entwurf zur Ausnahmesicherheit