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
:
class CResFallbackApp : public CWinApp
{
public:
CResFallbackApp();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
DECLARE_MESSAGE_MAP()
private:
HMODULE m_hResDll;
CDynLinkLibrary* m_pExeModule;
};
Now at the beginning of InitInstance
, add this code:
BOOL CResFallbackApp::InitInstance()
{
m_hResDll = LoadLibrary(_T("ResDll.dll"));
if(m_hResDll != NULL)
{
m_pExeModule =
new CDynLinkLibrary(AfxGetInstanceHandle(),
AfxGetResourceHandle());
AfxSetResourceHandle(m_hResDll);
}
}
Then add some clean-up code in ExitInstance
:
int CResFallbackApp::ExitInstance()
{
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;
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)
{
return nLen;
}
}
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:
- Build your EXE with embedded native resource.
- Extract resources with some specific localization tool.
- With the localization tool, translate resources and create a res-only DLL.
- Provide to your customer the EXE with his language DLL.
- When in the future you patch only the EXE without modifying resources, redistribute the EXE only.
- 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.