Click here to Skip to main content
Click here to Skip to main content

MFC resources fallback

, 11 Jul 2007
Rate this:
Please Sign up or sign in to vote.
A little 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 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 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_hResDlland m_pExeModule, to your application class and override 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;
};

Now at the beginning of InitInstance, add this code:

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:

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:

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.

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

  • 16 May, 2007 -- Original version posted
  • 11 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

About the Author

Giuseppe Marazzi
Web Developer
Italy Italy
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberJunfengGuo22-Apr-14 16:47 
GeneralThanks PinmemberMatt Clarkson23-Oct-09 0:45 
GeneralMFC static linking Pinmembersashoalm13-May-09 22:10 
GeneralGood work! PinmvpHans Dietrich11-Jul-07 11:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 11 Jul 2007
Article Copyright 2007 by Giuseppe Marazzi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid