MSDN Documentation

Microsoft Developer Network

Windows Error Handling

Effective error handling is crucial for developing robust and reliable Windows applications. This section explores the mechanisms and best practices for handling errors in Windows programming, covering both traditional Win32 error codes and modern exception handling techniques.

Win32 Error Codes

The Win32 API uses a system of error codes to indicate the success or failure of function calls. When a function fails, it typically returns a value indicating failure (e.g., NULL, FALSE, or -1), and the specific error code can be retrieved using GetLastError().

Error codes are 32-bit unsigned integers. The most significant bit indicates whether the error is a success code (0) or an error code (1). The remaining bits define the specific error condition. Common error codes include:

  • ERROR_SUCCESS (0)`: Operation was successful.
  • ERROR_INVALID_FUNCTION (1)`: Incorrect function.
  • ERROR_FILE_NOT_FOUND (2)`: The system cannot find the file specified.
  • ERROR_PATH_NOT_FOUND (3)`: The system cannot find the path specified.
  • ERROR_ACCESS_DENIED (5)`: Access is denied.
  • ERROR_INVALID_HANDLE (6)`: The handle is invalid.

You can find a comprehensive list of Win32 error codes in the Windows SDK documentation.

Using GetLastError()

The GetLastError() function retrieves the last error code set by a thread. It's crucial to call GetLastError() immediately after a function that might fail, as subsequent API calls can overwrite the error code.

Example: Retrieving and Using GetLastError()


#include <windows.h>
#include <iostream>

int main() {
    // Attempt an operation that might fail, e.g., opening a non-existent file
    HANDLE hFile = CreateFile(
        L"non_existent_file.txt",
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        DWORD errorStatus = GetLastError();
        std::wcerr << L"Failed to open file. Error code: " << errorStatus << std::endl;

        // You would typically use FormatMessage here to get a readable string
    } else {
        // Operation succeeded
        CloseHandle(hFile);
        std::wcout << L"File opened successfully." << std::endl;
    }

    return 0;
}
                    

Remember that GetLastError() is thread-local. Each thread maintains its own error code.

Formatting Error Messages

While GetLastError() provides a numeric code, it's often necessary to present a user-friendly message. The FormatMessage() function is used for this purpose. It can retrieve system error messages, formatted according to the specified arguments.

Example: Formatting an Error Message


#include <windows.h>
#include <iostream>
#include <vector>

void DisplayLastError(const std::wstring& functionName) {
    DWORD errorStatus = GetLastError();
    if (errorStatus == 0) {
        return; // No error
    }

    LPWSTR buffer = nullptr;
    DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;

    DWORD dwResult = FormatMessageW(
        dwFlags,
        NULL,                   // Source - NULL for system messages
        errorStatus,            // Message ID
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Language ID
        (LPWSTR)&buffer,        // Buffer to receive message
        0,                      // Minimum buffer size
        NULL                    // Arguments list
    );

    if (dwResult > 0) {
        std::wcerr << functionName << L" failed with error " << errorStatus << L": " << buffer << std::endl;
    } else {
        std::wcerr << functionName << L" failed with unknown error code: " << errorStatus << std::endl;
    }

    if (buffer) {
        LocalFree(buffer); // Free the buffer allocated by FormatMessage
    }
}

int main() {
    HANDLE hFile = CreateFile(
        L"another_non_existent_file.txt",
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        DisplayLastError(L"CreateFile");
    } else {
        CloseHandle(hFile);
    }
    return 0;
}
                    

Structured Exception Handling (SEH)

Structured Exception Handling (SEH) is a low-level mechanism in Windows for handling hardware exceptions (like division by zero) and software exceptions generated by the operating system or applications. It provides a way to catch and respond to unexpected events that would otherwise terminate a program.

SEH uses the keywords __try, __except, and __finally.

Example: SEH with __try and __except


#include <windows.h>
#include <iostream>

int main() {
    int numerator = 1;
    int denominator = 0;
    int result = 0;

    __try {
        result = numerator / denominator; // This will cause a division-by-zero exception
        std::cout << "Result: " << result << std::endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        std::cerr << "An exception occurred: Division by zero!" << std::endl;
        // You can inspect GetExceptionCode() here for more details
        DWORD exceptionCode = GetExceptionCode();
        if (exceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) {
            std::cerr << "Specific error: Integer division by zero." << std::endl;
        }
    }
    __finally {
        std::cout << "This finally block always executes." << std::endl;
    }

    return 0;
}
                    

__try encloses the code that might generate an exception. __except specifies how to handle the exception. __finally contains code that should always be executed, regardless of whether an exception occurred or was handled.

C++ Exceptions

For C++ development, the standard C++ exception handling mechanism using try, catch, and throw is generally preferred and is built on top of SEH for many platforms.

This mechanism is more type-safe and object-oriented than SEH.

Example: C++ Exceptions


#include <iostream>
#include <stdexcept> // For standard exception classes

void process_data(int value) {
    if (value < 0) {
        throw std::invalid_argument("Value cannot be negative.");
    }
    std::cout << "Processing value: " << value << std::endl;
}

int main() {
    try {
        process_data(10);
        process_data(-5); // This will throw an exception
        process_data(20); // This line will not be reached
    } catch (const std::invalid_argument& e) {
        std::cerr << "Caught an exception: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught a general exception: " << e.what() << std::endl;
    }
    // You can also catch SEH exceptions in C++ but it requires careful setup
    // __try { ... } __except (...) {}

    return 0;
}
                    

Best Practices

  • Check Return Values: Always check the return values of API functions, especially those that indicate success or failure.
  • Call GetLastError() Promptly: Retrieve the error code immediately after a function call that may have failed.
  • Use FormatMessage(): Provide user-friendly error messages by formatting error codes with FormatMessage().
  • Handle Exceptions Appropriately: Use C++ exceptions for logical errors in C++ code and SEH for low-level hardware or system exceptions.
  • Don't Ignore Errors: Never ignore error conditions. Either handle them, log them, or propagate them appropriately.
  • Resource Management: Ensure that resources (like handles, memory, files) are properly released in both normal execution paths and error handling paths (e.g., using RAII in C++).
  • Define Custom Error Codes: For your own application-specific errors, define a consistent set of error codes or exception types.