May 29th, 2007

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

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.)

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.