Psychic debugging: Why does FormatMessage say the resource couldn't be found?

Raymond Chen

Solving this next problem should be a snap with your nascent psychic powers:

I’m trying use FormatMessage to load a resource string with one insertion in it, and this doesn’t work for some reason. The string is “Blah blah blah %1. Blah blah blah.” The call to FormatMessage fails, and GetLastError() returns ERROR_RESOURCE_TYPE_NOT_FOUND. What am I doing wrong?

LPTSTR pszInsertion = TEXT("Sample");
LPTSTR pszResult;
FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_HMODULE |
        FORMAT_MESSAGE_ARGUMENT_ARRAY,
        //I also tried an instance handle and NULL.
        GetModuleHandle(NULL),
        IDS_MY_CUSTOM_MESSAGE,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
        (LPTSTR) &pszResult,
        0,
        (va_list*) &pszInsertion);

Hint: Take a closer look at the parameter IDS_MY_CUSTOM_MESSAGE.

Hint 2: What does “IDS_” tell you?

Resource identifiers that begin with “IDS_” are typically string resource identifiers, not message resource identifiers. There is no strong consensus on the naming convention for message resource identifiers, although I’ve seen “MSG_“. Part of the reason why there is no strong consensus on the naming convention for message resource identifiers is that almost nobody uses message resources! I don’t understand why they were added to Win32, since there was already a way of embedding strings in resources, namely, string resources.

That’s why you’re getting ERROR_RESOURCE_TYPE_NOT_FOUND. There is no message resource in your module. If you’re not going to use a message resource, you’ll have to use the FORMAT_MESSAGE_FROM_STRING flag and pass the format string explicitly.

DWORD_PTR rgdwInsertions[1] = { (DWORD_PTR)TEXT("Sample") };
TCHAR szFormat[256];
LoadString(hInstance, IDS_MY_CUSTOM_MESSAGE, szFormat, 256);
LPTSTR pszResult;
FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_STRING |
        FORMAT_MESSAGE_ARGUMENT_ARRAY,
        szFormat,
        0,
        0,
        (LPTSTR) &pszResult,
        0,
        (va_list*) &rgdwInsertions);

I also made a slight change to the final parameter. When you use FORMAT_MESSAGE_ARGUMENT_ARRAY, the last parameter must be an array of DWORD_PTRs. (The parameter must be cast to va_list* to keep the compiler happy.) It so happens that the original code got away with this mistake since sizeof(DWORD_PTR) == sizeof(LPTSTR) and they both have the same alignment requirements. On the other hand, if the insertion were a DWORD, passing (va_list*)&dwValue is definitely wrong and can crash if you’re sufficiently unlucky. (Determining the conditions under which your luck runs out is left as an exercise.)

0 comments

Discussion is closed.

Feedback usabilla icon