c++ - How to use Windows' FormatMessage without unchecked cast? - Stack Overflow

admin2025-04-16  5

  • Here is a pretty up-to-date code snippet demonstrating how to load the error message for a Windows API error code.
  • Here is a much older code snippet using the same syntax to do the same thing.
    (Note: This snippet makes a few assumptions that do not necessarily hold true, e.g. FormatMessage expanding to FormatMessageA or LPSTR and LPCSTR sharing the same character representation. It is otherwise correct, though.)
  • Here is the definition of FormatMessage in the official Microsoft documentation.
    Note particularly:

[out] lpBuffer

A pointer to a buffer that receives the null-terminated string that specifies the formatted message. If dwFlags includes FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer using the LocalAlloc function, and places the pointer to the buffer at the address specified in lpBuffer.

(emphasis mine)

The code snippet:

void ErrorExit() 
{ 
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 

    if (FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL) == 0) {
        MessageBox(NULL, TEXT("FormatMessage failed"), TEXT("Error"), MB_OK);
        ExitProcess(dw);
    }

    MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);

    LocalFree(lpMsgBuf);
    ExitProcess(dw); 
}

On my setup - x64, Visual Studio 2022 (v143), Windows 10 SDK - these types evaluate as follows:

  • LPVOID: void *
  • FormatMessage:
unsigned long FormatMessageW(
  unsigned long dwFlags,
  const void * lpSource,
  unsigned long dwMessageId,
  unsigned long dwLanguageId,
  wchar_t * lpBuffer,
  unsigned long nSize,
  char ** Arguments
)

As you can see, what we pass is indeed a "pointer to a buffer": void **. But what we cast it to is just a buffer: wchar_t *.

And we have to, as FormatMessage/-A/-W does not take a ** (or *&). Going by its signature, it should allocate the buffer right at &lpMsgBuf, write the second wchar at (&lpMsgBuf) + sizeof(wchar_t) and basically trash our stack.

(That is not what happens, of course: Both linked code snippets work as intended, if not as expected.)

It does seem to me that I must trick the compiler into letting me pass the wrong type into the function.

How would one write this code without using an unchecked cast?

(E.g. I'd have expected the code - after macro expansion - to look like this:

wchar_t * lpMsgBuf;
[...]
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    &lpMsgBuf,
    0, NULL) == 0) {
[...]

but that of course does not compile, as &lpMsgBuf is wchar_t **, not wchar_t *.)

X/Y answer: There would not happen to be a modern, UTF-8 version of this cursed API around somewhere?
This stuff is straight out of Microsoft's "let's code as alien as possible" era. Unfortunately GetLastError and thence FormatMessage seem to be the only way to do error handling for Windows API calls like GetProcessAffinityMask, for which currently no standard C++ equivalent exists. I'd much rather not touch this with the proverbial ten-feet pole.

  • Here is a pretty up-to-date code snippet demonstrating how to load the error message for a Windows API error code.
  • Here is a much older code snippet using the same syntax to do the same thing.
    (Note: This snippet makes a few assumptions that do not necessarily hold true, e.g. FormatMessage expanding to FormatMessageA or LPSTR and LPCSTR sharing the same character representation. It is otherwise correct, though.)
  • Here is the definition of FormatMessage in the official Microsoft documentation.
    Note particularly:

[out] lpBuffer

A pointer to a buffer that receives the null-terminated string that specifies the formatted message. If dwFlags includes FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer using the LocalAlloc function, and places the pointer to the buffer at the address specified in lpBuffer.

(emphasis mine)

The code snippet:

void ErrorExit() 
{ 
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 

    if (FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL) == 0) {
        MessageBox(NULL, TEXT("FormatMessage failed"), TEXT("Error"), MB_OK);
        ExitProcess(dw);
    }

    MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);

    LocalFree(lpMsgBuf);
    ExitProcess(dw); 
}

On my setup - x64, Visual Studio 2022 (v143), Windows 10 SDK - these types evaluate as follows:

  • LPVOID: void *
  • FormatMessage:
unsigned long FormatMessageW(
  unsigned long dwFlags,
  const void * lpSource,
  unsigned long dwMessageId,
  unsigned long dwLanguageId,
  wchar_t * lpBuffer,
  unsigned long nSize,
  char ** Arguments
)

As you can see, what we pass is indeed a "pointer to a buffer": void **. But what we cast it to is just a buffer: wchar_t *.

And we have to, as FormatMessage/-A/-W does not take a ** (or *&). Going by its signature, it should allocate the buffer right at &lpMsgBuf, write the second wchar at (&lpMsgBuf) + sizeof(wchar_t) and basically trash our stack.

(That is not what happens, of course: Both linked code snippets work as intended, if not as expected.)

It does seem to me that I must trick the compiler into letting me pass the wrong type into the function.

How would one write this code without using an unchecked cast?

(E.g. I'd have expected the code - after macro expansion - to look like this:

wchar_t * lpMsgBuf;
[...]
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    &lpMsgBuf,
    0, NULL) == 0) {
[...]

but that of course does not compile, as &lpMsgBuf is wchar_t **, not wchar_t *.)

X/Y answer: There would not happen to be a modern, UTF-8 version of this cursed API around somewhere?
This stuff is straight out of Microsoft's "let's code as alien as possible" era. Unfortunately GetLastError and thence FormatMessage seem to be the only way to do error handling for Windows API calls like GetProcessAffinityMask, for which currently no standard C++ equivalent exists. I'd much rather not touch this with the proverbial ten-feet pole.

Share Improve this question asked Feb 3 at 23:59 ZsarZsar 4633 silver badges16 bronze badges 3
  • When do LPSTR and LPCSTR possibly differ except for const-ness? If that is violated I would think you're in somewhere far outside windows-header-land. – SoronelHaetir Commented Feb 4 at 0:23
  • @SoronelHaetir : Please do not ask me. They are different types, defined completely separately, as if it was planned to have them diverge arbitrarily. I would myself be surprised if that happened, but it is possible by design and as such, someone (designing it) must have anticipated it. Had they defined LPCSTR as const LPSTR then it would be obvious that they truly are not meant to diverge and it would have been less code as well. – Zsar Commented Feb 4 at 14:03
  • LPSTR is char* (non-const pointer to non-const char). LPCSTR is char const * (non-const pointer to const char), whereas const LPSTR is char* const (const pointer to non-const char). They are not the same thing. – Remy Lebeau Commented Feb 4 at 15:44
Add a comment  | 

3 Answers 3

Reset to default 6

How would one write this code without using an unchecked cast?

Unfortunately, you cannot, since the function takes an LPTSTR. So, when using the FORMAT_MESSAGE_ALLOCATE_BUFFER flag, the type-cast is required. This is what allows you to pass in an LPTSTR* where an LPTSTR is expected. There is simply no getting around that limitation of this API.

That being said, when compiling for C++, you should be using reinterpret_cast instead of a C-style cast, eg:

FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | ...,
    ...
    reinterpret_cast<LPTSTR>(&lpMsgBuf),
    ...)

I'd have expected the code - after macro expansion - to look like this:

...

but that of course does not compile, as &lpMsgBuf is wchar_t **, not wchar_t *.

Actually, it would be a void** not a wchar_t**. But, just as you can't pass a wchar_t** to a wchar_t*, you can't pass a void** to it, either. Had the function taken an LPVOID instead, this would not be a problem at all.

That being said, you should declare lpMsgBuf as LPTSTR instead of LPVOID, then you don't need to type-cast it when passing it to MessageBox() at least, eg:

LPTSTR lpMsgBuf;

FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | ...,
    ...
    reinterpret_cast<LPTSTR>(&lpMsgBuf),
    ...)

MessageBox(NULL, lpMsgBuf, TEXT("Error"), MB_OK);

There would not happen to be a modern, UTF-8 version of this cursed API around somewhere?

Technically, yes. Starting with Windows 10 v1903, if you enable the UTF-8 codepage for your app then FormatMessageA() (and most other Win32 A APIs) will process and return UTF-8 strings.

However, do be aware that, as of a year ago, this option was broken in FormatMessageA() for UTF-8 encoded format strings. I don't know if that issue has been fixed yet, so use with caution.

If you need to support older Windows versions (or just don't want to trust an experimental feature) then you should use FormatMessageW() to get the text as UTF-16 first, and then convert it to UTF-8 using WideCharToMultiByte() or equivalent.

Unfortunately GetLastError and thence FormatMessage seem to be the only way to do error handling for Windows API calls like GetProcessAffinityMask, for which currently no standard C++ equivalent exists.

Actually, there is std::error_code and std::error_condition, both of which have a message() method. It is up to the standard library implementation to decide whether it uses FormatMessage() or not, but you could try it and see what happens, eg:

void ErrorExit() 
{ 
    DWORD dw = GetLastError();
    int ierr = static_cast<int>(dw);
    std::string msg = std::error_code(ierr, std::system_category()).message();
    MessageBoxA(NULL, msg.c_str(), "Error", MB_OK);
    ExitProcess(dw);
}

When calling without FORMAT_MESSAGE_ALLOCATE_BUFFER you don't need a cast.

With it, I usually prefer casting twice, one for safety and one to satisfy the API:

LPTSTR buffer;
FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER | ..., ...
  (LPTSTR)const_cast<LPTSTR*>(&buffer), 0,
  ...);

Having to type that all the time is annoying, just make a helper function:

inline UINT FormatMessageAlloc(UINT Flags, UINT MsgId, LPTSTR* ppszBuffer, LPCVOID Source = NULL, UINT LangId = 0, UINT MinSize = 0)
{
  Flags |= FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
  return FormatMessage(Flags, Source, MsgId, LangId, (LPTSTR)const_cast<LPTSTR*>(ppszBuffer), MinSize, NULL);
}

X/Y answer: There would not happen to be a modern, UTF-8 version of this cursed API around somewhere?

Not modern, but there is an UTF-8 "version".

Create an application.manifest file with the following content:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <application>
    <windowsSettings>
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
    </windowsSettings>
  </application>
</assembly>

Then link this into your .exe, for example if you're using CMake:

if(WIN32)
    target_sources(your_executable_target PRIVATE application.manifest)
endif()

After that FormatMessageA will give you UTF-8. See https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page for more information.

This won't solve your unchecked cast issue though.

转载请注明原文地址:http://www.anycun.com/QandA/1744744651a87004.html