Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

MFC Resources Fallback

5.00/5 (10 votes)
11 Jul 20073 min read 1   632  
A small tip for implementing a fallback resources process with MFC that's useful for localization

Introduction

This article explains a small but useful tip to implement a resource fallback process with MFC. With this tip, you can load resources dynamically from a DLL and if a resource isn't present in the DLL, it is loaded from the main EXE module.

Background

My ideal localization pattern is to have the main EXE with embedded resources in the native language and, optionally, a resource-only DLL containing those resources to be localized. If a resource isn't present in the DLL, it has to be loaded from the EXE module. An important requisite is that resource has to be loaded by the default MFC functions.

If you try to implement this behaviour in MFC, you find a problem: while it is very simple to override the default application resource handler with the DLL one, if a resource is missing in the DLL, it fails when loading. MFC provides a mechanism to load resources from an attached DLL if they are missing from the EXE using a global linked list (DLL chain). My idea is to attach the EXE to this DLL chain, so the resource loading process will search in the EXE if the resource isn't present in the DLL.

Using the Code

To implement this tip, you need to add two private variables, m_hResDll and m_pExeModule, to your application class and override ExitInstance:

C++
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;
};

Now at the beginning of InitInstance, add this code:

C++
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
     }
}

Then add some clean-up code in ExitInstance:

C++
int CResFallbackApp::ExitInstance() 
{
    //cleaning
    if(m_hResDll != NULL)
    {
        delete m_pExeModule;
        FreeLibrary(m_hResDll);
    }
 
    return CWinApp::ExitInstance();
}

The trick consists of creating an object of type CDynLinkLibrary with the EXE module instance and resources handlers. The constructor of CDynLinkLibrary attaches itself to the DLL chain in the module state (trace into for more details). Now if you or MFC tries to load any kind of resource, it is searched first in the DLL if it is not found in the EXE. For example:

C++
void CResFallbackDlg::OnTest() 
{
    CString sMsg;
    sMsg.LoadString(IDS_TEST);
    AfxMessageBox(sMsg);

    sMsg.LoadString(IDS_TEST2);
    AfxMessageBox(sMsg);
}

If you want more details, trace into LoadString and you will arrive at the piece of code below from mfc\src\dllinit.cpp. Here, you can see how resource seeking works.

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

Points of Interest

This tip is useful for localization in this scenario:

  1. Build your EXE with embedded native resource.
  2. Extract resources with some specific localization tool.
  3. With the localization tool, translate resources and create a res-only DLL.
  4. Provide to your customer the EXE with his language DLL.
  5. When in the future you patch only the EXE without modifying resources, redistribute the EXE only.
  6. When in the future you add new resources to your EXE, if you add new resources with new IDs, you can deploy the EXE and the customer will see old things translated and new things in the default language while waiting for the translation process. When the new translation completes, you have to deploy only the DLL.

History

  • 16th May, 2007 -- Original version posted
  • 11th July, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.