Partager via


Comment : Interface entre le code exceptionnel et non Exceptionnel

Cet article explique comment implémenter la gestion des exceptions cohérente dans le module c++, et également comment traduire ces exceptions à partir de codes d'erreur aux limites de l'exception.

Quelquefois le module c++ doit interagir au code qui n'utilise pas d'exceptions (code non exceptionnel).Cette interface est appelée une limite d'exception.Par exemple, vous pouvez appeler la fonction CreateFile Win32 dans votre programme C++.CreateFile ne lève pas d'exception ; à la place il définit les codes d'erreur qui peuvent être extraits par la fonction d' GetLastError .Si votre programme C++ est non trivial, puis -y vous préférez probablement d'avoir une stratégie exception- base cohérente de gestion des erreurs.Et vous ne souhaitez pas probablement abandonner des exceptions uniquement parce que vous vous connectiez par interface au code non exceptionnel, et aucune vous souhaitez associer des stratégies exception- basées sur et non-exception- d'erreur dans votre module C++.

Fonctions d'appel non exceptionnelles C++

Lorsque vous appelez une fonction non exceptionnelle C++, l'idée est de renvoi que fonction dans la fonction c++ qui détecte toutes les erreurs puis éventuellement lève une exception.Lorsque vous concevez une telle fonction wrapper, déterminez tout d'abord le type de garantie d'exception à la fournir : -- sans jet, fort, ou de base.Ensuite, créez la fonction afin que toutes les ressources, par exemple, handles de fichiers, être correctement libérées si une exception est levée.En général, cela signifie que vous utilisez des pointeurs intelligents ou les gestionnaires des ressources semblables au nettoyage des ressources.Pour plus d'informations sur les considérations de conception, consultez Comment : Conception de la sécurité d'exception.

Hh279691.collapse_all(fr-fr,VS.110).gifExemple

L'exemple suivant montre les fonctions C++ qui utilisent Win32 CreateFile et ReadFile fonctionne en interne pour ouvrir et lire deux fichiers.La classe d' File est une saisie de ressource est wrapper de (RAII) d'initialisation pour les handles de fichiers.Son constructeur détecte un état introuvable « fichier » et lève une exception pour propager l'erreur en haut de la pile des appels du module C++.Si une exception est levée après qu'un objet d' File soit entièrement construit, le destructeur appelle automatiquement CloseHandle pour libérer le handle de fichier.(Si vous préférez, vous pouvez utiliser la classe de (ATL) CHandle ATL à cette même objectif, ou unique_ptr avec un suppression personnalisé.) La fonction d' DiffHandles détecter des erreurs de lecture puis lève des exceptions C++.La fonction d' DiffFiles ne lève pas intercepte pas d'exception, mais il est sécurisée du point.Il permet simplement à toutes les exceptions pour propager vers le haut de la pile des appels.Toutes les fonctions fournissent une garantie forte d'exception ; si une exception est levée à tout moment dans ces fonctions, aucune ressource n'est coulée et aucun état du programme n'est modifié.

#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;
}

Code exceptionnel appel de code non exceptionnel

Les fonctions C++ qui sont déclarées comme « extern » C peuvent être appelées par des programmes C.Les serveurs COM C++ peuvent être utilisés par du code écrit dans un des de plusieurs langages différents.Lorsque vous implémentez des fonctions en exception- publiques en C++ à l'appel par du code non exceptionnel, la fonction C++ ne doit autoriser les exceptions à la propagation vers l'appelant.Par conséquent, la fonction C++ doit spécifiquement intercepter chaque exception qu'il sait comment gérer et, le cas échéant, convertit l'exception à un code d'erreur que l'appelant inclut.Si toutes les exceptions potentielles sont connues, la fonction C++ doivent avoir un bloc d' catch(…) comme dernier gestionnaire.Dans ce cas, il est préférable de stocker une erreur irrécupérable à l'appelant, car votre programme peut être dans un état inconnu.

L'exemple suivant illustre une fonction qui suppose que toute exception peut être levée est un Win32Exception ou un type d'exception dérivé d' std::exception.La fonction intercepte toute exception de ces types et propage les informations d'erreur en tant que code d'erreur Win32 à l'appelant.

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; 
} 

Lorsque vous convertissez des exceptions à codes d'erreur, un problème potentiel est que les codes d'erreur souvent ne contiennent pas la richesse des informations qu'une exception peut stocker.Pour résoudre ce problème, vous pouvez fournir un bloc d' catch pour chaque type d'exception spécifique qui peut être levée, et exécutez l'enregistrement pour stocker les détails de l'exception avant d'être convertie en un code d'erreur.Cette approche peut créer de nombreux répétition de code si plusieurs fonctions toute utilisation que le même jeu d' catch bloque.Un bon moyen d'éviter la répétition de code consiste en la refactorisation ces blocs dans une fonction de service privée qui implémente les blocs d' try et d' catch et reçoit un objet de fonction appelée dans le bloc d' try .Dans chaque fonction publique, passez-le à la fonction de service comme expression lambda.

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; 
} 

L'exemple suivant montre comment écrire l'expression lambda qui définit le functor.Lorsqu'un functor est « inline » défini à l'aide d'une expression lambda, il est souvent plus facile à lire qu'il serait s'il s'agissait écrits en tant qu'objet nommé de fonction.

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; 
    }); 
}

Pour plus d'informations sur les expressions lambda, consultez Expressions lambda en C++.

Voir aussi

Concepts

Erreurs et gestion des exceptions C++ (moderne)

Comment : Conception de la sécurité d'exception