Microsoft Developer Network
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.
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.
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.
#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.
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.
#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) 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.
#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.
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.
#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;
}
FormatMessage().