65.9K
CodeProject 正在变化。 阅读更多。
Home

MFC 资源回退

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2007年5月16日

3分钟阅读

viewsIcon

47415

downloadIcon

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);
}

关注点

此技巧对于以下场景中的本地化非常有用

  1. 使用嵌入的本机资源构建您的 EXE。
  2. 使用一些特定的本地化工具提取资源。
  3. 使用本地化工具,翻译资源并创建仅包含 res 的 DLL。
  4. 向您的客户提供包含其语言 DLL 的 EXE。
  5. 如果在将来您仅修补 EXE 而不修改资源,则仅重新分发 EXE。
  6. 如果在将来您向 EXE 添加新资源,如果您添加具有新 ID 的新资源,您可以在部署 EXE 时,客户将看到已翻译的旧内容和默认语言的新内容,同时等待翻译过程完成。 当新的翻译完成后,您只需部署 DLL。

历史

  • 2007 年 5 月 16 日 - 发布原始版本
  • 2007 年 7 月 11 日 - 文章经过编辑并移动到主 CodeProject.com 文章库

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.