MFC 资源回退





5.00/5 (9投票s)
2007年5月16日
3分钟阅读

47415

646
一个小型技巧,用于实现 MFC 的回退资源流程,这对于本地化很有用
引言
本文解释了一个实用的小技巧,用于使用 MFC 实现资源回退过程。 使用此技巧,您可以从 DLL 动态加载资源,如果 DLL 中不存在资源,则从主 EXE 模块加载。
背景
我理想的本地化模式是让主 EXE 包含以本机语言嵌入的资源,并可选地包含一个仅包含要本地化的资源的资源 DLL。 如果 DLL 中不存在资源,则必须从 EXE 模块加载。 一个重要的前提条件是必须由默认的 MFC 函数加载资源。
如果您尝试在 MFC 中实现此行为,您会发现一个问题:虽然使用 DLL 处理程序覆盖默认应用程序资源处理程序非常简单,但如果 DLL 中缺少资源,则在加载时会失败。 MFC 提供了一种机制,如果 EXE 中缺少资源,则使用全局链接列表(DLL 链)从附加的 DLL 加载资源。 我的想法是将 EXE 附加到此 DLL 链,因此如果 DLL 中不存在资源,资源加载过程将在 EXE 中搜索。
Using the Code
要实现此技巧,您需要将两个 private
变量 m_hResDll
和 m_pExeModule
添加到您的应用程序类,并重写 ExitInstance
class CResFallbackApp : public CWinApp
{
public:
CResFallbackApp();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CResFallbackApp)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
//{{AFX_MSG(CResFallbackApp)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
HMODULE m_hResDll;
CDynLinkLibrary* m_pExeModule;
};
现在在 InitInstance
的开头,添加此代码
BOOL CResFallbackApp::InitInstance()
{
//Search resources dll
m_hResDll = LoadLibrary(_T("ResDll.dll"));
//put here better way to find the right dll
if(m_hResDll != NULL)
{
//if found:
//1 - puts EXE module in extension DLL chain
//(using an instance of CDynLinkLibrary)
m_pExeModule =
new CDynLinkLibrary(AfxGetInstanceHandle(),
AfxGetResourceHandle());
//2 - puts DLL as principal resources supplier
AfxSetResourceHandle(m_hResDll);
//now a resource is searched starting from resdll
//and if not found, is searched in
//exe module
}
}
然后在 ExitInstance
中添加一些清理代码
int CResFallbackApp::ExitInstance()
{
//cleaning
if(m_hResDll != NULL)
{
delete m_pExeModule;
FreeLibrary(m_hResDll);
}
return CWinApp::ExitInstance();
}
诀窍是使用 EXE 模块实例和资源处理程序创建类型为 CDynLinkLibrary
的对象。 CDynLinkLibrary
的构造函数将自身附加到模块状态中的 DLL 链(跟踪以获取更多详细信息)。 现在,如果您或 MFC 尝试加载任何类型的资源,如果 EXE 中没有找到,则首先在 DLL 中搜索。 例如
void CResFallbackDlg::OnTest()
{
CString sMsg;
sMsg.LoadString(IDS_TEST);
AfxMessageBox(sMsg);
sMsg.LoadString(IDS_TEST2);
AfxMessageBox(sMsg);
}
如果您想要更多详细信息,请跟踪到 LoadString
,您将到达 mfc\src\dllinit.cpp 中的以下代码片段。 在这里,您可以看到资源查找的工作方式。
int AFXAPI AfxLoadString(UINT nID, LPTSTR lpszBuf, UINT nMaxBuf)
{
ASSERT(AfxIsValidAddress(lpszBuf, nMaxBuf*sizeof(TCHAR)));
LPCTSTR lpszName = MAKEINTRESOURCE((nID>>4)+1);
HINSTANCE hInst;
int nLen;
// first check the main module state
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
if (!pModuleState->m_bSystem)
{
hInst = AfxGetResourceHandle();
if (::FindResource(hInst, lpszName, RT_STRING) != NULL &&
(nLen = ::LoadString(hInst, nID, lpszBuf, nMaxBuf)) != 0)
{
// found a non-zero string in app
return nLen;
}
}
// check non-system DLLs in proper order
AfxLockGlobals(CRIT_DYNLINKLIST);
for (CDynLinkLibrary* pDLL =
pModuleState->m_libraryList; pDLL != NULL;
pDLL = pDLL->m_pNextDLL)
{
if (!pDLL->m_bSystem && (hInst = pDLL->m_hResource) != NULL &&
::FindResource(hInst, lpszName, RT_STRING) != NULL &&
(nLen = ::LoadString(hInst, nID, lpszBuf, nMaxBuf)) != 0)
{
AfxUnlockGlobals(CRIT_DYNLINKLIST);
return nLen;
}
}
AfxUnlockGlobals(CRIT_DYNLINKLIST);
}
关注点
此技巧对于以下场景中的本地化非常有用
- 使用嵌入的本机资源构建您的 EXE。
- 使用一些特定的本地化工具提取资源。
- 使用本地化工具,翻译资源并创建仅包含 res 的 DLL。
- 向您的客户提供包含其语言 DLL 的 EXE。
- 如果在将来您仅修补 EXE 而不修改资源,则仅重新分发 EXE。
- 如果在将来您向 EXE 添加新资源,如果您添加具有新 ID 的新资源,您可以在部署 EXE 时,客户将看到已翻译的旧内容和默认语言的新内容,同时等待翻译过程完成。 当新的翻译完成后,您只需部署 DLL。
历史
- 2007 年 5 月 16 日 - 发布原始版本
- 2007 年 7 月 11 日 - 文章经过编辑并移动到主 CodeProject.com 文章库
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。