-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add info about string-table resources #2110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: docs
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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 | ||||||
|
||||||
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
determine which string resource ID(s) they actually contain. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
|
||||||
|
||||||
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>(¶ms)); | ||||||
}; | ||||||
|
||||||
////////// | ||||||
|
||||||
// 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; | ||||||
} | ||||||
``` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
|
||||||
|
||||||
|
There was a problem hiding this comment.
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).