A Windows application uses a variety of resources that have identifiers in the resource file associated with the application.
The resource file contains various kinds of resources with resource identifiers (see How to: Manage Symbols) such as:
LoadString()
LoadBitmap()
LoadIcon()
LoadCursor()
LoadMenu()
Question: Is it possible and reasonable to use the same resource identifier for different types of resources in the same resource file (.rc file) and what are the rules that would prevent identifier collisions? By this I'm asking is the resource identifier scoped to a specific resource type depending on the Windows API function used with the resource identifier to access a resource as it appears that it is? So the same resource identifier used with different Windows API functions accesses different resource pools or collections and reusing the same resource identifier in the .rc file is fine with the Resource Compiler because it puts different resource types into different resource collections each with its own namespace?
Since resource identifiers use #define
an associated question is what is the limit for the number of resource identifiers in use for Visual Studio 2019 and later editions of Visual Studio? Does the Resource Compiler have a different limit?
AI search results says "There isn't a built-in limit to the number of preprocessor defines that Visual Studio 2019 can process during compilation. The compiler will process and handle all defines as specified in the source code." however the linked to reference doesn't seem to mention that limit.
So could I use the same resource identifier for the following in the same resource file:
MENUITEM
with identifier IDD_ABOUTBOX
IDD_ABOUTBOX DIALOG 0, 0, 219, 55
ICON
resource identifier in the dialog, ICON IDD_ABOUTBOX,IDC_STATIC,5,5,20,20
LTEXT
control identifier in the dialog, LTEXT "",IDD_ABOUTBOX,35,15,135,20,SS_NOPREFIX
STRINGTABLE
resource identifier IDD_ABOUTBOX "MyApp - a fine app\nVersion: 1.0.0\nCopyright: 2020-2025 Mine"
I realize that each control in a dialog must have a unique resource identifier within the scope of the dialog since the dialog API uses the specified resource identifier to determine which control in the dialog is the target for the dialog API. However resource identifiers for controls can be reused across multiple dialogs.
Looking at an older MFC application's resource file I see the following reuse of a resource identifier:
IDR_MAINFRAME ICON "res\\PCSample.ico"
IDR_MAINFRAME BITMAP "res\\Toolbar.bmp"
IDR_MAINFRAME TOOLBAR 16, 15
IDR_MAINFRAME MENU
IDR_MAINFRAME ACCELERATORS
STRINGTABLE
of IDR_MAINFRAME "PCSample\n\nPCSamp\n\n\nPCSample.Document\nPCSamp Document"
This Stackoverflow post, MFC - Resource IDs uniqueness, mentions this Microsoft Technical note, TN020: ID Naming and Numbering Conventions, which contains this table:
Prefix Resource type Valid range
IDR_ multiple 1 through 0x6FFF
IDD_ dialog templates 1 through 0x6FFF
IDC_,IDI_,IDB_ cursors, icons, bitmaps 1 through 0x6FFF
IDS_, IDP_ general strings 1 through 0x7FFF
ID_ commands 0x8000 through 0xDFFF
IDC_ controls 8 through 0xDFFF
and goes on to say:
Reasons for these range limits:
By convention, the ID value of 0 is not used.
Windows implementation limitations restrict true resource IDs to be less than or equal to 0x7FFF.
MFC's internal framework reserves these ranges:
0x7000 through 0x7FFF (see afxres.h)
0xE000 through 0xEFFF (see afxres.h)
16000 through 18000 (see afxribbonres.h)
These ranges may change in future MFC implementations.
Several Windows system commands use the range of 0xF000 through 0xFFFF.
Control IDs of 1 through 7 are reserved for standard controls such as IDOK and IDCANCEL.
The range of 0x8000 through 0xFFFF for strings is reserved for menu prompts for commands.
ADDENDUM: Simple console app to explore the resource file
I wrote a console application with Visual Studio 2019 containing the source code and adding a resource file with resource include file to further explore this topic.
I created this as a 32 bit Windows application and ran it in the Visual Studio 2019 debugger to exercise it.
The goal of this exercise was to look at parsing the binary resource file and to see what happens with string labels rather than integer labels for both the TYPE
and NAME
members of the RESOURCEHEADER
struct.
See Microsoft Learn article User-Defined Resource for a description of user defined resources. I did run into a problem when I tried the example with the ANSI string in a resource.
I modified the resource.rc file and resource.h file by hand trying the following:
NAME
or a stringTYPE
, or both NAME
and TYPE
as stringI ran into a limit for the length of the NAME
and TYPE
as specified in the resource.rc file. It appears there is a maximum number of characters that is shared for these two members. As I increased the number of characters in the TYPE
string the number of characters in the resource.res, the compiled resource file, for the NAME
string was reduced with the string being truncated.
The program is as follows:
// walkresourcefile.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
// walk through the entries of a binary resource file
// see Resource file formats
// URL: https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
//
// A binary resource file consists of a number of concatenated resource entries. Each entry consists of a resource header
// and the data for that resource. A resource header is DWORD-aligned in the file and consists of the following:
//
// A DWORD that contains the size of the resource header
// A DWORD that contains the size of the resource data
// The resource type
// The resource name
// Additional resource information
// The RESOURCEHEADER structure describes the format of this header.The data for the resource follows the resource header
// and is specific to each type of resource.Some resources also employ a resource - specific group header structure to
// provide information about a group of resources.
#include <Windows.h>
#include <WinUser.h>
#include <string.h>
#include <stdio.h>
// following strut more of a guideline than an actual rule. The first three
// members are
typedef struct {
DWORD DataSize; // size of the data block following the header in bytes
DWORD HeaderSize; // size of the header in bytes
DWORD TYPE; // indicates the type of data. see MAKEINTRESOURCE()
DWORD NAME; // contains the name. amy be integer constant or a wchar_t string of 10 characters.
DWORD DataVersion;
WORD MemoryFlags;
WORD LanguageId;
DWORD Version;
DWORD Characteristics;
} RESOURCEHEADER;
typedef struct {
DWORD NAME; // contains the name. amy be integer constant or a wchar_t string of 10 characters.
DWORD DataVersion;
WORD MemoryFlags;
WORD LanguageId;
DWORD Version;
DWORD Characteristics;
}RESOURCEHEADER1;
typedef struct {
DWORD DataVersion;
WORD MemoryFlags;
WORD LanguageId;
DWORD Version;
DWORD Characteristics;
}RESOURCEHEADER2;
int main()
{
// this Visual Studio console project contains a resource file that is
// compiled resulting in the binary resource file walkresourcefile.res
// which is included for testing purposes. Since we just run this app in
// the debugger of Visual Studio, we will just access that file which is
// in the Debug folder of our solution directory where all the build products
// end up when compiling in Debug mode.
char sFileName[256] = { "Debug\\walkresourcefile.res"};
RESOURCEHEADER resourceHeader = { 0 };
FILE *fStream;
errno_t myError;
long lOffset = 0;
ULONG ulTest = (ULONG)RT_STRING;
// create an array of labels for the standard resource types and
// populate the array with the labels. We are using the values of
// the standard types as the array index to make lookup easy.
const char* pStrings[20] = { 0 };
pStrings[(ULONG)RT_STRING] = "RT_STRING";
pStrings[(ULONG)RT_DIALOG] = "RT_DIALOG";
pStrings[(ULONG)RT_BITMAP] = "RT_BITMAP";
pStrings[(ULONG)RT_FONT] = "RT_FONT";
pStrings[(ULONG)RT_CURSOR] = "RT_CURSOR";
pStrings[(ULONG)RT_GROUP_CURSOR] = "RT_GROUP_CURSOR";
pStrings[(ULONG)RT_MENU] = "RT_MENU";
pStrings[(ULONG)RT_ICON] = "RT_ICON";
pStrings[(ULONG)RT_GROUP_ICON] = "RT_GROUP_ICON";
pStrings[(ULONG)RT_RCDATA] = "RT_RCDATA";
pStrings[(ULONG)RT_VERSION] = "RT_VERSION";
myError = fopen_s(&fStream, sFileName, "rb");
for (int i = 0; i < 3000; i++) { // we'll just put an arbitrary though large value on the amount of output.
fseek(fStream, lOffset, SEEK_SET);
if (fread(&resourceHeader, sizeof(resourceHeader), 1, fStream) == 0) break;
printf(" lOffset = %d: ", lOffset);
// A variable type member is called a Name or Ordinal member, and it is used
// in most places in the resource file where an identifier appears. The first
// WORD of a Name or Ordinal type member indicates whether the member is a
// numeric value or a string. If the first WORD in the member is equal to the
// value 0xffff, which is an invalid Unicode character, then the following WORD
// is a type number. Otherwise, the member contains a Unicode string and the
// first WORD in the member is the first character in the name string. For
// additional information about resource definition statements, see Resource-Definition Statements.
//
// It appears from testing with this application that there is a max length when both TYPE and NAME
// are both strings. They share this max length so as TYPE gets longer then NAME gets shorter.
ULONG ulTest = 0;
if (LOWORD(resourceHeader.TYPE) == 0xFFFF) {
ulTest = HIWORD(resourceHeader.TYPE);
if (ulTest < sizeof(pStrings) / sizeof(pStrings[0]) && pStrings[ulTest]) {
printf("%s ", pStrings[ulTest]);
}
if ((LOWORD(resourceHeader.NAME) != 0xFFFF)) {
wchar_t nameLabel[16] = { 0 };
for (int i = 0; i < 10; i++) { // from testing, it appears that ten characters is the max length
nameLabel[i] = ((wchar_t*)&resourceHeader.NAME)[i];
}
printf(" DataSize = %ul HeaderSize = %ul, Type = %ul 0x%x, Name = %d 0x%x %S \n", resourceHeader.DataSize, resourceHeader.HeaderSize, resourceHeader.TYPE, resourceHeader.TYPE, HIWORD(resourceHeader.NAME), LOWORD(resourceHeader.NAME), nameLabel);
}
else {
printf(" DataSize = %ul HeaderSize = %ul, Type = %ul 0x%x, Name = %d %d/0x%x \n", resourceHeader.DataSize, resourceHeader.HeaderSize, resourceHeader.TYPE, resourceHeader.TYPE, HIWORD(resourceHeader.NAME), LOWORD(resourceHeader.NAME), LOWORD(resourceHeader.NAME));
}
}
else {
// TYPE is a string and not a constant integer value
wchar_t* pType = (wchar_t*)&resourceHeader.TYPE;
size_t lTypeLen = wcslen(pType);
RESOURCEHEADER1 * pResource1 = (RESOURCEHEADER1 *)(pType + lTypeLen + 1);
wchar_t* pName = (wchar_t*)&pResource1->NAME;
printf(" %S ", (wchar_t*)&resourceHeader.TYPE);
printf(" DataSize = %ul HeaderSize = %ul, ", resourceHeader.DataSize, resourceHeader.HeaderSize);
if ((LOWORD(resourceHeader.NAME) != 0xFFFF)) {
wchar_t nameLabel[16] = { 0 };
for (int i = 0; i < 10; i++) { // from testing, it appears that ten characters is the max length
if (pName[i] > 0xff) break;
nameLabel[i] = pName[i];
}
printf("Name = % d 0x % x % S \n", HIWORD(pResource1->NAME), LOWORD(pResource1->NAME), nameLabel);
}
else {
printf("Name = %d %d/0x%x \n", HIWORD(resourceHeader.NAME), LOWORD(resourceHeader.NAME), LOWORD(resourceHeader.NAME));
}
}
if (ulTest == (ULONG)RT_STRING) {
wchar_t bundleBuff[4096] = { 0 }; // large buffer sized for 16 strings of 256 characters.
ULONG ulStrBundle = lOffset + resourceHeader.HeaderSize; // file offset to beginning of string bundle
// seek to where the string bundle begins in the file then read the string bundle in
// and then print the individual strings. strings have length in first entry
fseek(fStream, ulStrBundle, SEEK_SET);
if (fread(bundleBuff, resourceHeader.DataSize, 1, fStream) != 0) {
LPWSTR pwsz = bundleBuff;
for (int i = 0; i < 16; i++) {
if (*pwsz > 0) {
// replace the character after the end of the sring with a
// binary zero string terminator, print the string, then put back.
wchar_t temp = *(pwsz + *pwsz + 1);
*(pwsz + *pwsz + 1) = 0;
printf(" %d: %S\n", i, pwsz + 1);
*(pwsz + *pwsz + 1) = temp;
}
pwsz += 1 + *pwsz;
}
}
}
lOffset = resourceHeader.HeaderSize + resourceHeader.DataSize + lOffset; // compute file offset to the next resource in the file
if ((lOffset % 4) != 0) lOffset += 2; // make sure the file offset is on a DWORD boundary.
}
fclose(fStream);
}
The resource file source that is compiled into the binary resource file is:
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
//
IDB_BITMAP1 BITMAP "bitmap1.bmp"
MYBITMAP BITMAP "bitmap1.bmp"
MYBITMAP1234 BITMAP "bitmap1.bmp"
MYJUNK THING "bitmap1.bmp"
MYJUN3 THUNG "bitmap1.bmp"
MYJUN4 THING1234 "bitmap1.bmp"
// using the same NAME for two different TYPE
IDC_JUNK01 THENG "bitmap1.bmp"
IDC_JUNK01 THING1234 "bitmap1.bmp"
MYJUN2 THING2
{
L"A wchar_t string\0",
/* "An ANSI string\0", ANSI string seems to cause a problem.*/
101,
192168L,
0x1ff1
}
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_DIALOG1 DIALOGEX 0, 0, 287, 166
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog Box"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,198,145,50,14
PUSHBUTTON "Cancel",IDCANCEL,230,145,50,14
CONTROL "Check box 1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,19,20,155,21
EDITTEXT IDC_EDIT1,142,41,106,25,ES_AUTOHSCROLL
LTEXT "Static label for edit box",IDC_STATIC,41,47,98,15
CONTROL "",IDC_SLIDER1,"msctls_trackbar32",TBS_BOTH | WS_TABSTOP,143,74,104,23
LTEXT "Static label for slider control.",IDC_STATIC,35,81,103,16
CONTROL "Radio1",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,33,100,68,12
CONTROL "Radio2",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,34,115,68,17
CONTROL "Radio3",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,34,133,58,14
END
IDD_DIALOG2 DIALOGEX 0, 0, 255, 131
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog 2 empty"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,198,110,50,14
PUSHBUTTON "Cancel",IDCANCEL,198,110,50,14
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_DIALOG1, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 280
TOPMARGIN, 7
BOTTOMMARGIN, 159
END
IDD_DIALOG2, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 248
TOPMARGIN, 7
BOTTOMMARGIN, 124
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//
IDD_DIALOG1 AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_DIALOG2 AFX_DIALOG_LAYOUT
BEGIN
0
END
/////////////////////////////////////////////////////////////////////////////
//
// Cursor
//
IDC_CURSOR1 CURSOR "cursor1.cur"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_STRING101 "this is string 101"
IDS_STRING102 "this is string 102"
IDS_STRING103 "this is string 103"
END
STRINGTABLE
BEGIN
IDS_STRNG_01 "STRNG_01 test string id"
IDS_STRNG_02 "STRNG_02 test string id"
IDS_STRNG_03 "STRNG_03 test string id"
IDS_STRNG_04 "STRNG_04 test string id"
IDS_STRNG_05 "STRNG_05 test string id"
IDS_STRNG_06 "STRNG_06 test string id"
IDS_STRNG_07 "STRNG_07 test string id"
IDS_STRNG_08 "STRNG_08 test string id"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
And the resource.h include file is:
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by walkresourcefile.rc
//
#define IDS_STRING101 101
#define IDS_STRING102 102
#define IDB_BITMAP1 102
#define IDS_STRING103 103
#define IDD_DIALOG1 103
#define IDC_CURSOR1 105
#define IDD_DIALOG2 106
#define IDS_STRNG_01 108
#define IDS_STRNG_02 109
#define IDS_STRNG_03 110
#define IDS_STRNG_04 111
#define IDS_STRNG_05 112
#define IDS_STRNG_06 113
#define IDS_STRNG_07 114
#define IDS_STRNG_08 115
#define IDC_CHECK1 1001
#define IDC_EDIT1 1002
#define IDC_SLIDER1 1003
#define IDC_RADIO1 1004
#define IDC_RADIO2 1005
#define IDC_RADIO3 1006
#define IDC_JUNK01 1007
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 116
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1008
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
The output produced by the application is:
lOffset = 0: DataSize = 0l HeaderSize = 32l, Type = 65535l 0xffff, Name = 0 65535/0xffff
lOffset = 32: RT_BITMAP DataSize = 1256l HeaderSize = 32l, Type = 196607l 0x2ffff, Name = 102 65535/0xffff
lOffset = 1320: RT_BITMAP DataSize = 1256l HeaderSize = 48l, Type = 196607l 0x2ffff, Name = 89 0x4d MYBITMAP
lOffset = 2624: RT_BITMAP DataSize = 1256l HeaderSize = 56l, Type = 196607l 0x2ffff, Name = 89 0x4d MYBITMAP12
lOffset = 3936: THING DataSize = 1270l HeaderSize = 52l, Name = 89 0x 4d MYJUNK
lOffset = 5260: THUNG DataSize = 1270l HeaderSize = 52l, Name = 89 0x 4d MYJUN3
lOffset = 6584: THING1234 DataSize = 1270l HeaderSize = 60l, Name = 89 0x 4d MY
lOffset = 7916: THENG DataSize = 1270l HeaderSize = 40l, Name = 1007 0x ffff
lOffset = 9228: THING1234 DataSize = 1270l HeaderSize = 48l, Name = 1007 0x ffff
lOffset = 10548: THING2 DataSize = 44l HeaderSize = 52l, Name = 89 0x 4d MYJUN
lOffset = 10644: RT_DIALOG DataSize = 628l HeaderSize = 32l, Type = 393215l 0x5ffff, Name = 103 65535/0xffff
lOffset = 11304: RT_DIALOG DataSize = 172l HeaderSize = 32l, Type = 393215l 0x5ffff, Name = 106 65535/0xffff
lOffset = 11508: AFX_DIALOG_L DataSize = 2l HeaderSize = 64l, Name = 0 0x 0
lOffset = 11576: AFX_DIALOG_L DataSize = 2l HeaderSize = 64l, Name = 0 0x 0
lOffset = 11644: RT_CURSOR DataSize = 308l HeaderSize = 32l, Type = 131071l 0x1ffff, Name = 1 65535/0xffff
lOffset = 11984: RT_GROUP_CURSOR DataSize = 20l HeaderSize = 32l, Type = 851967l 0xcffff, Name = 105 65535/0xffff
lOffset = 12036: RT_STRING DataSize = 324l HeaderSize = 32l, Type = 458751l 0x6ffff, Name = 7 65535/0xffff
5: this is string 101
6: this is string 102
7: this is string 103
12: STRNG_01 test string id
13: STRNG_02 test string id
14: STRNG_03 test string id
15: STRNG_04 test string id
lOffset = 12392: RT_STRING DataSize = 216l HeaderSize = 32l, Type = 458751l 0x6ffff, Name = 8 65535/0xffff
0: STRNG_05 test string id
1: STRNG_06 test string id
2: STRNG_07 test string id
3: STRNG_08 test string id
Although I have been unable to find a formal specification for the format of a binary resource file used by MFC resources (and MSVC/Windows ".res" files in general), the outline given in this short Microsoft article seems to suggest quite clearly that both the resource type and its name (or ID) are used in the 'header' for each resource contained in such a binary file.
In C++ terms, the RESOURCEHEADER
structure is defined as follows:
typedef struct {
DWORD DataSize;
DWORD HeaderSize;
DWORD TYPE;
DWORD NAME;
DWORD DataVersion;
WORD MemoryFlags;
WORD LanguageId;
DWORD Version;
DWORD Characteristics;
} RESOURCEHEADER;
(The DWORD TYPE
and DWORD NAME
fields, although most often integer values, can be null-terminated Unicode strings, but that doesn't change the point I'm making.)
So, as long as there are no two resources with similar values for both TYPE
and NAME
, then there will be no 'collision'. (In fact, I think that differences in the LanguageID
field will allow two or more resources to actually have the same values for both the aforementioned fields.)
In summary: although there is no formal namespace separation between resource types, those value are, effectively, both part of the "unique identity" of each resource in a given binary ".res" file.