windowsvisual-c++resource-files

Are there different MFC resource spaces allowing same resource id to be used for different types of resources


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:

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:

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:

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:

I 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

Solution

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