Processamento de erros com C++/WinRTError handling with C++/WinRT

Este tópico aborda as estratégias para processar erros ao programar com C++/WinRT.This topic discusses strategies for handling errors when programming with C++/WinRT. Para obter informações gerais e o histórico, veja Processamento de erros e exceções (C++ moderno).For more general info, and background, see Errors and Exception Handling (Modern C++).

Evitar a captura e a geração de exceçõesAvoid catching and throwing exceptions

É recomendável continuar escrevendo código à prova de exceções, mas evite a captura e a geração de exceções sempre que possível.We recommend that you continue to write exception-safe code, but that you prefer to avoid catching and throwing exceptions whenever possible. Se não houver nenhum manipulador para uma exceção, o Windows vai gerar automaticamente um relatório de erros (incluindo um minidespejo da falha), que ajudará você a detectar onde está o problema.If there's no handler for an exception, then Windows automatically generates an error report (including a minidump of the crash), which will help you track down where the problem is.

Não gere uma exceção que você pretende capturar.Don't throw an exception that you expect to catch. E não use exceções para falhas esperadas.And don't use exceptions for expected failures. Gere uma exceção somente quando ocorrer um erro de runtime inesperado e manipule todo o restante com códigos de erro/resultado—diretamente, e feche a origem da falha.Throw an exception only when an unexpected runtime error occurs, and handle everything else with error/result codes—directly, and close to the source of the failure. Dessa forma, quando uma exceção for gerada, você saberá que a causa é um bug no código ou um estado de erro excepcional no sistema.That way, when an exception is thrown, you know that the cause is either a bug in your code, or an exceptional error state in the system.

Considere o cenário de acesso ao Registro do Windows.Consider the scenario of accessing the Windows Registry. Se o aplicativo falhar ao ler um valor no Registro, isso era esperado, e você deve tratar a situação normalmente.If your app fails to read a value from the Registry, then that's to be expected, and you should handle it gracefully. Não gere uma exceção; em vez disso, retorne um valor bool ou enum, indicando que, e talvez por que, o valor não foi lido.Don't throw an exception; rather return a bool or enum value indicating that, and perhaps why, the value wasn't read. Por outro lado, a falha ao gravar um valor no Registro, provavelmente indica que há um problema maior que você pode processar de maneira perceptível em seu aplicativo.Failing to write a value to the Registry, on the other hand, is likely to indicate that there's a bigger problem than you can handle sensibly in your application. Em casos assim, não é recomendado que o aplicativo continue. Portanto, uma exceção que resulta em um relatório de erros é a maneira mais rápida de impedir que o aplicativo cause danos.In a case like that, you don't want your application to continue, so an exception that results in an error report is the fastest way to keep your application from causing any harm.

Em outro exemplo, considere recuperar uma imagem em miniatura de uma chamada a StorageFile.GetThumbnailAsync e passá-la para BitmapSource.SetSourceAsync.For another example, consider retrieving a thumbnail image from a call to StorageFile.GetThumbnailAsync, and then passing that thumbnail to BitmapSource.SetSourceAsync. Se essa sequência de chamadas resultar em passar nullptr para SetSourceAsync (o arquivo de imagem não pode ser lido; talvez sua extensão faça parecer que ele contém dados de imagem, mesmo não contendo), você provocará a geração de uma exceção de ponteiro inválido.If that sequence of calls causes you to pass nullptr to SetSourceAsync (the image file can't be read; perhaps its file extension makes it look like it contains image data, but it actually doesn't), then you'll cause an invalid pointer exception to be thrown. Se você descobrir um caso semelhante no seu código, em vez de capturar e processar o caso como uma exceção, verifique se nullptr foi retornado de GetThumbnailAsync.If you discover a case like that in your code, rather than catching and handling the case as an exception, instead check for nullptr returned from GetThumbnailAsync.

A geração de exceções tende a ser mais lenta do que usar códigos de erro.Throwing exceptions tends to be slower than using error codes. Caso uma exceção seja gerada somente quando um erro fatal ocorrer, e se tudo correr bem, você nunca terá um problema de desempenho.If you only throw an exception when a fatal error occurs, then if all goes well you'll never pay the performance price.

Mas um impacto mais provável no desempenho envolve a sobrecarga do runtime ao garantir que os destruidores apropriados sejam chamados no evento improvável de geração da exceção.But a more likely performance hit involves the runtime overhead of ensuring that the appropriate destructors are called in the unlikely event that an exception is thrown. O custo dessa garantia é percebido não importando se uma exceção é de fato gerada ou não.The cost of this assurance comes whether an exception is actually thrown or not. Assim, você deve garantir que o compilador tenha uma boa noção sobre quais funções podem potencialmente gerar exceções.So, you should ensure that the compiler has a good idea of what functions can potentially throw exceptions. Se o compilador puder provar que não haverá qualquer exceção nas funções específicas (a especificação noexcept), é possível otimizar o código gerado.If the compiler can prove that there won't be any exceptions from certain functions (the noexcept specification), then it can optimize the code it generates.

Capturando exceçõesCatching exceptions

Uma condição de erro que surge na camada ABI do Windows Runtime é retornada na forma de um valor HRESULT.An error condition that arises at the Windows Runtime ABI layer is returned in the form of a HRESULT value. Mas você não precisa processar HRESULTs em seu código.But you don't need to handle HRESULTs in your code. O código de projeção do C++/WinRT gerado para uma API no lado de consumo detecta um código de erro de HRESULT na camada ABI e converte o código em uma exceção winrt::hresult_error, que você pode capturar e processar.The C++/WinRT projection code that's generated for an API on the consuming side detects an error HRESULT code at the ABI layer and converts the code into a winrt::hresult_error exception, which you can catch and handle. Se você realmente desejar processar HRESULTS, use o tipo winrt::hresult.If you do wish to handle HRESULTS, then use the winrt::hresult type.

Por exemplo, se o usuário excluir uma imagem da biblioteca de imagens enquanto o aplicativo estiver iterando nessa coleção, a projeção vai gerar uma exceção.For example, if the user happens to delete an image from the Pictures Library while your application is iterating over that collection, then the projection throws an exception. Nesse caso, é necessário capturar e processar essa exceção.And this is a case where you'll have to catch and handle that exception. Veja a seguir um exemplo de código mostrando esse caso.Here's a code example showing this case.

#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Windows::UI::Xaml::Media::Imaging;

IAsyncAction MakeThumbnailsAsync()
{
    auto imageFiles{ co_await KnownFolders::PicturesLibrary().GetFilesAsync() };

    for (StorageFile const& imageFile : imageFiles)
    {
        BitmapImage bitmapImage;
        try
        {
            auto thumbnail{ co_await imageFile.GetThumbnailAsync(FileProperties::ThumbnailMode::PicturesView) };
            if (thumbnail) bitmapImage.SetSource(thumbnail);
        }
        catch (winrt::hresult_error const& ex)
        {
            winrt::hresult hr = ex.code(); // HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
            winrt::hstring message = ex.message(); // The system cannot find the file specified.
        }
    }
}

Use esse mesmo padrão em uma corrotina ao chamar uma função co_await-ed.Use this same pattern in a coroutine when calling a co_await-ed function. Outro exemplo dessa conversão de HRESULT em exceção é que, quando uma API de componente retorna E_OUTOFMEMORY, isso gera um std::bad_alloc.Another example of this HRESULT-to-exception conversion is that when a component API returns E_OUTOFMEMORY, that causes a std::bad_alloc to be thrown.

Prefira winrt::hresult_error::code quando você estiver apenas espiando um código do HRESULT.Prefer winrt::hresult_error::code when you're just peeking at a HRESULT code. A função winrt::hresult_error::to_abi, por outro lado, é convertida em um objeto de erro COM e conduz o estado para o armazenamento local de thread COM.The winrt::hresult_error::to_abi function on the other hand converts to a COM error object, and pushes state into the COM thread-local storage.

Acionamento de exceçõesThrowing exceptions

Há casos em que você deve decidir que, em caso de falha na chamada para uma função específica, o aplicativo não será capaz de se recuperar (não é possível confiar no funcionamento previsível).There will be cases where you decide that, should your call to a given function fail, your application won't be able to recover (you'll no longer be able to rely on it to function predictably). O exemplo de código abaixo usa um valor de winrt::handle como um wrapper em torno do IDENTIFICADOR retornado de CreateEvent.The code example below uses a winrt::handle value as a wrapper around the HANDLE returned from CreateEvent. Em seguida, passa o identificador (criando um valor bool a partir dele) para o modelo de função winrt::check_bool.It then passes the handle (creating a bool value from it) to the winrt::check_bool function template. winrt::check_bool funciona com um bool ou com qualquer valor que possa ser convertido em false (uma condição de erro) ou true (uma condição de sucesso).winrt::check_bool works with a bool, or with any value that's convertible to false (an error condition), or true (a success condition).

winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));

Se o valor que você passar para winrt::check_bool for false, ocorrerá a sequência de ações a seguir.If the value that you pass to winrt::check_bool is false, then the following sequence of actions take place.

Como as APIs do Windows relatam erros em tempo de execução usando vários tipos de valor de retorno, existem algumas outras funções auxiliares úteis além de winrt::check_bool para verificar os valores e gerar exceções.Because Windows APIs report run-time errors using various return-value types, there are in addition to winrt::check_bool a handful of other useful helper functions for checking values and throwing exceptions.

  • winrt::check_hresult.winrt::check_hresult. Verifica se o código HRESULT representa um erro e, em caso afirmativo, chama winrt::throw_hresult.Checks whether the HRESULT code represents an error and, if so, calls winrt::throw_hresult.
  • winrt::check_nt.winrt::check_nt. Verifica se um código representa um erro e, em caso afirmativo, chama winrt::throw_hresult.Checks whether a code represents an error and, if so, calls winrt::throw_hresult.
  • winrt::check_pointer.winrt::check_pointer. Verifica se um ponteiro é nulo e, em caso afirmativo, chama winrt::throw_last_error.Checks whether a pointer is null and, if so, calls winrt::throw_last_error.
  • winrt::check_win32.winrt::check_win32. Verifica se um código representa um erro e, em caso afirmativo, chama winrt::throw_hresult.Checks whether a code represents an error and, if so, calls winrt::throw_hresult.

Você pode usar essas funções auxiliares para tipos comuns de código de retorno ou pode responder a qualquer condição de erro e chamar winrt::throw_last_error ou winrt::throw_hresult.You can use these helper functions for common return code types, or you can respond to any error condition and call either winrt::throw_last_error or winrt::throw_hresult.

Gerando exceções ao criar uma APIThrowing exceptions when authoring an API

Todos os limites da Interface Binária de Aplicativo do Windows Runtime (ou limites do ABI) precisam ser noexcept—o que significa que as exceções nunca devem fazer escape nesse local.All Windows Runtime Application Binary Interface boundaries (or ABI boundaries) must be noexcept—meaning that exceptions must never escape there. Ao criar uma API, você sempre deverá marcar o limite do ABI com a palavra-chave noexcept do C++.When you author an API, you should always mark the ABI boundary with the C++ noexcept keyword. noexcept tem um comportamento específico no C++.noexcept has specific behavior in C++. Se uma exceção do C++ atingir um limite noexcept, o processo falhará rapidamente com std::terminate.If a C++ exception hits a noexcept boundary, then the process will fail fast with std::terminate. Esse comportamento é geralmente desejável, porque uma exceção sem tratamento quase sempre implica um estado desconhecido no processo.That behavior is generally desirable, because an unhandled exception almost always implies unknown state in the process.

Como as exceções não devem cruzar o limite do ABI, uma condição de erro acionada em uma implementação é retornada na camada do ABI na forma de um código de erro HRESULT.Since exceptions mustn't cross the ABI boundary, an error condition that arises in an implementation is returned across the ABI layer in the form of an HRESULT error code. Ao criar uma API usando C++/WinRT, o código é gerado para você converter qualquer exceção gerada na implementação em um HRESULT.When you're authoring an API using C++/WinRT, code is generated for you to convert any exception that you do throw in your implementation into an HRESULT. A função winrt::to_hresult é usada no código gerado em um padrão semelhante a este.The winrt::to_hresult function is used in that generated code in a pattern like this.

HRESULT DoWork() noexcept
{
    try
    {
        // Shim through to your C++/WinRT implementation.
        return S_OK;
    }
    catch (...)
    {
        return winrt::to_hresult(); // Convert any exception to an HRESULT.
    }
}

winrt::to_hresult processa as exceções derivadas de std::exception, winrt::hresult_error e seus tipos derivados.winrt::to_hresult handles exceptions derived from std::exception, and winrt::hresult_error and its derived types. Na implementação, você deve preferir winrt::hresult_error, ou um tipo derivado, para que os consumidores da API recebam informações de erro detalhadas.In your implementation, you should prefer winrt::hresult_error, or a derived type, so that consumers of your API receive rich error information. std::exception (mapeada para E_FAIL) será aceita caso surjam exceções devido ao uso da Biblioteca de Modelos Padrão.std::exception (which maps to E_FAIL) is supported in case exceptions arise from your use of the Standard Template Library.

Capacidade de depuração com noexceptDebuggability with noexcept

Como mencionamos acima, uma exceção do C++ que atinge um limite noexcept falha rapidamente com std::terminate.As we mentioned above, a C++ exception hitting a noexcept boundary fails fast with std::terminate. Isso não é ideal para depuração, porque std::terminate muitas vezes perde grande parte ou todo o contexto de erro ou de exceção gerado, especialmente quando as corrotinas estão envolvidas.That's not ideal for debugging, because std::terminate often loses much or all of the error or the exception context thrown, especially when coroutines are involved.

Portanto, esta seção trata do caso em que o método do ABI (que você anotou corretamente com noexcept) usa co_await para chamar o código de projeção assíncrona do C++/WinRT.So, this section deals with the case where your ABI method (which you've properly annotated with noexcept) uses co_await to call asynchronous C++/WinRT projection code. Recomendamos que você encapsule as chamadas ao código de projeção do C++/WinRT em um winrt::fire_and_forget.We recommend that you wrap the calls to the C++/WinRT projection code within a winrt::fire_and_forget. Essa ação fornece um local adequado para que uma exceção sem tratamento seja registrada corretamente como uma exceção recolhida, o que aumenta significativamente a capacidade de depuração.Doing so provides a proper place for an unhandled exception to be properly recorded as a stowed exception, which greatly increases debuggability.

HRESULT MyWinRTObject::MyABI_Method() noexcept
{
    winrt::com_ptr<Foo> foo{ get_a_foo() };

    [/*no captures*/](winrt::com_ptr<Foo> foo) -> winrt::fire_and_forget
    {
        co_await winrt::resume_background();

        foo->ABICall();

        AnotherMethodWithLotsOfProjectionCalls();
    }(foo);

    return S_OK;
}

winrt::fire_and_forget tem um auxiliar de método unhandled_exception interno, que chama winrt::terminate, que, por sua vez, chama RoFailFastWithErrorContext.winrt::fire_and_forget has a built-in unhandled_exception method helper, which calls winrt::terminate, which in turn calls RoFailFastWithErrorContext. Isso garante que qualquer contexto (exceção recolhida, código de erro, mensagem de erro, backtrace de pilha e assim por diante) seja preservado para depuração dinâmica ou para um despejo post-mortem.This guarantees that any context (stowed exception, error code, error message, stack backtrace, and so on) is preserved either for live debugging or for a post-mortem dump. Para sua conveniência, você pode fatorar a parte de acionamento e esquecimento em uma função separada que retorna um winrt::fire_and_forget e, em seguida, chama isso.For convenience, you can factor the fire-and-forget portion into a separate function that returns a winrt::fire_and_forget, and then call that.

Código síncronoSynchronous code

Em alguns casos, o método do ABI (que, mais uma vez, você anotou corretamente com noexcept) chama apenas o código síncrono.In some cases, your ABI method (which, again, you've properly annotated with noexcept) calls only synchronous code. Em outras palavras, ele nunca usa co_await, seja para chamar um método assíncrono do Windows Runtime, seja para alternar entre os threads de primeiro e segundo plano.In other words, it never uses co_await, either to call an asynchronous Windows Runtime method, or to switch between foreground and background threads. Nesse caso, a técnica fire_and_forget ainda funcionará, mas não será eficiente.In that case, the fire_and_forget technique will still work, but it's not efficient. Em vez disso, você poderá fazer algo assim.Instead, you can do something like this.

HRESULT abi() noexcept try
{
    // ABI code goes here.
} catch (...) { winrt::terminate(); }

Fail fastFail fast

O código na seção anterior ainda falha rapidamente.The code in the previous section still fails fast. Conforme escrito, esse código não trata nenhuma exceção.As written, that code doesn't handle any exceptions. Qualquer exceção sem tratamento resulta na terminação do programa.Any unhandled exception results in program termination.

Mas esse formato é superior, pois garante a capacidade de depuração.But that form is superior, because it ensures debuggability. Em casos raros, talvez você deseje aplicar try/catch e tratar determinadas exceções.In rare cases, you might want to try/catch, and handle certain exceptions. Mas isso deve ser raro porque, como este tópico explica, não recomendamos o uso de exceções como um mecanismo de controle de fluxo para condições esperadas.But that should be rare because, as this topic explains, we discourage using exceptions as a flow-control mechanism for conditions that you expect.

Lembre-se de que é uma boa ideia deixar uma exceção sem tratamento fazer escape de um contexto noexcept naked.Remember that it's a bad idea to let an unhandled exception escape a naked noexcept context. Sob essa condição, o runtime C++ usará std::terminate para terminar o processo, perdendo todas as informações de exceção recolhidas cuidadosamente registradas pelo C++/WinRT.Under that condition, the C++ runtime will std::terminate the process, thereby losing any stowed exception information that C++/WinRT carefully recorded.

AsserçõesAssertions

Para suposições internas em seu aplicativo, existem asserções.For internal assumptions in your application, there are assertions. Prefira static_assert para a validação de tempo de compilação sempre que possível.Prefer static_assert for compile-time validation, wherever possible. Para condições de tempo de execução, use WINRT_ASSERT com uma expressão booliana.For run-time conditions, use WINRT_ASSERT with a Boolean expression. WINRT_ASSERT é uma definição de macro e se expande para _ASSERTE.WINRT_ASSERT is a macro definition, and it expands to _ASSERTE.

WINRT_ASSERT(pos < size());

WINRT_ASSERT é compilado nas compilações lançadas; em uma compilação de depuração, ele interrompe o aplicativo no depurador na linha de código onde está a asserção.WINRT_ASSERT is compiled away in release builds; in a debug build, it stops the application in the debugger on the line of code where the assertion is.

Você não deve usar exceções em seu destruidores.You shouldn't use exceptions in your destructors. Portanto, pelo menos nas compilações de depuração, você pode declarar o resultado da chamada de uma função a partir de um destrutor com WINRT_VERIFY (com uma expressão booliana) e WINRT_VERIFY_ (com um resultado esperado e uma expressão booliana).So, at least in debug builds, you can assert the result of calling a function from a destructor with WINRT_VERIFY (with a Boolean expression) and WINRT_VERIFY_ (with an expected result and a Boolean expression).

WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));

APIs importantesImportant APIs