27

The macro is defined as:

#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))

How come this can be used to indicate either a resource ID (a 16-bit unsigned int) or its name (a pointer to an array of char)? Doesn't this effectively limit the address space (on a 32-bit system) to 16-bit? Otherwise how does the system know whether I'm using an ID or a name?

twf
  • 549
  • 1
  • 5
  • 11
  • 5
    These macros always confused me. Why did they not just implement two separate functions, one that takes an integer ID and one that takes a string... – dreamlax Aug 31 '10 at 21:11
  • thanks this was actually what I was looking for. – sandun dhammika Oct 12 '12 at 18:47
  • 1
    @dreamlax: Not doing that actually solves one of the problems it was created for in the first place ;) All data and functions in a DLL are assigned an ordinal, and the ordinal can be used in place of a name if you want. This imposes a limit on the number of useable ordinals in a DLL to 65K (due, ironically, to the existence of this macro and how it interacts with `GetProcAddress`). If you want to maintain ABI compatibility between DLL versions by having multiple versions of functions, each with unique ordinals, the fewer functions you have clogging things up the better. – Andon M. Coleman Dec 03 '16 at 05:10

3 Answers3

36

This works because Windows doesn't allow mapping pages for the first 64 KB of the address space. To catch null pointer references. But I think also to catch pointer bugs in programs that were converted from the 16-bit version of Windows.

A side-effect is that this allows to reliably distinguish resource IDs packed into a pointer value since they'll always point to non-mappable memory.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    +1 for history. It certainly seems like a plausible reason for that restriction on pointers. – RBerteig Sep 01 '10 at 06:50
  • 2
    @rbe The full historic context can be found here: [What is the highest numerical resource ID permitted by Win32?](https://devblogs.microsoft.com/oldnewthing/20110217-00/?p=11463) Plot spoiler: The fact that the first 64KB of memory are permanently invalid in 32-bit Windows is not the reason why the chosen partitioning is safe. That's just a happy coincidence. – IInspectable Nov 10 '20 at 20:20
22

MAKEINTRESOURCE macro just makes casting between numeric parameter and string pointer. The resulting string pointer is invalid and cannot be dereferenced as resource name. However, resource handling API detect such pointers by their absolute value and treat them as resource ID and not resource name. Since C-style API doesn't support overloading, they cannot define two functions like:

HICON LoadIcon(HINSTANCE hInstance,LPCTSTR lpIconName);
HICON LoadIcon(HINSTANCE hInstance,UINT resourceId);

So, API developers decided to use the same function for both cases, providing MAKEINTRESOURCE macro for API users. I believe that two different functions could look better:

HICON LoadIconByName(HINSTANCE hInstance,LPCTSTR lpIconName);
HICON LoadIconById(HINSTANCE hInstance,UINT resourceId);

But this is not the way Windows API is implemented. Valid resource ID is always less than minimal possible pointer value. Resource name parameter is passed to API without this macro, and its value is not restricted.

Alex F
  • 42,307
  • 41
  • 144
  • 212
7

Yes, it does limit the address space, but not as much as you think. They've effectively carved out 64KB of your 4GB address space. Most, if not all, of that 64KB is already reserved for other things on Windows, so the effective loss is nothing.

Overall, it's a space savings, because they don't need an extra bit of information to distinguish between a pointer and an integer ID. This was invented back in the bad old days, when space was at a premium.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • Thanks for your reply. But I don't understand why it only took 64KB. A resourse ID is a 16-bit interger, and a pointer is 32-bit long. Wouldn't it reduce the addressable space of a pointer to 16-bit long? – twf Aug 31 '10 at 23:30
  • 3
    If the value is less than 64KB, it's assumed to be an integer ID. If it's greater than 64KB (all the way up to 4GB), then it's assumed to be a pointer. – Adrian McCarthy Aug 31 '10 at 23:48
  • 1
    Remember, you don't pass a string resource name through this macro, only an integer resource ID. It does restrict integer resource IDs to 16-bits, but IIRC they were already by the binary resource format itself. – RBerteig Sep 01 '10 at 06:49
  • 1
    This also works for DLL ordinals, and allows you to import data and functions where the name has been stripped. Basically, for a handful of functions in the Win32 API that take a pointer to a C-string, you have the option of using this to reference something that has no actual name. All scenarios where this works have little to no practical need to ever distiniguish more than 64k unique unnamed items :) – Andon M. Coleman Dec 03 '16 at 04:50