使用 UpdateResource 更改字符串资源






4.49/5 (7投票s)
2004 年 9 月 21 日
3分钟阅读

101618
用于更改字符串资源的代码,确实有效!
引言
此例程展示了如何使用 UpdateResource
更改可执行文件中的单个字符串资源。
这一切的开始是因为我正在编写一个国际化的屏幕保护程序,它必须在所有 Windows 平台上工作,并且我需要在安装时能够将屏幕保护程序的名称更改为正确的语言。
对于 Win9x,这很容易,因为 .scr 文件名就是显示设置中显示的名称(要验证,在 9x 系统上,搜索 *.scr 文件),所以我所要做的就是重命名文件。 对于 NT4/2000/XP,情况就不同了。在这些操作系统上,屏幕保护程序的名称位于 .scr 文件的字符串资源 ID=1 中;文件名完全不同。
因此,我必须想办法在运行时更改字符串资源。
我知道 UpdateResource
是解决方案,但它的文档不完善,我发现在新闻组中,很多人在使用它处理字符串资源时遇到了很多问题。 这是一个有趣的 API 例程,即使它不起作用,似乎也永远不会返回错误代码!
好吧,我捅了捅,戳了戳,并在十六进制编辑器和资源查看器的帮助下进行调试,我终于弄清楚了。
让事情顺利工作的关键是
- 首先将替换字符串转换为 Unicode(这一点有明确的文档记录)。
- 除非您正在编写字符串 ID=0,否则传递给
UpdateResource
的缓冲区必须以一个或多个前导零的WCHAR
开头; 您添加的每个WCHAR
都会将字符串资源 ID 递增 1(因此在下面的代码中,要获取字符串 ID=1,我用一个零的WCHAR
开始缓冲区)。 - 在前导零
WCHAR
之后,包括一个包含字符串中字符数的WCHAR
,然后用实际转换的 Unicode 字符填充缓冲区。 - 缓冲区必须是
WCHAR
对齐的,并且它的大小必须是偶数个WCHAR
(为了保证这一点,我使用GlobalAlloc
)。
我确信我的代码可以更简洁,并且上面的一些假设可能并不完全正确,尽管它们在我尝试代码的有限范围内有效。
PADDINGXX 谜团解决了吗?
我发现 UpdateResource
有一个无害但奇怪的副作用……如果它需要插入填充以正确对齐部分,它会根据需要插入重复的模式 PADDINGXX。 因此,如果您在文件中看到这些字节,则很可能是因为该文件已由 UpdateResource
处理。 如果您正在调试 UpdateResource
,则输出文件中存在 PADDINGXX 字节的片段也可能表明您存在对齐或填充问题(但它们也可能无害地存在,例如,如果前面的部分需要填充几个字节)。
注意事项
- 此代码仅在字符串表与其他字符串表分开的具有字符串资源 ID=1 的可执行文件(例如,屏幕保护程序)上进行了测试。
- 这仅在 Windows NT4/2000/XP 上有效(Win9x 不支持
UpdateResource
和相关 API)。
示例代码
// szSSPath = path to .exe file // szSSName = replacement text BOOL SetScreenSaverName(LPCTSTR szSSPath, LPCTSTR szSSName) { HANDLE h = ::BeginUpdateResource(szSSPath,FALSE); if(!h) { // BeginUpdateResource failed return FALSE; } CString sNewString = szSSName; int iCharCount = sNewString.GetLength() + 1; LPWSTR pUnicodeString = new WCHAR[iCharCount]; if(!pUnicodeString) { // new failed return FALSE; } DWORD dwUnicodeCharCount = MultiByteToWideChar(CP_ACP, NULL, sNewString.GetBuffer(0), -1, pUnicodeString, iCharCount); HGLOBAL hGlob = GlobalAlloc(GHND, (dwUnicodeCharCount + 4) * sizeof(WCHAR) ); if(!hGlob) { // GlobalAlloc failed delete pUnicodeString; return FALSE; } LPWSTR pBufStart = (LPWSTR)GlobalLock(hGlob); if(!pBufStart) { // GlobalLock failed GlobalFree(hGlob); delete pUnicodeString; return FALSE; } LPWSTR pBuf = pBufStart; pBuf++; // offset to make it string ID=1. Increment by more // to change to a different string ID // next 2 bytes is string length *(pBuf++) = (WCHAR)dwUnicodeCharCount-1; for(int i=0; i<(int)dwUnicodeCharCount-1; i++) // remaining bytes are string in wide chars (2 bytes each) *(pBuf++) = pUnicodeString[i]; delete pUnicodeString; if(++dwUnicodeCharCount % 1) // make sure we write an even number dwUnicodeCharCount++; BOOL bSuccess = TRUE; if(!::UpdateResource(h, RT_STRING, MAKEINTRESOURCE(1), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), pBufStart, dwUnicodeCharCount * sizeof(WCHAR) ) ) { // UpdateResource failed bSuccess = FALSE; } GlobalUnlock(hGlob); GlobalFree(hGlob); ::EndUpdateResource(h, FALSE); // write changes return bSuccess; }