diff --git a/desktop-src/menurc/resource-types.md b/desktop-src/menurc/resource-types.md
index 55d00785f6..f90d5deeee 100644
--- a/desktop-src/menurc/resource-types.md
+++ b/desktop-src/menurc/resource-types.md
@@ -60,12 +60,151 @@ The following are the predefined resource types.
|
- **RT\_MESSAGETABLE**
- MAKEINTRESOURCE(11)
| Message-table entry.
|
| - **RT\_PLUGPLAY**
- MAKEINTRESOURCE(19)
| Plug and Play resource.
|
| - **RT\_RCDATA**
- MAKEINTRESOURCE(10)
| Application-defined resource (raw data).
|
-| - **RT\_STRING**
- MAKEINTRESOURCE(6)
| String-table entry.
|
+| - **RT\_STRING**
- MAKEINTRESOURCE(6)
| String-table entry. See **Remarks** below for more info.
|
| - **RT\_VERSION**
- MAKEINTRESOURCE(16)
| Version resource.
|
| - **RT\_VXD**
- MAKEINTRESOURCE(20)
| VXD.
|
+## Remarks
+
+### String Table Resources
+
+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
+determine which string resource ID(s) they actually contain.
+
+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(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(lParam);
+ if (lpType == RT_STRING)
+ {
+ return EnumerateResourceNamesWrapperForStrings(hModule, lpName, params->lParam, params->lpEnumFunc);
+ }
+
+ return params->lpEnumFunc(hModule, lpType, lpName, params->lParam);
+ }, reinterpret_cast(¶ms));
+};
+
+//////////
+
+// Sample callback that just increments a counter.
+BOOL CountResources(HMODULE, LPCWSTR, LPWSTR, LONG_PTR lParam)
+{
+ // Add one to the count...
+ (*(reinterpret_cast(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;
+}
+```
+
## Requirements