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