Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 140 additions & 1 deletion desktop-src/menurc/resource-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,151 @@ The following are the predefined resource types.
| <span id="RT_MESSAGETABLE"></span><span id="rt_messagetable"></span><dl> <dt>**RT\_MESSAGETABLE**</dt> <dt>MAKEINTRESOURCE(11)</dt> </dl> | Message-table entry.<br/> |
| <span id="RT_PLUGPLAY"></span><span id="rt_plugplay"></span><dl> <dt>**RT\_PLUGPLAY**</dt> <dt>MAKEINTRESOURCE(19)</dt> </dl> | Plug and Play resource.<br/> |
| <span id="RT_RCDATA"></span><span id="rt_rcdata"></span><dl> <dt>**RT\_RCDATA**</dt> <dt>MAKEINTRESOURCE(10)</dt> </dl> | Application-defined resource (raw data).<br/> |
| <span id="RT_STRING"></span><span id="rt_string"></span><dl> <dt>**RT\_STRING**</dt> <dt>MAKEINTRESOURCE(6)</dt> </dl> | String-table entry.<br/> |
| <span id="RT_STRING"></span><span id="rt_string"></span><dl> <dt>**RT\_STRING**</dt> <dt>MAKEINTRESOURCE(6)</dt> </dl> | String-table entry. See **Remarks** below for more info.<br/> |
| <span id="RT_VERSION"></span><span id="rt_version"></span><dl> <dt>**RT\_VERSION**</dt> <dt>MAKEINTRESOURCE(16)</dt> </dl> | Version resource.<br/> |
| <span id="RT_VXD"></span><span id="rt_vxd"></span><dl> <dt>**RT\_VXD**</dt> <dt>MAKEINTRESOURCE(20)</dt> </dl> | VXD.<br/> |



## Remarks

### String Table Resources
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this content belongs in a separate page entirely, since it applies to multiple APIs (FindResourceW, EnumResourceNamesW, UpdateResourceW, plus the A versions).


When enumerating String Table resources (type **RT_STRING**) with functions such as **EnumResourceNamesW**, the system doesn't
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inpsect these blocks to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inpsect these blocks to
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inspect these blocks to

determine which string resource ID(s) they actually contain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This phrasing makes it sound like EnumResourceNamesW contains a special case for strings. But really, there is no special case. EnumResourceNamesW enumerates the resources. It's just that strings do not map to resources 1:1.

I think we can delete this paragraph and instead augment the second paragraph.
'

Strings provided in the resource string table are packaged into blocks of 16 strings per block. Each string in the block takes the form of a 16-bit integer representing the length of the string, followed by that number of 16-bit Unicode (UTF-16LE) code units. Resource ID 1 is a block that holds strings 0 to 15, resource ID 2 is a block that holds strings 16 to 31, and in general, resource ID N is a block that holds strings (N-1) × 16 to (N-1)×16 + 15. Missing string table IDs are treated as empty strings, and any block that consists entirely of empty strings is omitted. The EnumResourceNamesW function enumerates the resource blocks, not the individual strings.

For example, the following string table

STRINGTABLE BEGIN
1 "One"
5 "Five"
100 "Hundred"
STRINGTABLE END

is encoded as two string blocks.

Block 1:

0000 | Length of string 0 (empty string)
0003 | Length of string 1 (three characters)
004F | "O"
006E | "n"
0065 | "e"
0003 | Length of string 2 (three characters)
0054 | "T"
0077 | "w"
006F | "o"
0000 | Length of string 3 (empty string)
0000 | Length of string 4 (empty string)
0004 | Length of string 5 (four characters)
0046 | "F"
0069 | "i"
0076 | "v"
0065 | "e"
0000 | Length of string 6 (empty string)
0000 | Length of string 7 (empty string)
0000 | Length of string 8 (empty string)
0000 | Length of string 9 (empty string)
0000 | Length of string 10 (empty string)
0000 | Length of string 11 (empty string)
0000 | Length of string 12 (empty string)
0000 | Length of string 13 (empty string)
0000 | Length of string 14 (empty string)
0000 | Length of string 15 (empty string)

Block 7:

0000 | Length of string 96 (empty string)
0000 | Length of string 97 (empty string)
0000 | Length of string 98 (empty string)
0000 | Length of string 99 (empty string)
0007 | Length of string 100 (7 characters)
0048 | "H"
0075 | "u"
006E | "n"
0064 | "d"
0072 | "r"
0065 | "e"
0064 | "d"
0000 | Length of string 101 (empty string)
0000 | Length of string 102 (empty string)
0000 | Length of string 103 (empty string)
0000 | Length of string 104 (empty string)
0000 | Length of string 105 (empty string)
0000 | Length of string 106 (empty string)
0000 | Length of string 107 (empty string)
0000 | Length of string 108 (empty string)
0000 | Length of string 109 (empty string)
0000 | Length of string 110 (empty string)
0000 | Length of string 111 (empty string)

Note that the strings are not null-terminated.


String resources are packaged into blocks, each of which contains 16 length-prefixed strings representing 16 consecutive
IDs (some of which may not be used; see below). Given a string resource ID `X`, it will placed in the resource block number
`(X \ 16) + 1` (where `\` denotes integer division). Within that block, the resource can be found as the `N`th entry,
where `N = X % 16`.

The following table shows some example string resource IDs and the block number (and offset) of where they will be
located:

| Resource ID | Block # | Offset |
|-|-|-|
| 1 | `(1 \ 16) + 1` = 1 | `1 % 16` = 1 |
| 2 | `(2 \ 16) + 1` = 1 | `2 % 16` = 2 |
| 5 | `(3 \ 16) + 1` = 1 | `5 % 16` = 5 |
| 15 | `(15 \ 16) + 1` = 1 | `15 % 16` = 15 |
| 20 | `(20 \ 16) + 1` = 2 | `20 % 16` = 4 |
| 32 | `(32 \ 16) + 1` = 3 | `32 % 16` = 0 |
| 50 | `(50 \ 16) + 1` = 4 | `50 % 16` = 2 |
| 100 | `(100 \ 16) + 1` = 7 | `100 % 16` = 4 |

Any unused (missing) resource IDs are denoted by zero-length strings. So in the example above, block #1 will have zero-length strings
representing IDs 0, 3, 4, and 6 - 14. None of the strings in the block have terminating **NULL** characters, since it is permitted
for string resources to contain embedded **NULLs**. Thus, the memory layout of block #1 looks like this, assuming each string's value
is "Hello world" (11 characters long) and where the numbers in angle brackets represent integers (not literal characters):

```
<0><11>Hello world<11>Hello world<0><0>
<11>Hello world<0><0><0><0><0><0><0><0>
<0><11>Hello world
```

The following code snippet shows an enumeration callback function that will enumerate individual **RT_STRING** resources
just like other types (like **RT_ICON**) than the blocks:

```C++
// Number of entries in the string table.
constexpr UINT STRING_TABLE_SIZE{ 16 };

// Returns the original resource ID from a given block / offset.
inline UINT GetStringResourceIdFromStringTable(LPCWSTR lpName, const unsigned int index)
{
_ASSERT(index < STRING_TABLE_SIZE);
return ((reinterpret_cast<UINT>(lpName) - 1) * STRING_TABLE_SIZE) + index;
}

// Helper function that will enumerate string table blocks, looking for resources.
BOOL EnumerateResourceNamesWrapperForStrings(HMODULE hModule, LPWSTR lpName, LONG_PTR lParam, ENUMRESNAMEPROCW lpEnumFunc)
{
// No need to free or unlock resources in Win32, so OK to throw away intermediates
auto ptr = (wchar_t*)LockResource(LoadResource(hModule, FindResource(hModule, lpName, RT_STRING)));
if (ptr)
{
for (unsigned int i = 0; i < STRING_TABLE_SIZE; ++i)
{
wchar_t size = *ptr;
if (size > 0)
{
auto id = GetStringResourceIdFromStringTable(lpName, i);

// Invoke the callback for this string resource ID.
auto callbackResult = lpEnumFunc(hModule, RT_STRING, MAKEINTRESOURCE(id), lParam);
if (!callbackResult)
{
return callbackResult;
}
}
// Skip to next potential string in the block
ptr += size + 1;
}
return TRUE;
}

// Couldn't load the string table entry; something is wrong.
return FALSE;
}

// Wrapper function for EnumResourceNamesW that will enumerate individual string resources.
BOOL EnumResourceNamesIncludingStringsW(HMODULE hModule, LPCWSTR lpType, ENUMRESNAMEPROCW lpEnumFunc, LONG_PTR lParam)
{
struct param_wrapper { ENUMRESNAMEPROCW lpEnumFunc; LONG_PTR lParam; } params{ lpEnumFunc, lParam };

// Use a simple lambda to either call our String helper, or directly call the user's callback
return EnumResourceNamesW(hModule, lpType, [](auto hModule, auto lpType, auto lpName, auto lParam)
{
auto params = reinterpret_cast<param_wrapper*>(lParam);
if (lpType == RT_STRING)
{
return EnumerateResourceNamesWrapperForStrings(hModule, lpName, params->lParam, params->lpEnumFunc);
}

return params->lpEnumFunc(hModule, lpType, lpName, params->lParam);
}, reinterpret_cast<LONG_PTR>(&params));
};

//////////

// Sample callback that just increments a counter.
BOOL CountResources(HMODULE, LPCWSTR, LPWSTR, LONG_PTR lParam)
{
// Add one to the count...
(*(reinterpret_cast<UINT*>(lParam)))++;
return TRUE;
}

// Sample usage:
void CountStringsInExplorer()
{
auto lib = LoadLibraryExW(LR"(c:\windows\explorer.exe)", nullptr, LOAD_LIBRARY_AS_DATAFILE);
if (!lib)
{
return;
}

UINT nStringBlocks{ 0 };
UINT nStrings{ 0 };

// Count the number of string blocks using raw Win32 API.
EnumResourceNamesW(lib, RT_STRING, CountResources, (LONG_PTR)&nStringBlocks);

// Count the number of actual strings, using the wrapper.
EnumResourceNamesIncludingStringsW(lib, RT_STRING, CountResources, (LONG_PTR)&nStrings);

// Outputs something like:
//
// Explorer.exe contains 44 strings (in 17 blocks).
//

std::wcout << L"Explorer.exe contains " << nStrings << L" strings (in " << nStringBlocks
<< L" blocks)." << std::endl;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would accept a path as a parameter, rather than hard-coding Explorer.

Maybe

BOOL PrintStringsInBlock(HMODULE module, PCWSTR type, PCWSTR name, LONG_PTR data)
{
    // Load the block and obtain a pointer to it.
    auto ptr = (wchar_t*)LockResource(LoadResource(hModule, FindResource(module, name, type)));
    if (ptr)
    {
        // Print all the non-empty strings in the block.
        unsigned int baseId = (PtrToInt(name) - 1) * 16;
        for (unsigned int stringIndex = 0; stringIndex < 16; ++stringIndex)
        {
            wchar_t size = *ptr;
            if (size > 0)
            {
                printf("ID %u: ", baseId + stringIndex);
                for (unsigned int charIndex = 0; charIndex < size; ++charIndex)
                {
                    fputwc(ptr[charIndex + 1], stdout); // requires stdout to be set to Unicode
                }
                printf("\n");
            }
            // Skip to next potential string in the block
            ptr += size + 1;
        }
    }
    return TRUE;
}

void PrintStringsInModule(PCWSTR modulePath)
{
    HMODULE module = LoadLibraryExW(modulePath, nullptr, LOAD_LIBRARY_AS_DATAFILE);
    if (!module) return;

    EnumResourceNamesW(module, RT_STRING, PrintStringsInBlock, 0);
    FreeLibrary(module);
]


## Requirements


Expand Down