Как правильно использовать FormatMessage() на С++?
Без
Как я могу использовать FormatMessage()
, чтобы получить текст ошибки для HRESULT
?
HRESULT hresult = application.CreateInstance("Excel.Application");
if (FAILED(hresult))
{
// what should i put here to obtain a human-readable
// description of the error?
exit (hresult);
}
Ответы
Ответ 1
Вот правильный способ получить сообщение об ошибке из системы для HRESULT
(в данном случае с именем hresult, или вы можете заменить его на GetLastError()
):
LPTSTR errorText = NULL;
FormatMessage(
// use system message tables to retrieve error text
FORMAT_MESSAGE_FROM_SYSTEM
// allocate buffer on local heap for error text
|FORMAT_MESSAGE_ALLOCATE_BUFFER
// Important! will fail otherwise, since we're not
// (and CANNOT) pass insertion parameters
|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
hresult,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorText, // output
0, // minimum size for output buffer
NULL); // arguments - see note
if ( NULL != errorText )
{
// ... do something with the string 'errorText' - log it, display it to the user, etc.
// release memory allocated by FormatMessage()
LocalFree(errorText);
errorText = NULL;
}
Основное различие между этим и ответом Дэвида Ханака заключается в использовании флага FORMAT_MESSAGE_IGNORE_INSERTS
. В MSDN немного неясно, как следует использовать вставки, но Раймонд Чен отмечает, что вы никогда не должны использовать их при получении системного сообщения, поскольку у вас нет способа узнать, какие вставки ожидает система.
Кстати, если вы используете Visual C++, вы можете немного упростить свою жизнь с помощью класса _com_error
:
{
_com_error error(hresult);
LPCTSTR errorText = error.ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
}
Насколько мне известно, не является частью MFC или ATL напрямую.
Ответ 2
Имейте в виду, что вы не можете сделать следующее:
{
LPCTSTR errorText = _com_error(hresult).ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
}
Как класс создается и уничтожается в стеке, оставляя errorText, указывая на недопустимое местоположение. В большинстве случаев это место по-прежнему будет содержать строку ошибок, но эта вероятность быстро отпадает при написании приложений с резьбой.
Итак, всегда сделайте это так, как описано Shog9 выше:
{
_com_error error(hresult);
LPCTSTR errorText = error.ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
}
Ответ 3
Попробуйте следующее:
void PrintLastError (const char *msg /* = "Error occurred" */) {
DWORD errCode = GetLastError();
char *err;
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPTSTR) &err,
0,
NULL))
return;
static char buffer[1024];
_snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err);
OutputDebugString(buffer); // or otherwise log it
LocalFree(err);
}
Ответ 4
Вот версия функции David, которая обрабатывает Unicode
void HandleLastError(const TCHAR *msg /* = "Error occured" */) {
DWORD errCode = GetLastError();
TCHAR *err;
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPTSTR) &err,
0,
NULL))
return;
//TRACE("ERROR: %s: %s", msg, err);
TCHAR buffer[1024];
_sntprintf_s(buffer, sizeof(buffer), _T("ERROR: %s: %s\n"), msg, err);
OutputDebugString(buffer);
LocalFree(err);
}
Ответ 5
Это больше подходит для большинства ответов, но вместо использования LocalFree(errorText)
используйте функцию HeapFree
:
::HeapFree(::GetProcessHeap(), NULL, errorText);
С сайта MSDN:
Windows 10:
LocalFree не находится в современном SDK, поэтому его нельзя использовать для освобождения буфера результатов. Вместо этого используйте HeapFree (GetProcessHeap(), allocMessage). В этом случае это то же самое, что вызов LocalFree в памяти.
Обновление
Я обнаружил, что LocalFree
находится в версии 10.0.10240.0 SDK (строка 1108 в WinBase.h). Однако предупреждение по-прежнему существует в ссылке выше.
#pragma region Desktop Family or OneCore Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
WINBASEAPI
_Success_(return==0)
_Ret_maybenull_
HLOCAL
WINAPI
LocalFree(
_Frees_ptr_opt_ HLOCAL hMem
);
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */
#pragma endregion
Обновление 2
Я также предложил бы использовать флаг FORMAT_MESSAGE_MAX_WIDTH_MASK
, чтобы убрать разрывы строк в системных сообщениях.
С сайта MSDN:
FORMAT_MESSAGE_MAX_WIDTH_MASK
Функция игнорирует регулярные разрывы строк в тексте определения сообщения. Функция сохраняет стробированные разрывы строк в тексте определения сообщения в выходной буфер. Функция не генерирует никаких новых разрывов строк.
Обновление 3
Кажется, что есть 2 определенных системных кода ошибки, которые не возвращают полное сообщение с использованием рекомендуемого подхода:
Почему FormatMessage создает только частичные сообщения для системных ошибок ERROR_SYSTEM_PROCESS_TERMINATED и ERROR_UNHANDLED_EXCEPTION?
Ответ 6
Как указано в других ответах:
-
FormatMessage
принимает результат DWORD
не HRESULT
(обычно GetLastError()
). -
LocalFree
необходим для освобождения памяти, которая была выделена FormatMessage
Я взял вышеупомянутые пункты и добавил еще несколько для моего ответа:
- Оберните
FormatMessage
в классе, чтобы автоматически распределять и освобождать память по мере необходимости - Используйте перегрузку оператора (например,
operator LPTSTR() const { return...; }
чтобы ваш класс мог использоваться как строка
class CFormatMessage
{
public:
CFormatMessage(DWORD dwError) : m_ErrorText(NULL) { Assign(dwError); }
~CFormatMessage() { Clear(); }
void Clear() { if (m_ErrorText != NULL) { LocalFree(m_ErrorText); m_ErrorText = NULL; } }
void Assign(DWORD dwError) {
Clear();
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&m_ErrorText,
0,
NULL);
}
LPTSTR ErrorText() const { return m_ErrorText; }
operator LPTSTR() const { return ErrorText(); }
protected:
LPTSTR m_ErrorText;
};
Найдите более полную версию приведенного выше кода здесь: https://github.com/stephenquan/FormatMessage
С вышеупомянутым классом использование просто:
std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
Ответ 7
Код ниже - это эквивалент С++, который я написал в отличие от Microsoft ErrorExit(), но слегка измененный, чтобы избежать всех макросов и использовать unicode. Идея здесь заключается в том, чтобы избежать ненужных бросков и маллоков. Я не мог избежать всех бросков C, но это лучшее, что я мог собрать. Относительно FormatMessageW(), для которого требуется указать указатель на функцию формата и идентификатор ошибки из GetLastError(). Указатель после static_cast может использоваться как обычный указатель wchar_t.
#include <string>
#include <windows.h>
void __declspec(noreturn) error_exit(const std::wstring FunctionName)
{
// Retrieve the system error message for the last-error code
const DWORD ERROR_ID = GetLastError();
void* MsgBuffer = nullptr;
LCID lcid;
GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid));
//get error message and attach it to Msgbuffer
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL);
//concatonate string to DisplayBuffer
const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer);
// Display the error message and exit the process
MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid));
ExitProcess(ERROR_ID);
}
Ответ 8
Начиная с С++ 11, вы можете использовать стандартную библиотеку вместо FormatMessage
:
#include <system_error>
std::string message = std::system_category().message(hr)