Horrifically nasty gotcha: FindResource and FindResourceEx


The Find­Resource­Ex function is an extension of the Find­Resource function in that it allows you to specify a particular language fork in which to search for the resource. Calilng the Find­Resource function is equivalent to calling Find­Resource­Ex and passing zero as the wLanguage.

Except for the horrible nasty gotcha: The second and third parameters to Find­Resource­Ex are in the opposite order compared to the second and third parameters to Find­Resource!

In other words, if you are adding custom language support to a program, you cannot just stick a wLanguage parameter on the end when you switch from Find­Resource to Find­Resource­Ex. You also have to flip the second and third parameters.

Original code Find­Resource(hModule, MAKEINTRESOURCE(IDB_MYBITMAP), RT_BITMAP)
You change it to Find­Resource­Ex(hModule, MAKEINTRESOURCE(IDB_MYBITMAP), RT_BITMAP, 0)
You should have changed it to Find­Resource­Ex(hModule, RT_BITMAP, MAKEINTRESOURCE(IDB_MYBITMAP), 0)

The nasty part of this is that since the second and third parameters are the same type, the compiler won't notice that you got them backward. The only way you find out is that your resource code suddenly stopped working.

Comments (15)
  1. 12BitSlab says:

    Raymond, is there any sort of team at Microsoft that has oversight on official API structure?

    [There is now (in the form of a v-team), but there wasn't back in 1989. -Raymond]
  2. Joker_vD says:

    "The nasty part of this is that since the second and third parameters are the same type".

    Ah, the ancient variant of the modern "stringly typed" approach. Let's call it "types are integral for quality assurance".

  3. Yuri Khan says:

    How could such an horrible API extension happen?

    [Things were a bit wilder back in 1989. -Raymond]
  4. Kevin says:

    Perhaps FindResourceEx should be deprecated in favor of a hypothetical FindResourceExEx which doesn't switch the parameters?

  5. In retrospect, lpType looks like a good candidate for an enum.

    [Though that wouldn't leave a place for user-defined types. -Raymond]
  6. Joshua says:

    [Though that wouldn't leave a place for user-defined types. -Raymond]

    Wait, you can add user-defined types? What happens when the next version of Windows supports that type?

    [Windows-defined types are integers (RT_*). User-defined types are strings (TEXT("name")). -Raymond]
  7. Euro Micelli says:

    @Joshua, I imagine that most people calling FindResourceEx are also authors of the resources themselves. I suspect that interference shouldn't be much of a real problem in practice.

  8. Deanna says:

    Joshua, the types are just recommendations, and can actually contain any data you want, if you read them in a generic way.

    Of course, if you want something else to read them, then stick to the standard type values.

  9. Joshua says:

    Ok now I feel really silly. FindResourceEx/LoadResource return the raw bytes even if it is a standard type. Calling LoadResource on an icon resource returns the bytes of the .ico file. While I'm pretty sure a cross-id would cause major headaches, it won't be a headache here (although it might have been on Win9x but that branch is not getting anymore feature releases).

  10. Is there any specific reason why there never was a LoadStringEx method that allowed passing a language ID as well?

    It's not too hard to assemble, but it shouldn't be required.

  11. foo says:

    @Greg Rottensteiner. Those resource API's take an instance handle to the DLL as the first parameter. Isn't that usually the MUI DLL's handle, done by LoadMUILibrary for old Windows versions, and just LoadLibrary for Vista and beyond? (And I suppose you could implement a custom scheme if you want as long as you get the desired resource DLL HINSTANCE.) But I'm not a globalisation dude so may be wrong.

  12. You can do user-defined types with an integer (e.g., WM_USER.)

  13. Jonathan says:

    Similar thing happened to me with WNetAddConnection2, whose prototype is (..., LPCTSTR lpPassword, LPCTSTR lpUsername, ...). Who in his right mind puts the password first? That's one debugging day I'd like to have back.

    In other news, C# and other modern languages have named parameters to avoid these kinds of mistakes:

    WNetAddConnection2(..., lpPassword: myPassword, lpUsername: myUsername, ...)

    I've been pestering my team mates to add these anywhere the params are not blindingly obvious.

  14. Rick C says:

    "In other news, C# and other modern languages have named parameters to avoid these kinds of mistakes"

    And if you're using Visual Studio (or another IDE that provides an equivalent) IntelliSense will help you out as you're typing the function call out by showing you the parameter names.

  15. @Rick C says:

    "IntelliSense will help you" This helps to write a new call, but it doesn't help you to verify (understand) already written code. (I know how to force the tooltip in the IDE. But if you are looking at something in source control, you can't.)

Comments are closed.

Skip to main content