天天看點

如何使用動态連結庫中的資源

近來在論壇上很有多文章問到如何使用DLL中的資源(包括對話框,圖示等)的問題,現在筆者就來就此問題談談,包含在DLL内部使用資源,DLL中使用其它DLL中的資源和在應用程式中使用資源。

       我們先以圖示為例說起(其它的資源與此圖示的加載原理大緻相同),我們要加載圖示,一般是調用AfxGetApp()->LoadIcon(…);下面是CWinApp::LoadIcon的實作(afxwin2.inl):

_AFXWIN_INLINE HICON CWinApp::LoadIcon(LPCTSTR lpszResourceName) const

{ return ::LoadIcon(AfxFindResourceHandle(lpszResourceName,

         RT_GROUP_ICON), lpszResourceName); }

_AFXWIN_INLINE HICON CWinApp::LoadIcon(UINT nIDResource) const

{ return ::LoadIcon(AfxFindResourceHandle(MAKEINTRESOURCE(nIDResource),

         RT_GROUP_ICON), MAKEINTRESOURCE(nIDResource)); }

       可以看到CWinApp::LoadIcon實際上調用了API .LoadIcon,下面是API LoadIcon的原型:

hInstance

[in] Handle to an instance of the module whose executable file contains the icon to be loaded. This parameter must be NULL when a standard icon is being loaded.

hInstance是我們要加載ICON的子產品執行個體,這個執行個體從何來,當然我們可以直接傳入DLL的執行個體,對于通過LoadLibrary動态加載的DLL我們可以很容易的得到其句柄,但對于我們直接連結的DLL得到其句柄則要費一番周折。可以看到CWinApp::LoadIcon是通過AfxFindResouceHandle找到此句柄的。下面是AfxFindResourceHandle的定義(afxwin.h):

#ifndef _AFXDLL

#define AfxFindResourceHandle(lpszResource, lpszType) AfxGetResourceHandle()

#else

HINSTANCE AFXAPI AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType);

#endif

       我們先讨論在靜态連結庫中使用MFC DLL的情況。可以看到,我們如果在靜态庫中使用MFC DLL的話(#ifndef _AFXDLL),實際上就是調用的AfxGetResourceHandle,MSDN中的說明是

AfxGetResourceHandle  

This function accesses the application’s resources directly by using the HINSTANCE handle returned, for example, in calls to the Windows function FindResource.

HINSTANCE AfxGetResourceHandle( );

Return Value

An HINSTANCE handle where the default resources of the application are loaded.

函數傳回的應用程式加載的預設資源的HINSTANCE句柄,HINSTANCE相當于HMODULE,也就是資源所在的子產品句柄。顯然在此,我們使用的是DLL中的資源,那麼我們就應該傳回此DLL中的HINSTANCE了,如果讓AfxGetResourceHandle傳回DLL的HINSTANCE呢?答案是通過AfxSetResourceHandle設定。MSDN中AfxSetResouceHandle的說明如下:

AfxSetResourceHandle

This function sets the HINSTANCE handle that determines where the default resources of the application are loaded.

void AfxSetResourceHandle(

HINSTANCE hInstResource );

Parameters

hInstResource

Specifies the instance or module handle to an .EXE or DLL file from which the application’s resources are loaded.

我們隻需将DLL的HINSTANCE傳入AfxSetResouceHanle就行了。如何得到DLL的HINSTANCE呢,我們可以通過DLL聲明一個接口HINSTANCE GetInstance獲得,也可以通過EnumProcessMoudules找到(詳細的過程見MSDN中EnumProcessModules的說明和示例)。

我們使用完DLL中的資源要使用EXE中的資源的資源怎麼辦呢?我們需要在使用完成後用AfxSetResource重新将資源子產品的句柄設定為原來的值,如果來保證在資源使用完成後完成這一個工作呢,即使在使用過程中發生異常了,為此我們利C++類的構造和析構機制建立了這一類:

class CLocalResource 

{

public:

         CLocalResource(HINSTANCE hInstance)

         m_hInstOld=AfxGetInstanceHandle();

         AfxSetInstanceHandle(hInstance);

}

         virtual ~CLocalResource()

         AfxSetInstanceHandle(m_hInstOld);

protected:

         HINSTANCE m_hInstOld;

};

         我們隻需在使用DLL的資源之前構造一個CLocalInstance就行了。

void CXXXX::LoadResouceFromDLL(HINSTANCE hInst,UINT nResID,…)

         CLocalResouce localRes(hInst);

         …

下面來讨論在動态庫中使用MFC DLL的情況(也就是定義了_AFXDLL的情況)

來看看AfxGetInstanceHandle ,AfxGetResourceHandle和AfxSetResouceHandle的實作(afxwin1.inl中):

_AFXWIN_INLINE HINSTANCE AFXAPI AfxGetInstanceHandle()

         { ASSERT(afxCurrentInstanceHandle != NULL);

         return afxCurrentInstanceHandle; }

_AFXWIN_INLINE HINSTANCE AFXAPI AfxGetResourceHandle()

{ ASSERT(afxCurrentResourceHandle != NULL);

return afxCurrentResourceHandle; }

_AFXWIN_INLINE void AFXAPI AfxSetResourceHandle(HINSTANCE hInstResource)

{ ASSERT(hInstResource != NULL); afxCurrentResourceHandle = hInstResource; }

實際上通路的就是afxCurrentInstanceHandle,afxCurrentResourceHandle。

/////////////////////////////////////////////////////////////////////////////

// Global functions for access to the one and only CWinApp

#define afxCurrentInstanceHandle    AfxGetModuleState()->m_hCurrentInstanceHandle

#define afxCurrentResourceHandle   AfxGetModuleState()->m_hCurrentResourceHandle

….

AFX_MODULE_STATE* AFXAPI AfxGetModuleState()

         _AFX_THREAD_STATE* pState = _afxThreadState;

         AFX_MODULE_STATE* pResult;

         if (pState->m_pModuleState != NULL)

         {

                   // thread state's module state serves as override

                   pResult = pState->m_pModuleState;

         }

         else

                   // otherwise, use global app state

                   pResult = _afxBaseModuleState.GetData();

         ASSERT(pResult != NULL);

         return pResult;

其中的_AFX_THREAD_STATE在此我們就不讨論了,有興趣的讀者可以閱讀(afxstat_.h和afxstate.cpp)。

         那AfxGetModuleState()->m_hCurrentResourceHandle又是在哪初始化的呢?

我們可以在appinit.cpp中的BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)看到

// set resource handles

         AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

         pModuleState->m_hCurrentInstanceHandle = hInstance;

         pModuleState->m_hCurrentResourceHandle = hInstance;

       和appinit.cpp中的void CWinApp::SetCurrentHandles()中看到

         AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();

         pModuleState->m_hCurrentInstanceHandle = m_hInstance;

         pModuleState->m_hCurrentResourceHandle = m_hInstance;

         CWinApp::SetCurrentHandles()也是由AfxWinInit調用的,那AfxWinInit又由誰調用呢?

我們可以在DllMain(dllinit.cpp和dllmodul.cpp,分别對應于MFC擴充DLL和MFC規則DLL)看到

(dllinit.cpp,MFC擴充DLL)

static AFX_EXTENSION_MODULE coreDLL;

extern "C"

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)

         if (dwReason == DLL_PROCESS_ATTACH)

                   …….

                   // initialize this DLL's extension module

                   VERIFY(AfxInitExtensionModule(coreDLL, hInstance));

#ifdef _AFX_OLE_IMPL

                   AfxWinInit(hInstance, NULL, _T(""), 0);

                   ….

                   // wire up this DLL into the resource chain

                   CDynLinkLibrary* pDLL = new CDynLinkLibrary(coreDLL, TRUE);

                   ASSERT(pDLL != NULL);

                   pDLL->m_factoryList.m_pHead = NULL;

         else if (dwReason == DLL_PROCESS_DETACH)

                   // cleanup module state for this process

                   AfxTermExtensionModule(coreDLL);

                   // cleanup module state in OLE private module state

                   AfxTermExtensionModule(coreDLL, TRUE);

可以看到在提供自動化支援時,将調用AfxWinInit(但MFC的DLL向導,對于擴充DLL卻不允許添加自動化支援)。

(在dllmodul.cpp中,MFC正常DLL)

#ifdef _AFXDLL

static AFX_EXTENSION_MODULE controlDLL;

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)

                   _AFX_THREAD_STATE* pState = AfxGetThreadState();

                   AFX_MODULE_STATE* pPrevModState = pState->m_pPrevModuleState;

                   // Initialize DLL's instance(/module) not the app's

                   if (!AfxWinInit(hInstance, NULL, _T(""), 0))

                   {

                            AfxWinTerm();

                            goto Cleanup;       // Init Failed

                   }

                   VERIFY(AfxInitExtensionModule(controlDLL, hInstance));

                   CDynLinkLibrary* pDLL; pDLL = new CDynLinkLibrary(controlDLL);

                   AfxInitLocalData(hInstance);

                   AfxTermExtensionModule(controlDLL, TRUE);

                   AfxTermLocalData(hInstance, TRUE);

         看到上面的代碼,其實與MFC擴充DLL的代碼大同小異,隻過是MFC正常DLL可以支援在靜态連結庫全用MFC DLL,相應的初始化/反初始化函數就變為了AfxInitLocalData/ AfxTermLocalData,在此我們不對在靜态連結庫中使用MFC DLL的情況作更多讨論。

以及在winmain.cpp中的int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow),在appmodul.cpp中我們可以看到入口函數的實作:

extern "C" int WINAPI

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

         LPTSTR lpCmdLine, int nCmdShow)

         // call shared/exported WinMain

         return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);

看看AfxInitExtensionModule , AfxTermExtensionModule CDynLinkLibrary的定義(afxdll_.h)和實作(在dllinit.cpp中)就明白了DllMain做了什麼:

BOOL AFXAPI AfxInitExtensionModule(AFX_EXTENSION_MODULE& state, HMODULE hModule)

         // only initialize once

         if (state.bInitialized)

                   AfxInitLocalData(hModule);

                   return TRUE;

         state.bInitialized = TRUE;

         // save the current HMODULE information for resource loading

         ASSERT(hModule != NULL);

         state.hModule = hModule;

         state.hResource = hModule;

         // save the start of the runtime class list

         state.pFirstSharedClass = pModuleState->m_classList.GetHead();

         pModuleState->m_classList.m_pHead = pModuleState->m_pClassInit;

#ifndef _AFX_NO_OLE_SUPPORT

         // save the start of the class factory list

         state.pFirstSharedFactory = pModuleState->m_factoryList.GetHead();

         pModuleState->m_factoryList.m_pHead = pModuleState->m_pFactoryInit;

         return TRUE;

void AFXAPI AfxTermExtensionModule(AFX_EXTENSION_MODULE& state, BOOL bAll)

         // make sure initialized

         if (!state.bInitialized)

                   return;

         // search for CDynLinkLibrary matching state.hModule and delete it

         ASSERT(state.hModule != NULL);

         AfxLockGlobals(CRIT_DYNLINKLIST);

         for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL; )

                   CDynLinkLibrary* pNextDLL = pDLL->m_pNextDLL;

                   if (bAll || pDLL->m_hModule == state.hModule)

                            delete pDLL;    // will unwire itself

                   pDLL = pNextDLL;

         AfxUnlockGlobals(CRIT_DYNLINKLIST);

         // delete any local storage attached to this module

         AfxTermLocalData(state.hModule, TRUE);

         // remove any entries from the CWnd message map cache

         AfxResetMsgCache();

class CDynLinkLibrary : public CCmdTarget

         DECLARE_DYNAMIC(CDynLinkLibrary)

// Constructor

         explicit CDynLinkLibrary(AFX_EXTENSION_MODULE& state, BOOL bSystem = FALSE);

         CDynLinkLibrary(HINSTANCE hModule, HINSTANCE hResource);

// Attributes

         HMODULE m_hModule;

         HMODULE m_hResource;                // for shared resources

         ….

         BOOL m_bSystem;                     // TRUE only for MFC DLLs

// Implementation

         CDynLinkLibrary* m_pNextDLL;        // simple singly linked list

         virtual ~CDynLinkLibrary();

CDynLinkLibrary::CDynLinkLibrary(AFX_EXTENSION_MODULE& state, BOOL bSystem)

         // copy info from AFX_EXTENSION_MODULE struct

         m_hModule = state.hModule;

         m_hResource = state.hResource;

         m_classList.m_pHead = state.pFirstSharedClass;

         m_bSystem = bSystem;

// insert at the head of the list (extensions will go in front of core DLL)

         DEBUG_ONLY(m_pNextDLL = NULL);

         m_pModuleState->m_libraryList.AddHead(this);

CDynLinkLibrary::CDynLinkLibrary(HINSTANCE hModule, HINSTANCE hResource)

         m_hModule = hModule;

         m_hResource = hResource;

         m_classList.m_pHead = NULL;

         m_bSystem = FALSE;

         // insert at the head of the list (extensions will go in front of core DLL)

CDynLinkLibrary::~CDynLinkLibrary()

         // remove this frame window from the list of frame windows

         m_pModuleState->m_libraryList.Remove(this);

       由此我們可以看出DLL初始化時先調AfxInitExtensionModule是為了構造一個AFX_EXTENSION_MODULE,然後将它插入插入一個AFX_MODULE_STATE連結清單中,同時将目前DLL的HINSTANCE插入此AFX_MODULE_STATE的CDynLinkLibrary連結清單中,以便稍後講到的AfxFindResourceHandle找到。DLL從記憶體移出時就從此連結清單中删除自己。

讓我們來看看AfxFindResourceHandle的實作(dllinit.cpp):

HINSTANCE AFXAPI AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType)

         ASSERT(lpszName != NULL);

         ASSERT(lpszType != NULL);

         HINSTANCE hInst;

         // first check the main module state

         if (!pModuleState->m_bSystem)

                   hInst = AfxGetResourceHandle();

                   if (::FindResource(hInst, lpszName, lpszType) != NULL)

                            return hInst;

         // check for non-system DLLs in proper order

         CDynLinkLibrary* pDLL;

         for (pDLL = pModuleState->m_libraryList; pDLL != NULL;

                   pDLL = pDLL->m_pNextDLL)

                   if (!pDLL->m_bSystem && pDLL->m_hResource != NULL &&

                            ::FindResource(pDLL->m_hResource, lpszName, lpszType) != NULL)

                            // found it in a DLL

                            AfxUnlockGlobals(CRIT_DYNLINKLIST);

                            return pDLL->m_hResource;

         // check language specific resource next

         hInst = pModuleState->m_appLangDLL;

         if (hInst != NULL && ::FindResource(hInst, lpszName, lpszType) != NULL)

                   return hInst;

         // check the main system module state

         if (pModuleState->m_bSystem)

         // check for system DLLs in proper order

         for (pDLL = pModuleState->m_libraryList; pDLL != NULL; pDLL = pDLL->m_pNextDLL)

                   if (pDLL->m_bSystem && pDLL->m_hResource != NULL &&

         // if failed to find resource, return application resource

         return AfxGetResourceHandle();

上面的代碼首先檢查主子產品的狀态,通過傳回的是個AFX_MODULE_STATE的類對象指針. AFX_MODULE_STATE又是個什麼呢?下面看看AFX_MODULE_STATE的定義(afxstat_.h中,它的實作在afxstate.cpp中,有興趣的讀者可以讀讀):

// AFX_MODULE_STATE (global data for a module)

class AFX_MODULE_STATE : public CNoTrackObject

         AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,

                   BOOL bSystem = FALSE);

         explicit AFX_MODULE_STATE(BOOL bDLL);

         ~AFX_MODULE_STATE();

         CWinApp* m_pCurrentWinApp;

         HINSTANCE m_hCurrentInstanceHandle;

         HINSTANCE m_hCurrentResourceHandle;

         LPCTSTR m_lpszCurrentAppName;

         BYTE m_bDLL;    // TRUE if module is a DLL, FALSE if it is an EXE

         BYTE m_bSystem; // TRUE if module is a "system" module, FALSE if not

         // CDynLinkLibrary objects (for resource chain)

         CTypedSimpleList<CDynLinkLibrary*> m_libraryList;

         // special case for MFC71XXX.DLL (localized MFC resources)

         HINSTANCE m_appLangDLL;

我們來看看我們關心的屬性

CWinApp* m_pCurrentWinApp;

HINSTANCE m_hCurrentInstanceHandle;

HINSTANCE m_hCurrentResourceHandle;

LPCTSTR m_lpszCurrentAppName;

BYTE m_bDLL;    // TRUE if module is a DLL, FALSE if it is an EXE

BYTE m_bSystem; // TRUE if module is a "system" module, FALSE if not

// CDynLinkLibrary objects (for resource chain)

CTypedSimpleList<CDynLinkLibrary*> m_libraryList;

// special case for MFC71XXX.DLL (localized MFC resources)

HINSTANCE m_appLangDLL;

看了相關注釋,也就明白什麼意思了,在此就不多解釋了。

如果目前主子產品是系統子產品(通過m_bSystem辨別),并且從目前子產品中找到了相應的資源就傳回此子產品的HINSTANCE。接下來目前子產品所載的DLL連結清單中的所有使用者子產品中查找,本地化的MFC資源DLL中查找,系統DLL中查找。如果都找不到則從EXE(或者通過AfxSetResouceHandle中設定的HINSTANCE)中查找。

如果不考慮DLL連結清單,則需要我們每次使用DLL中的資源前調用AfxSetResouceHandle,使用後再調用AfxSetResourceHandle,就同前面講的在靜态連結庫中使用MFC DLL一樣。麻不麻煩?

怎麼解決呢?解決方法是用資源DLL的HINSTANCE構造一CDynLinkLibrary對象插入主子產品(EXE)的AFX_MODULE_STATE的m_libraryList中。怎麼做?

看了上面DllMain的實作代碼,我們可以照着做。

static AFX_EXTENSION_MODULE ResouceDLL = { NULL, NULL }

BOOL CXApp::InitInstance()

// Get the resource DLL instance

         if (!AfxInitExtensionModule(ResouceDLL, hResourceModule))

                  return 0;

new CDynLinkLibrary(ResouceDLL);  

int CGenericMFCApp::ExitInstance()

AfxTermExtensionModule(ResouceDLL);

         return CWinApp::ExitInstance();

這樣我們就可以在應用程式中安全的使用DLL中的資源了,就好比EXE中的一樣。對于我們自己編寫入口函數的DLL,如果其中涉及到資源的話,我們可以參考上述的MFC正常DLL的實作。

對于我們在ActiveX和一些DLL中的AFX_MANAGE_STATE(AfxGetStaticModuleState());我們又怎麼了解呢?看看afxstat_.h:

class _AFX_THREAD_STATE;

struct AFX_MAINTAIN_STATE2

         explicit AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pModuleState);

         ~AFX_MAINTAIN_STATE2();

         AFX_MODULE_STATE* m_pPrevModuleState;

         _AFX_THREAD_STATE* m_pThreadState;

#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE2 _ctlState(p);

#else  // _AFXDLL

#define AFX_MANAGE_STATE(p)

#endif //!_AFXDLL

對于在靜态庫中使用MFC DLL時它就相當于不存在,反之,它構造一個AFX_MAINTAIN_STATE2對象,在afxstate.cpp中

AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState)

         m_pThreadState = _afxThreadState;

         m_pPrevModuleState = m_pThreadState->m_pModuleState;

         m_pThreadState->m_pModuleState = pNewState;

#endif //_AFXDLL

,

再看dllmodul.cpp中有關AfxGetStaticModuleState的實作:

static _AFX_DLL_MODULE_STATE afxModuleState;

AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState()

         AFX_MODULE_STATE* pModuleState = &afxModuleState;

         return pModuleState;

(MFC中太多的全局變量的确不雅!)

可以看到通過構造一個AFX_MAINTAIN_STATE2對象(再結合AfxGetResouceHandle的實作),就使AfxFindResourceHandle傳回的是ActiveX控件(實際上也是一個正常DLL)或DLL的HINSTANE了,這樣AfxFindResourceHandle就不必須在DLL連結清單中查找了,但缺點是每次使用資源(或響應消息)時都要構造一個AFX_MAINTAIN_STATE2對象,并且,而且使用這種方法在此DLL中無法使用其它DLL中的資源或在EXE中使用DLL中的資源(在不使用AfxSetResourceHandle/AfxGetResourceHandle的前提下)。當然,我們這兒關心的隻是資源的使用,如果涉及到其它,包括事件響應或多個UI線程時,AFX_MODULE_STATE還是不能少的。