第九讲 MFC的状态
MFC定义了多种状态信息,这里要介绍的是模块状态、进程状态、线程状态。这些状态可以组合在一起,例如MFC句柄映射就是模块和线程局部有效的,属于模块-线程状态的一部分。
- 模块状态
这里模块的含义是:一个可执行的程序或者一个使用MFC DLL的DLL,比如一个OLE控件就是一个模块。
一个应用程序的每一个模块都有一个状态,模块状态包括这样一些信息:用来加载资源的 Windows实例句柄、指向当前CWinApp或者CWinThread对象的指针、OLE模块的引用计数、Windows对象与相应的MFC对象之间的映射。只有单一模块的应用程序的状态如图9-1所示。
m_pModuleState 指针是线程对象的成员变量,指向当前模块状态信息(一个AFX_MODULE_STATE结构变量)。当程序运行进入某个特定的模块时,必须保证当前使用的模块状态是有效的模块状态──是这个特定模块的模块状态。所以,每个线程对象都有一个指针指向有效的模块状态,每当进入某个模块时都要使它指向有效模块状态,这对维护应用程序全局状态和每个模块状态的完整性来说是非常重要的。为了作到这一点,每个模块的所有入口点有责任实现模块状态的切换。模块的入口点包括:DLL的输出函数;OLE/COM界面的成员函数;窗口过程。
在讲述窗口过程和动态链接到MFC DLL的规则DLL时,曾提到了语句AFX_MANAGE_STATE(AfxGetStaticModuleState( )),它就是用来在入口点切换模块状态的。其实现机制将在后面9.4.1节讲解。
多个模块状态之间切换的示意图如图9-2所示。
图9-2中,m_pModuleState总是指向当前模块的状态。
- 模块、进程和线程状态的数据结构
MFC定义了一系列类或者结构,通过它们来实现状态信息的管理。这一节将描述它们的关系,并逐一解释它们的数据结构、成员函数等。
- 层次关系
图9-3显示了线程状态、模块状态、线程-模块状态等几个类的层次关系:
线程状态用类_AFX_THREAD_STATE描述,模块状态用类AFX_MODULE_STATE描述,模块-线程状态用类AFX_MODULE_THREAD_STATE描述。这些类从类CNoTrackObject派生。进程状态类用_AFX_BASE_MODULE_STATE描述,从模块状态类AFX_MODULE_STATE派生。进程状态是了一个可以独立执行的MFC应用程序的模块状态。还有其他状态如DLL的模块状态等也从模块状态类_AFX_MODULE_STATE派生。
图9-4显示了这几个类的交互关系。
从图9-4可以看出:首先,每个线程有一个线程状态,线程状态的指针m_pModuleState和m_pPreModuleState分别指向线程当前运行模块的状态或前一运行模块的状态;其次,每一个模块状态都有一个线程局部的变量用来存储模块-线程状态。
下面各小节列出状态信息管理所涉及的各个类的定义。
- CNoTrackObject类
在图9-3中, CnoTrackObject是根类,所有状态类都是从它这里派生的,其定义如下:
class CNoTrackObject
{
public:
void* PASCAL operator new(size_t nSize);
void PASCAL operator delete(void*);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
void* PASCAL operator new(size_t nSize, LPCSTR, int);
#endif
virtual ~CNoTrackObject() { }
};
该类的析构函数是虚拟函数;而且,CNoTrackObject重载new操作符用来分配内存,重载delete操作符号用来释放内存,内部通过LocalAlloc/LocalFree提供了一个低层内存分配器(Low_level alloctor)。
- AFX_MODULE_STATE类
AFX_MODULE_STATE类的定义如下:
// AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
{
public:
#ifdef _AFXDLL
AFX_MODULE_STATE(BOOL bDLL,WNDPROC pfnAfxWndProc,
DWORD dwVersion);
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc,
DWORD dwVersion,BOOL bSystem);
#else
AFX_MODULE_STATE(BOOL bDLL);
#endif
~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
//TRUE if module is a "system" module, FALSE if not
BYTE m_bSystem;
BYTE m_bReserved[2]; // padding
//Runtime class data:
#ifdef _AFXDLL
CRuntimeClass* m_pClassInit;
#endif
CTypedSimpleList<CRuntimeClass*> m_classList;
// OLE object factories
#ifndef _AFX_NO_OLE_SUPPORT
#ifdef _AFXDLL
COleObjectFactory* m_pFactoryInit;
#endif
CTypedSimpleList<COleObjectFactory*> m_factoryList;
#endif
// number of locked OLE objects
long m_nObjectCount;
BOOL m_bUserCtrl;
// AfxRegisterClass and AfxRegisterWndClass data
TCHAR m_szUnregisterList[4096];
#ifdef _AFXDLL
WNDPROC m_pfnAfxWndProc;
DWORD m_dwVersion; // version that module linked against
#endif
// variables related to a given process in a module
// (used to be AFX_MODULE_PROCESS_STATE)
#ifdef _AFX_OLD_EXCEPTIONS
// exceptions
AFX_TERM_PROC m_pfnTerminate;
#endif
void (PASCAL *m_pfnFilterToolTipMessage)(MSG*, CWnd*);
#ifdef _AFXDLL
// CDynLinkLibrary objects (for resource chain)
CTypedSimpleList<CDynLinkLibrary*> m_libraryList;
// special case for MFCxxLOC.DLL (localized MFC resources)
HINSTANCE m_appLangDLL;
#endif
#ifndef _AFX_NO_OCC_SUPPORT
// OLE control container manager
COccManager* m_pOccManager;
// locked OLE controls
CTypedSimpleList<COleControlLock*> m_lockList;
#endif
#ifndef _AFX_NO_DAO_SUPPORT
_AFX_DAO_STATE* m_pDaoState;
#endif
#ifndef _AFX_NO_OLE_SUPPORT
// Type library caches
CTypeLibCache m_typeLibCache;
CMapPtrToPtr* m_pTypeLibCacheMap;
#endif
// define thread local portions of module state
THREAD_LOCAL(AFX_MODULE_THREAD_STATE, m_thread)
};
从上面的定义可以看出,模块状态信息分为如下几类:
模块信息,资源信息,对动态链接到MFC DLL的支持信息,对扩展DLL的支持信息,对DAO的支持信息,对OLE的支持信息,模块-线程状态信息。
模块信息包括实例句柄、资源句柄、应用程序名称、指向应用程序的指针、是否为DLL模块、模块注册的窗口类,等等。其中,成员变量m_fRegisteredClasses、m_szUnregisterList曾经在讨论MFC的窗口注册时提到过它们的用处。
在“#ifdef _AFXDLL…#endif”条件编译范围内的是支持MFC DLL的数据;
在“#ifndef _AFX_NO_OLE_SUPPOR…#endif”条件编译范围内的是支持OLE的数据;
在“#ifndef _AFX_NO_OCC_SUPPOR…#endif”条件编译范围内的是支持OLE控件的数据;
在“#ifndef _AFX_NO_DAO_SUPPORT”条件编译范围内的是支持DAO的数据。
THREAD_LOCAL宏定义了线程私有的模块-线程类型的变量m_thread。
- _AFX_BASE_MODULE_STATE
该类定义如下:
class _AFX_BASE_MODULE_STATE : public AFX_MODULE_STATE
{
public:
#ifdef _AFXDLL
_AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE,
AfxWndProcBase, _MFC_VER)
#else
_AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE)
#endif
{ }
};
由定义可见,该类没有在_AFX_MODULE_STATE类的基础上增加数据。它类用来实现一个MFC应用程序模块的状态信息。
- _AFX_THREAD_STATE
该类定义如下:
class _AFX_THREAD_STATE : public CNoTrackObject
{
public:
_AFX_THREAD_STATE();
virtual ~_AFX_THREAD_STATE();
// override for m_pModuleState in _AFX_APP_STATE
AFX_MODULE_STATE* m_pModuleState;
AFX_MODULE_STATE* m_pPrevModuleState;
// memory safety pool for temp maps
void* m_pSafetyPoolBuffer; // current buffer
// thread local exception context
AFX_EXCEPTION_CONTEXT m_exceptionContext;
// CWnd create, gray dialog hook, and other hook data
CWnd* m_pWndInit;
CWnd* m_pAlternateWndInit; // special case commdlg hooking
DWORD m_dwPropStyle;
DWORD m_dwPropExStyle;
HWND m_hWndInit;
BOOL m_bDlgCreate;
HHOOK m_hHookOldCbtFilter;
HHOOK m_hHookOldMsgFilter;
// other CWnd modal data
MSG m_lastSentMsg; // see CWnd::WindowProc
HWND m_hTrackingWindow; // see CWnd::TrackPopupMenu
HMENU m_hTrackingMenu;
TCHAR m_szTempClassName[96]; // see AfxRegisterWndClass
HWND m_hLockoutNotifyWindow; // see CWnd::OnCommand
BOOL m_bInMsgFilter;
// other framework modal data
CView* m_pRoutingView; // see CCmdTarget::GetRoutingView
CFrameWnd*m_pRoutingFrame;//see CmdTarget::GetRoutingFrame
// MFC/DB thread-local data
BOOL m_bWaitForDataSource;
// common controls thread state
CToolTipCtrl* m_pToolTip;
CWnd* m_pLastHit; // last window to own tooltip
int m_nLastHit; // last hittest code
TOOLINFO m_lastInfo; // last TOOLINFO structure
int m_nLastStatus; // last flyby status message
CControlBar* m_pLastStatus; // last flyby status control bar
// OLE control thread-local data
CWnd* m_pWndPark; // "parking space" window
long m_nCtrlRef; // reference count on parking window
BOOL m_bNeedTerm; // TRUE if OleUninitialize needs to be called
};
从定义可以看出,线程状态的成员数据分如下几类:
指向模块状态信息的指针,支持本线程的窗口创建的变量,MFC命令和消息处理用到的信息,处理工具条提示信息(tooltip)的结构,和处理OLE相关的变量,等等。
- AFX_MODULE_THREAD_STATE
该类定义如下:
// AFX_MODULE_THREAD_STATE (local to thread *and* module)
class AFX_MODULE_THREAD_STATE : public CNoTrackObject
{
public:
AFX_MODULE_THREAD_STATE();
virtual ~AFX_MODULE_THREAD_STATE();
// current CWinThread pointer
CWinThread* m_pCurrentWinThread;
// list of CFrameWnd objects for thread
CTypedSimpleList<CFrameWnd*> m_frameList;
// temporary/permanent map state
DWORD m_nTempMapLock; // if not 0, temp maps locked
CHandleMap* m_pmapHWND;
CHandleMap* m_pmapHMENU;
CHandleMap* m_pmapHDC;
CHandleMap* m_pmapHGDIOBJ;
CHandleMap* m_pmapHimageLIST;
// thread-local MFC new handler (separate from C-runtime)
_PNH m_pfnNewHandler;
#ifndef _AFX_NO_SOCKET_SUPPORT
// WinSock specific thread state
HWND m_hSocketWindow;
CMapPtrToPtr m_mapSocketHandle;
CMapPtrToPtr m_mapDeadSockets;
CPtrList m_listSocketNotifications;
#endif
};
模块-线程状态的数据成员主要有:
指向当前线程对象(CWinThread对象)的指针m_pCurrentWinThread;
当前线程的框架窗口对象(CFrameWnd对象)列表m_frameList(边框窗口在创建时(见图5-8)把自身添加到m-frameList中,销毁时则删除掉,通过列表m_frameList可以遍历模块所有的边框窗口);
new操作的例外处理函数m_pfnNewHandler;
临时映射锁定标识m_nTempMapLock,防止并发修改临时映射。
系列Windows对象-MFC对象的映射,如m_pmapHWND等。
这些数据成员都是线程和模块私有的。
下一节讨论MFC如何通过上述这些类来实现其状态的管理。
- 线程局部存储机制和状态的实现
MFC实现线程、模块或者线程-模块私有状态的基础是MFC的线程局部存储机制。MFC定义了CThreadSlotData类型的全局变量_afxThreadData来为进程的线程分配线程局部存储空间:
CThreadSlotData* _afxThreadData;
在此基础上,MFC定义了变量_afxThreadState来管理线程状态,定义了变量_afxBaseModuleState来管理进程状态。
THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)
对于每个THREAD_LOCAL宏定义的变量,进程的每个线程都有自己独立的拷贝,这个变量在不同的线程里头可以有不同的取值。
对于每个PROCESS_LOCAL宏定义的变量,每个进程都有自己独立的拷贝,这个变量在不同的进程里头可以有不同的取值。
分别解释这三个变量。
- CThreadSlotData和_afxThreadData
- CThreadSlotData的定义
以Win32线程局部存储机制为基础,MFC设计了类CThreadSlotData来提供管理线程局部存储的功能,MFC应用程序使用该类的对象──全局变量_afxThreadData来管理本进程的线程局部存储。CThreadSlotData类的定义如下:
class CThreadSlotData
{
public:
CThreadSlotData();
//Operations
int AllocSlot();
void FreeSlot(int nSlot);
void* GetValue(int nSlot);
void SetValue(int nSlot, void* pValue);
// delete all values in process/thread
void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);
// assign instance handle to just constructed slots
void AssignInstance(HINSTANCE hInst);
// Implementation
DWORD m_tlsIndex;// used to access system thread-local storage
int m_nAlloc; // number of slots allocated (in UINTs)
int m_nRover; // (optimization) for quick finding of free slots
int m_nMax; // size of slot table below (in bits)
CSlotData* m_pSlotData; // state of each slot (allocated or not)
//list of CThreadData structures
CTypedSimpleList<CThreadData*> m_list;
CRITICAL_SECTION m_sect;
// special version for threads only!
void* GetThreadValue(int nSlot);
void* PASCAL operator new(size_t, void* p){ return p; }
void DeleteValues(CThreadData* pData, HINSTANCE hInst);
~CThreadSlotData();
};
通过TLS索引m_tlsIndex,CThreadSlotData对象(_afxThreadData)为每一个线程分配一个线程私有的存储空间并管理该空间。它把这个空间划分为若干个槽,每个槽放一个线程私有的数据指针,这样每个线程就可以存放任意个线程私有的数据指针。
- CThreadSlotData的一些数据成员
在CThreadSlotData类的定义中所涉及的类或者结构定义如下:
(1)m_sect
m_sect是一个关键段变量,在_afxThreadData创建时初始化。因为_afxThreadData是一个全局变量,所以必须通过m_sect来同步多个线程对该变量的并发访问。
(2)m_nAlloc和m_pSlotData
m_nAlloc表示已经分配槽的数目,它代表了线程局部变量的个数。每一个线程局部变量都对应一个槽,每个槽对应一个线程局部变量。槽使用CSlotData类来管理。
CSlotData的定义如下:
struct CSlotData{
DWORD dwFlags; // slot flags (allocated/not allocated)
HINSTANCE hInst; // module which owns this slot
};
该结构用来描述槽的使用:
域dwFlags表示槽的状态,即被占用或者没有;
域hInst表示使用该槽的模块的句柄。
m_pSlotData表示一个CSlotData类型的数组,用来描述各个槽。该数组通过成员函数AllocSlot和FreeSlot来动态地管理,见图9-6。
(3)m_list
先讨论CThreadData 类。CThreadData定义如下:
struct CThreadData : public CNoTrackObject{
CThreadData* pNext; // required to be member of CSimpleList
int nCount; // current size of pData
LPVOID* pData; // actual thread local data (indexed by nSlot)
};
该结构用来描述CThreadSlotData为每个线程管理的线程局部空间:
域pNext把各个线程的CThreadData项目链接成一个表,即把各个线程的线程私有空间链接起来;
域nCount表示域pData的尺寸,即存储了多少个线程私有数据;
pData表示一个LPVOID类型的数组,数组中的每一个元素保存一个指针,即线程私有数据指针,该指针指向一个在堆中分配的真正存储线程私有数据的地址。数组元素的个数和槽的个数相同,每个线程局部变量(THREAD_LOCAL定义的变量)都有一个对应的槽号,用该槽号作为下标来引用pData。
m_list表示一个CThreadData类型的指针数组,数组中的各项指向各个线程的线程私有空间,每个线程在数组中都有一个对应项。该数组通过GetValue、SetValue、DeleteValues等成员函数来管理,见图9-6。
- _afxThreadData
_afxThreadData仅仅定义为一个CThreadSlotData类型的指针,所指对象在第一次被引用时创建,在此之前该指针为空。下文_afxThreadData含义是它所指的对象。图9-5、9-6图解了MFC的线程局部存储机制的实现。
图9-5表示_afxTheadData使用TLS技术负责给进程分配一个TLS索引,然后使用TLS索引为进程的每一个线程分配线程局部存储空间。
图9-6表示每个线程的的局部存储空间可以分多个槽,每个槽可以放一个线程私有的数据指针。_afxThreadData负责给线程局部变量分配槽号并根据槽号存取数据。图的左半部分描述了管理槽的m_pSlotData及类CSlotData的结构,右半部分描述了管理MFC线程私有空间的m_list及类CThreadData的结构。
结合图9-6,对MFC线程局部存储机制总结如下:
- 每个线程局部变量(宏THREAD_LOCAL定义)占用一个槽,并有一个槽号。。
- 每个线程都有自己的MFC局部存储空间(下文多次使用“线程的MFC局部存储空间”,表示和此处相同的概念)。
- 通过TLS索引得到的是一个指针P1,它指向线程的MFC局部存储空间。
- 通过指针P1和线程局部变量在空间所占用的槽号,得到该槽所存储的线程私有的数据指针,即真正的线程私有数据的地址P2;
- 从地址P2得到数据D。
这个过程相当于几重间接寻址:先得到TLS线程私有数据指针,从TLS线程私有数据指针得到线程的MFC线程局部存储空间,再从MFC局部存储空间的对应槽得到一个线程私有的数据指针,从该指针得到最终的线程私有数据。如果没有这种机制,使用Win32 TLS只要一次间接寻址:得到TLS线程私有数据指针,从该指针得到最终的线程私有数据。
- 线程状态_afxThreadState
从上一节知道了MFC的线程局部存储机制。但有一点还不清楚,即某个线程局部变量所占用的槽号是怎么保存的呢?关于这点可从线程局部的线程状态变量_afxThreadState的实现来分析MFC的作法。变量_afxThreadState的定义如下:
THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)
THREAD_LOCAL 是一个宏,THREAD_LOCAL(class_name, ident_name)宏展开后如下:
AFX_DATADEF CThreadLocal<class_name> ident_name;
这里,CThreadLocal是一个类模板,从CThreadLocalObject类继承。
CThreadLocalObject和CThreadLocal的定义如下:
class CThreadLocalObject
{
public:
// Attributes
CNoTrackObject* GetData(CNoTrackObject* (AFXAPI*
pfnCreateObject)());
CNoTrackObject* GetDataNA();
// Implementation
int m_nSlot;
~CThreadLocalObject();
};
CThreadLocalObject用来帮助实现一个线程局部的变量。成员变量m_nSlot表示线程局部变量在MFC线程局部存储空间中占据的槽号。GetDataNA用来返回变量的值。GetData也可以返回变量的值,但是如果发现还没有给该变量分配槽号(m_slot=0),则给它分配槽号并在线程的MFC局部空间为之分配一个槽;如果在槽m_nSlot还没有数据(为空),则调用参数pfnCreateObject传递的函数创建一个数据项,并保存到槽m_nSlot中。
template<class TYPE>
class CThreadLocal : public CThreadLocalObject
{
// Attributes
public:
inline TYPE* GetData()
{
TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);
ASSERT(pData != NULL);
return pData;
}
inline TYPE* GetDataNA()
{
TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA();
return pData;
}
inline operator TYPE*()
{ return GetData(); }
inline TYPE* operator->()
{ return GetData(); }
// Implementation
public:
static CNoTrackObject* AFXAPI CreateObject()
{ return new TYPE; }
};
CThreadLocal模板用来声明任意类型的线程私有的变量,因为通过模板可以自动的正确的转化(cast)指针类型。程序员可以使用它来实现自己的线程局部变量,正如MFC实现线程局部的线程状态变量和模块-线程变量一样。
CThrealLocal的成员函数CreateObject用来创建动态的指定类型的对象。成员函数GetData调用了基类CThreadLocalObject的同名函数,并且把CreateObject函数的地址作为参数传递给它。
另外,CThreadLocal模板重载了操作符号“*”、“->”,这样编译器将自动地进行有关类型转换,例如:
_AFX_THREAD_STATE *pStata = _afxThreadState
是可以被编译器接收的。
现在回头来看_afxThreadState的定义:
从以上分析可以知道,THREAD_LOCAL(class_name, ident_name)定义的结果并没有产生一个名为ident_name的class_name类的实例,而是产生一个CThreadLocal模板类(确切地说,是其派生类)的实例,m_nSlot初始化为0。所以,_afxThreadState实质上是一个CThreadLocal模板类的全局变量。每一个线程局部变量都对应了一个全局的CThreadLoacl模板类对象,模板对象的m_nSlot记录了线程局部变量对象的槽号。
- 进程模块状态afxBaseModuleState
进程模块状态定义如下:
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)
表示它是一个_AFX_BASE_MODULE_STATE类型的进程局部(process local)的变量。
进程局部变量的实现方法主要是为了用于Win32s下。在Win32s下,一个DLL模块如果被多个应用程序调用,它将让这些程序共享它的全局数据。为了DLL的全局数据一个进程有一份独立的拷贝,MFC设计了进程私有的实现方法,实际上就是在进程的堆(Heap)中分配全局数据的内存空间。
在Win32下,DLL模块的数据和代码被映射到调用进程的虚拟空间,也就是说,DLL定义的全局变量是进程私有的;所以进程局部变量的实现并不为Win32所关心。但是,不是说afxBaseModuleState不重要,仅仅是采用PROCESS_LOCAL技术声明它是进程局部变量不是很必要了。PROCESS_LOCAL(class_name, ident_name)宏展开后如下:
AFX_DATADEF CProcessLocal<class_name> ident_name;
这里,CProcessLocal是一个类模板,从CProcessLocalObject类继承。
CProcessLocalObject和CProcessLocal的定义如下:
class CProcessLocalObject
{
public:
// Attributes
CNoTrackObject* GetData(CNoTrackObject* (AFXAPI*
pfnCreateObject)());
// Implementation
CNoTrackObject* volatile m_pObject;
~CProcessLocalObject();
};
template<class TYPE>
class CProcessLocal : public CProcessLocalObject
{
// Attributes
public:
inline TYPE* GetData()
{
TYPE* pData =(TYPE*)CProcessLocalObject::GetData(&CreateObject);
ASSERT(pData != NULL);
return pData;
}
inline TYPE* GetDataNA()
{ return (TYPE*)m_pObject; }
inline operator TYPE*()
{ return GetData(); }
inline TYPE* operator->()
{ return GetData(); }
// Implementation
public:
static CNoTrackObject* AFXAPI CreateObject()
{ return new TYPE; }
};
类似于线程局部对象,每一个进程局部变量都有一个对应的全局CProcessLocal模板对象。
- 状态对象的创建
回顾前一节的三个定义:
CThreadSlotData* _afxThreadData;
THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)
第一个仅仅定义了一个指针;第二和第三个定义了一个模板类的实例。相应的CThreadSlotData对象(全局)、_AFX_THREAD_STATE对象(线程局部)以及_AFX_BASE_MODULE_STATE对象(进程局部)并没有创建。当然,模块状态对象的成员模块-线程对象也没有被创建。这些对象要到第一次被访问时,才会被创建,这样做会提高加载DLL的速度。
下面以一个动态链接到MFC DLL的单模块应用程序为例,说明这些对象的创建过程。
当第一次访问状态信息时,比如使用 AfxGetModuleState得到模块状态,导致系列创建过程的开始,如图9-7所示。
首先分析语句pState=_afxThreadState。如果_afxThreadData、线程状态和模块状态还没有创建,该语句可以导致这些数据的创建。
pState声明为CNoTrackObject对象的指针,_afxThreadState声明为一个模板CThreadLocal的实例,pState=_afxThreadData为什么可以通过编译器的检查呢?因为CThreadLocal模板重载了操作符“”*”和“->”,这两个运算返回CNoTrackObject类型的对象。回顾3.2节CThreadLocalObject、CThreadLocal的定义,这两个操作符运算到最后都是调用CThreadLocalObject的成员函数GetData。
- 创建_afxThreadData所指对象和线程状态
CThreadLocalObject::GetData用来获取线程局部变量(这个例子中是线程状态)的值,其参数用来创建动态的线程局部变量。图9-7的上面的虚线框表示其流程:
它检查成员变量m_nSlot是否等于0(线程局部变量是否曾经被分配了MFC线程私有空间槽位),检查全局变量_afxTheadData指针是否为空。如果_afxThreadData空,则创建一个CThreadSlotData类对象,让_afxThreadData指向它,这样本程序的MFC线程局部存储的管理者被创建。如果m_nSlot等于0,则让_afxThreadDtata调用AllocSlot分配一个槽位并把槽号保存在m_nSlot中。
得到了线程局部变量(线程状态)所占用的槽位后,委托_afxThreadData调用GetThreadValue(m_nSlot)得到线程状态值(指针)。如果结果非空,则返回它;如果结果是NULL,则表明该线程状态还没有被创建,于是使用参数创建一个动态的线程状态,并使用SetValue把其指针保存在槽m_nSlot中,返回该指针。
- 创建模块状态
得到了线程状态的值后,通过它得到模块状态m_pModuleState。如果m_pModuleState为空,表明该线程状态是才创建的,其许多成员变量还没有赋值,程序的进程模块状态还没有被创建。于是调用函数_afxBaseModule.GetData,导致进程模块状态被创建。
图9-7的下面一个虚线框表示了CProcessLocalObject::GetData的创建过程:
_afxBaseModule首先检查成员变量m_pObject是否空,如果非空就返回它,即进程模块状态指针;否则,在堆中创建一个动态的_AFX_BASE_MODULE_STATE对象,返回。
从上述两个GetData的实现可以看出,CThreadLocal模板对象负责线程局部变量的创建和管理(查询,修改,删除);CProcessLocal模板对象负责进程局部变量的创建和管理(查询,修改,删除)。
- 模块-线程状态的创建
模块状态的成员模块-线程状态m_thread的创建类似于线程状态的创建:当第一次访问m_thread所对应的CThreadLocal模板对象时,给m_thread分配MFC线程局部存储的私有槽号m_nSlot,并动态地创建_AFX_MODULE_THREAD_STATE对象,保存对象指针在m_nSlot槽中。
创建过程所涉及的几个重要函数的算法描述如下:
- AllocSlot
AllocSlot用来分配线程的MFC私有存储空间的槽号。由于该函数要修改全局变量_afxThreadData,所以必须使用m_sect关键段对象来同步多个线程对该函数的调用。
CThreadSlotData::AllocSlot()
{
进入关键段代码(EnterCriticalSection(m_sect);)
搜索m_pSlotData,查找空槽(SLOT)
如果不存在空槽(第一次进入时,肯定不存在)
分配或再分配内存以创建新槽,
指针m_pSlotData指向分配的地址。
得到新槽(SLOT)
标志该SLOT为已用
记录最新可用的SLOT到成员变量m_nRover中。
离开关键段代码(LeaveCriticalSection(m_sect);)
返回槽号
}
- GetThreadValue
GetThreadValue用来获取调用线程的第slot个线程局部变量的值。每一个线程局部变量都占用一个且只一个槽位。
CThreadSlotData::GetThreadValue(int slot)
{
//得到一个CThreadData型的指针pData
//pData指向MFC线程私有存储空间。
//m_tlsIndex在_afxThreadData创建时由构造函数创建
pData=(CThreadData*)TlsGetValue(m_tlsIndex),。
如果指针空或slot>pData->nCount, 则返回空。
否则,返回pData
}
- SetValue
SetValue用来把调用线程的第slot个线程局部变量的值(指针)存放到线程的MFC私有存储空间的第slot个槽位。
CThreadSlotData::SetValue(int slot, void *pValue)
{
//通过TLS索引得到线程的MFC私有存储空间
pData = (CThreadData*)TlsGetValue(m_tlsIndex)
//没有得到值或者pValue非空且当前槽号,即
//线程局部变量的个数
//大于使用当前局部变量的线程个数时
if (pData NULL or slot > pData->nCount && pValue!=NULL)
{
if pData NULL //当前线程第一次访问该线程局部变量
{
创建一个CThreadData实例;
添加到CThreadSlotData::m_list;
令pData指向它;
}
按目前为止,线程局部变量的个数为pData->pData分配或重分配内存,
用来容纳指向真正线程数据的指针
调用TlsSetValue(pData)保存pData
}
//把指向真正线程数据的pValue保存在pData对应的slot中
pData->pData[slot] = pValue
}
模块状态切换就是把当前线程的线程状态的m_pModuleState指针指向即将运行模块的模块状态。
MFC使用AFX_MANAGE_STATE宏来完成模块状态的切换,即进入模块时使用当前模板的模板状态,并保存原模板状态;退出模块时恢复原来的模块状态。这相当于状态的压栈和出栈。实现原理如下。
先看MFC关于AFX_MANAGE_STATE的定义:
#ifdef _AFXDLL
struct AFX_MAINTAIN_STATE
{
AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pModuleState);
~AFX_MAINTAIN_STATE();
protected:
AFX_MODULE_STATE* m_pPrevModuleState;
};
//AFX_MANAGE_STATE宏的定义:
#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE _ctlState(p);
#else // _AFXDLL
#define AFX_MANAGE_STATE(p)
#endif //!_AFXDLL
如果使用MFC DLL,MFC提供类AFX_MAINTAIN_STATE来实现状态的压栈和出栈,AFX_MANAGE_SATATE宏的作用是定义一个AFX_MAINTAIN_STATE类型的局部变量_ctlState。
AFX_MAINTAIN_STATE的构造函数在其成员变量m_pPrevModuleState中保存当前的模块状态对象,并把参数指定的模块状态设定为当前模块状态。所以该宏作为入口点的第一条语句就切换了模块状态。
在退出模块时,局部变量_ctlState将自动地销毁,这导致AFX_MAINTAIN_STATE的析构函数被调用,析构函数把保存在m_pPrevModuleState的状态设置为当前状态。
AFX_MANAGE_SATATE的参数在不同场合是不一样的,例如,
- DLL的输出函数使用
AFX_MANAGE_SATATE(AfxGetStaticModuleState());
其中,AfxGetStaticModuleState返回DLL的模块状态afxModuleState。
- 窗口函数使用
AFX_MANAGE_STATE(_afxBaseModuleState.GetData());
其中,_afxBaseModuleState.GetData()返回的是应用程序的全局模块状态。
OLE使用的模块切换方法有所不同,这里不作讨论。
上面讨论了线程执行行不同模块的代码时切换模块状态的情况。在线程创建时怎么处理模块状态呢?
- 一个进程(使用MFC的应用程序)的主线程创建线程模块状态和进程模块状态,前者是_AFX_THREAD_STATE类的实例,后者是_AFX_BASE_MODULE_STATE类的实例。
- 当进程的新的线程被创建时,它创建自己的线程状态,继承父线程的模块状态。在线程的入口函数_AfxThreadEntry完成这样的处理,该函数的描述见8.5.3节。
- 扩展DLL的模块状态
7.3.1节指出扩展DLL的实现必须遵循五条规则,为此,首先在扩展DLL实现文件里头,定义AFX_EXTENSION_MODULE类型的静态扩展模块变量,然后在DllMain入口函数里头使用AfxInitExtension初始化扩展模块变量,并且实现和输出一个初始化函数供扩展DLL的使用者调用。
使用者必须具备一个CWinApp对象,通常在它的InitInstance函数中调用扩展DLL提供的初始化函数。
一般用以下的几段代码完成上述任务。首先是扩展模块变量的定义和初始化:
static AFX_EXTENSION_MODULE extensionDLL;
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(extensionDLL,hInstance))
return 0;
……
}
}
然后是扩展DLL的初始化函数,假定初始化函数命名为InitMyDll,InitMyDll被定义为“C”链接的全局函数,并且被输出。
// wire up this DLL into the resource chain
extern “C” void WINAPI InitMyDll()
{
CDynLinkLibrary* pDLL = new
CDynLinkLibrary(extensionDLL, TRUE);
ASSERT(pDLL != NULL);
...
}
最后是调用者的处理,假定在应用程序对象的InitInstance函数中调用初始化函数:
BOOL CMyApp::InitInstance()
{
InitMyMyDll();
…
}
上述这些代码只有在动态链接到MFC DLL时才有用。下面,对这些代码进行分析和解释
- _AFX_EXTENSION_MODULE
在分析代码之前,先讨论描述扩展模块状态的_AFX_EXTENSION_MODULE类。_AFX_EXTENSION_MODULE没有基类,其定义如下:
struct AFX_EXTENSION_MODULE
{
BOOL bInitialized;
HMODULE hModule;
HMODULE hResource;
CRuntimeClass* pFirstSharedClass;
COleObjectFactory* pFirstSharedFactory;
};
其中:
第一个域表示该结构变量是否已经被初始化了;
第二个域用来保存扩展DLL的模块句柄;
第三个域用来保存扩展DLL的资源句柄;
第四个域用来保存扩展DLL要输出的CRuntimeClass类;
第五个域用来保存扩展DLL的OLE Factory。
该结构用来描述一个扩展DLL的模块状态信息,每一个扩展DLL都要定义一个该类型的静态变量,例如extensionDLL。
在DllMain中,调用AfxInitExtensionModule函数来初始化本DLL的静态变量该变量(扩展模块状态),如extensionDLL。函数AfxInitExtensionModule原型如下:
BOOL AFXAPI AfxInitExtensionModule(
AFX_EXTENSION_MODULE& state, HMODULE hModule)
其中:
参数1是DllMain传递给它的扩展DLL的模块状态,如extensionDLL;
参数2是DllMain传递给它的模块句柄。
AfxInitExtensionModule函数主要作以下事情:
(1)把扩展DLL模块的模块句柄hModule、资源句柄hModule分别保存到参数state的成员变量hModule、hResource中;
(2)把当前模块状态的m_classList列表的头保存到state的成员变量pFirstSharedClass中,m_classInit的头设置为模块状态的m_pClassInit。在扩展DLL模块进入DllMain之前,如果该扩展模块构造了静态AFX_CLASSINIT对象,则在初始化时把有关CRuntimeClass信息保存在当前模块状态(注意不是扩展DLL模块,而是应用程序模块)的m_classList列表中。因此,扩展DLL模块初始化的CRuntimeClass信息从模块状态的m_classList中转存到扩展模块状态state的pFirstSharedClass中,模块状态的m_classInit恢复被该DLL改变前的状态。
关于CRuntimeclass信息和AFX_CLASSINIT对象的构造,在3.3.1节曾经讨论过。一个扩展DLL在初始化时,如果需要输出它的CRuntimeClass对象,就可以使用相应的CRuntimeClass对象定义一个静态的AFX_CLASSINIT对象,而不一定要使用IMPLEMENT_SERIAL宏。当然,可以序列化的类必定导致可以输出的CRuntimeClass对象。
(3)若支持OLE的话,把当前模块状态的m_factoryList的头保存到state的成员变量pFirstSharedFactory中。m_factoryList的头设置为模块状态的m_m_pFactoryInit。
(4)这样,经过初始化之后,扩展DLL模块包含了扩展DLL的模块句柄、资源句柄、本模块初始化的CRuntimeClass类等等。
扩展DLL的初始化函数将使用扩展模块状态信息。下面,讨论初始化函数的作用。
- 扩展DLL的初始化函数
在初始化函数InitMyDll中,创建了一个动态的CDynLinkLibrary对象,并把对象指针保存在pDLL中。CDynLinkLibrary类从CCmdTarget派生,定义如下:
class CDynLinkLibrary : public CCmdTarget
{
DECLARE_DYNAMIC(CDynLinkLibrary)
public:
// Constructor
CDynLinkLibrary(AFX_EXTENSION_MODULE& state,
BOOL bSystem = FALSE);
// Attributes
HMODULE m_hModule;
HMODULE m_hResource; // for shared resources
CTypedSimpleList<CRuntimeClass*> m_classList;
#ifndef _AFX_NO_OLE_SUPPORT
CTypedSimpleList<COleObjectFactory*> m_factoryList;
#endif
BOOL m_bSystem; // TRUE only for MFC DLLs
// Implementation
public:
CDynLinkLibrary* m_pNextDLL; // simple singly linked list
virtual ~CDynLinkLibrary();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif //_DEBUG
};
CDynLinkLibrary的结构和AFX_EXTENSION_MODULE有一定的相似性,存在对应关系。
CDynLinkLibrary构造函数的第一个参数就是经过AfxInitExtensionModule初始化后的扩展DLL的模块状态,如extensionDLL,第二个参数表示该DLL模块是否是系统模块。
创建CDynLinkLibrary对象导致CCmdTarget和CDynLinkLibrary类的构造函数被调用。CCmdTarget的构造函数将获取模块状态并且保存在成员变量m_pModuleState中。CDynLinkLibrary的构造函数完成以下动作:
构造列表m_classList和m_factoryList;
把参数state的域hModule、hResource复制到对应的成员变量m_hModule、m_hResource中;
把state的pFirstSharedClass、pFirstSharedFactory分别插入到m_classList列表、m_factoryList列表的表头;
把参数2的值赋值给成员变量m_bSystem中;
至此,CDynLinkLibrary对象已经构造完毕。之后,CDynLinkLibrary构造函数把CDynLinkLibrary对象自身添加到当前模块状态(调用扩展DLL的应用程序模块或者规则DLL模块)的CDynLinkLibrary列表m_libraryList的表头。为了防止多个线程修改模块状态的m_libraryList,访问m_libraryList时使用了同步机制。
这样,调用模块执行完扩展模块的初始化函数之后,就把该扩展DLL的资源、CRuntimeClass类、OLE Factory等链接到调用者的模块状态中,形成一个链表。图9-8表明了这种关系链。
综合以上分析,可以知道:
扩展DLL的模块仅仅在该DLL调用DllMain期间和调用初始化函数期间被使用,在这些初始化完毕之后,扩展DLL模块被链接到当前调用模块的模块状态中,因此它所包含的资源信息等也就被链接到调用扩展DLL的应用程序或者规则DLL的模块状态中了。扩展DLL扩展了调用者的资源等,这是“扩展DLL”得名的原因之一。
也正因为扩展DLL没有自己的模块状态(指AFX_MODULE_STATE对象,扩展DLL模块状态不是),而且必须由有模块状态的模块来使用,所以只有动态链接到MFC的应用程序或者规则DLL才可以使用扩展DLL模块的输出函数或者输出类。
- 核心MFC DLL
所谓核心MFC DLL,就是MFC核心类库形成的DLL,通常说动态链接到MFC,就是指核心MFC DLL。
核心MFC DLL实际上也是一种扩展DLL,因为它定义了自己的扩展模块状态coreDLL,实现了自己的DllMain函数,使用AfxInitExtensionModule初始化核心DLL的扩展模块状态coreDLL,并且DllMain还创建了CDynLinkLibrary,把核心DLL的扩展模块状态coreDLL链接到当前应用程序的模块状态中。所有这些,都符合扩展DLL的处理标准。
但是,核心MFC DLL是一种特殊的扩展DLL,因为它定义和实现了MFC类库,模块状态、线程状态、进程状态、状态管理和使用的机制就是核心MFC DLL定义和实现的。例如核心MFC DLL定义和输出的模块状态变量,即_afxBaseModuleState,就是动态链接到MFC的DLL的应用程序的模块状态。
但是MFC DLL不作为独立的模块表现出来,而是把自己作为一个扩展模块来处理。当应用程序动态链接到MFC DLL时,MFC DLL把自己的扩展模块状态coreDLL链接到模块状态afxBaseModuleState,模块状态的成员变量m_hCurrentInstanceHandle指定为应用程序的句柄。当规则DLL动态链接到MFC DLL时,由规则DLL的DllMain把核心MFC DLL的扩展模块状态coreDLL链接到规则DLL的模块状态afxModuleState中,模块状态afxModuleState的m_hCurrentInstanceHandle指定为规则DLL的句柄。
关于afxModuleState和规则DLL的模块状态,见下一节的讨论。
- 动态链接的规则DLL的模块状态的实现
在本节中,动态链接到MFC DLL(定义了_AFXDLL)的规则DLL在下文简称为规则DLL。
(1)规则DLL的模块状态的定义
规则DLL有自己的模块状态_afxModuleState,它是一个静态变量,定义如下:
static _AFX_DLL_MODULE_STATE afxModuleState;
_AFX_DLL_MODULE_STATE的基类是AFX_MODULE_STATE。
在前面的模块状态切换中提到的AfxGetStaticModuleState函数,其定义和实现如下:
_AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState()
{
AFX_MODULE_STATE* pModuleState = &afxModuleState;
return pModuleState;
}
它返回规则DLL的模块状态afxModuleState。
规则DLL的内部函数使用afxModuleState作为模块状态;输出函数在被调用的时候首先切换到该模块状态,然后进一步处理。
(2)规则DLL的模块状态的初始化
从用户角度来看,动态链接到MFC DLL的规则DLL不需要DllMain函数,只要提供CWinApp对象即可。其实,MFC内部是在实现扩展DLL的方法基础上来实现规则DLL的,它不仅为规则DLL提供了DllMain函数,而且规则DLL也有扩展DLL模块状态controlDLL。
顺便指出,和扩展DLL相比,规则DLL有一个CWinApp(或其派生类)应用程序对象和一个模块状态afxModuleState。应用程序对象是全局对象,所以在进入规则DLL的DllMain之前已经被创建,DllMain可以调用它的初始化函数InitInstance。模块状态afxModuleState是静态全局变量,也在进入DllMain之前被创建,DllMain访问模块状态时得到的就是该变量。扩展DLL是没有CWinApp对象和模块状态的,它只能使用应用程序或者规则DLL的CWinApp对象和模块状态。
由于核心MFC DLL的DllMain被调用的时候,访问的必定是应用程序的模块状态,要把核心DLL的扩展模块状态链接到规则DLL的模块状态中,必须通过规则DLL的DllMain来实现。
规则DLL的DllMain(MFC内部实现)把参数1表示的模块和资源句柄通过AfxWinInit函数保存到规则DLL的模块状态中。顺便指出,WinMain也通过AfxWinInit函数把资源和模块句柄保存到应用程序的模块状态中。
然后,该DllMain还创建了一个CDynLinkLibrary对象,把核心MFC DLL的扩展模块 coreDLL链接到本DLL的模块状态afxModuleState。
接着,DllMain得到自己的应用程序对象并调用InitInstance初始化。
之后,DllMain创建另一个CDynLinkLibrary对象,把本DLL的扩展模块controlDLL链接到本DLL的模块状态afxModuleState。
(3)使用规则DLL的应用程序可不需要CwinApp对象
规则DLL的资源等是由DLL内部使用的,不存在资源或者CRuntimeClass类输出的问题,这样调用规则DLL的程序不必具有模块状态,不必关心规则DLL的内部实现,不一定需要CwinApp对象,所以可以是任意Win32应用程序,
还有一点需要指出,DllMain也是规则DLL的入口点,在它之前,调用DllMain的RawDllMain已经切换了模块状态,RawDllMain是静态链接的,所以不必考虑状态切换。
- 状态信息的作用
在分析了MFC模块状态的实现基础和管理机制之后,现在对状态信息的作用进行专门的讨论。
-
传统上,线程状态、模块状态等包含的信息是全局变量,但是为了支持Win32s、多线程、DLL等,这些变量必须是限于进程或者线程范围内有效,或者限于某个模块内有效。也就是,不再可能把它们作为全局变量处理。因此,MFC引入模块、线程、模块-线程状态等来保存和管理一些重要的信息。
例如:一个模块注册了一个“窗口类”之后,应用程序要保存“窗口类”的名字,以便在模块退出时取消注册的“窗口类”。因此,模块状态使用成员变量m_szUnregisterList在注册成功之后保存的“窗口类”名字。窗口注册见2.2.1节。
又如:Tooltip窗口是线程相关的,每个线程一个,所以线程状态用成员变量m_pToolTip来保存本线程的MFC Tooltip窗口对象。Tooltip窗口见13.2.4.4节。
还有,MFC对象是线程和模块相关的,所以模块线程中有一组变量用来管理本线程的MFC对象到Windows对象的映射关系。关于MFC对象和Windows对象的映射,见稍后的讨论。
模块状态、线程状态、模块线程状态的每个成员变量都有自己存在的必要和作用,这里就不一一论述了,在此,只是强调模块状态自动地实现对模块句柄和资源句柄等信息的保存和管理,这对MFC应用程序是非常重要的。
SDK 下的应用程序或者DLL,通常使用一个全局变量来保存模块/资源句柄。有了模块状态之后,程序员就不必这么作了。规则DLL或者应用程序的模块和资源句柄在调用DllMain或WinMain时被保存到了当前模块的模块状态中。如果是扩展DLL,则其句柄被保存到扩展模块状态中,并通过CDynLinkLibrary对象链接到主模块的模块状态。
图9-8示意了MFC模块状态对资源、CRuntimeClass对象、OLE工厂等模块信息的管理。
图9-8的说明:
左边的主模块状态表示动态链接到MFC DLL的应用程序或者规则DLL的模块状态,其资源句柄和模块句柄用来查找和获取资源,资源句柄一般是应用程序的模块句柄;CRuntimeClass对象列表和COleObjectFactory对象列表分别表示该模块初始化了的CRuntimeClass对象和该模块的OLE工厂对象;CDynLinkLibrary列表包含了它引用的系列扩展DLL的扩展模块状态(包括核心MFC DLL的状态),链表中的每一个CDynLinkLibrary对象对应一个扩展模块状态,代表了创建该对象的扩展DLL的有关资源、信息。
MFC查找资源、CRuntimeClass类、OLE工厂时,首先查找模块状态,然后,遍历CDynLinkLibrary表搜索相应的对象。下面两节举例说明。
- MFC资源、运行类信息的查找
-
MFC内部使用的资源查找函数是:
HINSTANCE AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType):
其中:
参数1是要查找的资源名称,参数2是要查找的资源类型。
返回包含指定资源的模块的句柄。
上述函数的查找算法如下:
- 如果进程模块状态(主模块)不是系统模块,则使用::FindResource(下同)搜索它,成功则返回;
- 如果没有找到,则遍历CDynLinkLibrary对象列表,搜索所有的非系统模块,成功则返回;
- 如果没有找到,则检查主模块的语言资源,成功则返回;
- 如果没有找到,并且主模块是系统模块,则搜索它,成功则返回;
- 如果没有找到,则遍历CDynLinkLibrary对象列表,搜索所有的系统模块,成功则返回;
- 如果没有找到,则使用AfxGetResourceHanlde返回应用程序的资源。
需要指出的是,遍历CDynLinkLibrary对象列表时,必须采取同步措施,防止其他线程改变链表。MFC是通过锁定全局变量CRIT_DYNLINKLIST来实现的,类似的全局变量MFC定义了多个。
运行时类信息的查找算法类似。
3.3.4节指出,对象进行“<<”序列化操作时,首先需要搜索到指定类的运行时信息,方法如下:
CRuntimeClass* PASCAL CRuntimeClass::Load(
CArchive& ar, UINT* pwSchemaNum)
- 遍历主模块的CRuntimeClass对象列表m_classList,搜索主模块是否实现了指定的CRuntimeClass类;
- 遍历CDynLinkLibrary对象列表m_libraryList;对每一个CDynLinkLibrary对象,遍历它的CRuntimeClass对象列表m_classList。这样,所有的扩展DLL模块的CRuntimeClass对象都会被搜索到。
- 模块信息的显示
遍历模块状态和CDynLinkLibrary列表,可以显示模块状态及其扩展模块状态的有关信息。下面,给出一个实现,它显示程序的当前模块名称、句柄和初始化的CRuntimeClass类,然后显示所有扩展模块的名称名称、句柄和初始化的CRuntimeClass类。
#ifdef _DEBUG
AFX_MODULE_STATE* pState = AfxGetModuleState();
//显示应用程序的名称和句柄
TRACE("APP %s HANDLE %x\r\n", pState->m_lpszCurrentAppName,
pState->m_hCurrentInstanceHandle);
TCHAR szT[256];
int nClasses;
nClasses=0;
//显示CRuntimeClass类信息
AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
for (CRuntimeClass* pClass = pModuleState->m_classList;
pClass != NULL;pClass = pClass->m_pNextClass)
{
nClasses++;
TRACE("CRuntimeClass: %s\r\n",pClass->m_lpszClassName, );
}
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
TRACE("all %d classes\r\n", nClasses);
//遍历CDynLinkLibrary列表
AfxLockGlobals(CRIT_DYNLINKLIST);
for (CDynLinkLibrary* pDLL = pState->m_libraryList; pDLL != NULL;
pDLL = pDLL->m_pNextDLL)
{
// 得到模块名并且显示
TCHAR szName[64];
GetModuleFileName(pDLL->m_hModule, szName, sizeof(szName));
TRACE("MODULE %s HANDLE IS %x \r\n", szName, pDLL->m_hModule);
//得到CRuntimeClass信息并显示
nClasses = 0;
for (CRuntimeClass* pClass = pDLL->m_classList;
pClass != NULL; pClass = pClass->m_pNextClass)
{
nClasses++;
TRACE("CRuntimeClass: %s\r\n",pClass->m_lpszClassName, );
}
wsprintf(szT, _T(" Module %s has %d classes"),szName, nClasses);
}
AfxUnlockGlobals(CRIT_DYNLINKLIST);
#endif
使用MFC提供的调试函数AfxDoForAllClasses可以得到DLL模块的输出CRuntimeClass类的信息。上述实现类似于AfxDoForAllClasses函数的处理,只不过增加了模块名和模块句柄信息。
- 模块-线程状态的作用
由模块-线程状态类的定义可知,一个模块-线程状态包含了几类Windows对象—MFC对象的映射。下面讨论它们的作用。
MFC规定:
- 不能从一个非MFC线程创建和访问MFC对象
如果一个线程被创建时没有用到CWinThread对象,比如,直接使用“C”的_beginthread或者_beginthreadex创建的线程,则该线程不能访问MFC对象;换句话说,只有通过CWinThread创建MFC线程对象和Win32线程,才可能在创建的线程中使用MFC对象。
- 一个线程仅仅能访问它所创建的MFC对象
这两个规定的原因是:
为了防止多个线程并发地访问同一个MFC对象,MFC对象和Windows对象之间有一个一一对应的关系,这种关系以映射的形式保存在创建线程的当前模块的模块-线程状态信息中。当一个线程使用某个MFC对象指针P时,ASSERT_VALID(P)将验证当前线程的当前模块是否有Windows句柄和P对应,即是否创建了P所指的Windows对象,验证失败导致ASSERT断言中断程序的执行。如果一个线程要使用其他线程的Windows对象,则必须传递Windows对象句柄,不能传递MFC对象指针。
当然一般来说,MFC应用程序仅仅在Debug版本下才检查这种映射关系,所以访问其他线程的MFC对象的程序在Realease版本下表面上不会有问题,但是MFC对象被并发访问的后果是不可预见的。
MFC提供了几个函数完成MFC对象和Windows对象之间的映射或者解除这种映射关系,以及从MFC对象得到Windows对象或者从Windows对象得到或创建相应的MFC对象。
每一个MFC对象类都有成员函数Attach和Detach,FromHandle和FromHandlePermanent,AssertValid。这些成员函数的形式如下:
- Attach(HANDLE Windows_Object_Handle)
例如:CWnd类的是Attach(HANLDE hWnd),CDC类的是Attach(HDC hDc)。
Attach用来把一个句柄永久性(Perment)地映射到一个MFC对象上:它把一个Windows对象捆绑(Attach)到一个MFC对象上,MFC对象的句柄成员变量赋值为Windows对象句柄,该MFC对象应该已经存在,但是句柄成员变量为空。
- Detach()
Detach用来取消Windows对象到MFC对象的永久性映射。如果该Windows对象有一个临时的映射存在,则Detach不理会它。MFC让线程的Idle清除临时映射和临时MFC对象。
- FromHandle(HANDLE Windows_Object)
它是一个静态成员函数。如果该Windows对象没有映射到一个MFC对象,FromHandle则创建一个临时的MFC对象,并把Windows对象映射到临时的MFC对象上,然后返回临时MFC对象。
- FromHandlePermanent(HANDLE Windows_Object)
它是一个静态成员函数。如果该Windows对象没有永久地映射到一个MFC对象上,则返回NULL,否则返回对应的MFC对象。
- AssertValid()
它是从CObject类继承来的虚拟函数。MFC覆盖该函数,实现了至少一个功能:判断当前MFC对象的指针this是否映射到一个对应的可靠的Windows对象。
图 9-9示意了MFC对映射结构的实现层次,对图9-9解释如下。
图中上面的虚线框表示使用映射关系的高层调用,包括上面讲述的几类函数。MFC和应用程序通过它们创建、销毁、使用映射关系。
图中中间的虚线框表示MFC使用CHandleMap类实现对映射关系的管理。一个CHandleMap对象可以通过两个成员变量来管理两种映射数据:临时映射和永久映射。模块-线程状态给每一类MFC对象分派一个CHandleMap对象来管理其映射数据(见模块-线程类的定义),例如m_pmapHWND所指对象用来保存CWnd对象(或派生类对象)和Windows window之间的映射。
下面的虚线框表示映射关系的最底层实现,MFC使用通用类CMapPtrToPtr来管理MFC对象指针和Windows句柄之间的映射数据。
对本节总结如下:
- MFC的映射数据保存在模块-线程状态中,是线程和模块局部的。每个线程管理自己映射的数据,其他线程不能访问到本线程的映射数据,也就不允许使用本线程的MFC对象。
- 每一个MFC对象类(CWnd、CDC等)负责创建或者管理这类线程-模块状态的对应CHandleMap类对象。例如,CWnd::Attach创建一个永久性的映射保存在m_pmapHwnd所指对象中,如果m_pmapHand还没有创建,则使用AfxMapHWND创建相应的CHandleMap对象。
- 映射分两类:永久性的或者临时的。
在2.4节就曾经提到了临时对象,现在是深入了解它们的时候了。
- 临时对象指MFC对象,是MFC或者程序员使用FromHandle或者SelectObject等从一个Windows对象句柄创建的对应的MFC对象。
- 在模块-线程状态中,临时MFC对象的映射是和永久映射分开保存的。
- 临时MFC对象在使用完毕后由MFC框架自动删除,MFC在线程的Idle处理中删除本线程的临时MFC对象,为了防止并发修改,通过线程状态m_nTempMapLock(等于0,可以修改,大于0,等待)来同步。所以,临时MFC对象不能保存备用。
至此,本章讨论了MFC的线程局部存储机制,MFC状态的定义、实现和用途。在程序或者DLL退出之前,模块状态被销毁;在线程退出时,线程状态被销毁。状态对象被销毁之前,它活动期间所动态创建的对象被销毁,动态分配的内存被释放。
先解释几个函数:
AfxTermExtensionModule(HANDLE hInstanceOfDll,BOOL bAll);
若bAll为真,则该函数销毁本模块(hInstanceOfDll标识的模块)的模块状态的m_libraryList列表中所有动态分配的CDynLinkLibrary对象,否则,该函数清理本DLL动态分配的CDynLinkLibrary对象,并调用AfxTerLocalData释放本DLL模块为当前线程的线程局部变量分配的堆空间。
AfxTermLocalData(HANDLE hInstance, BOOL bAll);
若bAll为真,则删除MFC线程局部存储的所有槽的指针所指的对象,也就是销毁当前线程的全部局部变量,释放为这些线程局部变量分配的内存;否则,仅仅删除、清理当前线程在hInstance表示的DLL模块中创建的线程局部变量。
参与清理工作的函数有多种、多个,下面结合具体情况简要描述它们的作用。
(1)对动态链接到MFC DLL的应用程序
动态链接到MFC DLL的应用程序退出时,将在DllMain和RawDllMain处理进程分离时清理状态对象,该DllMain和RawDllMain是核心MFC DLL的入口和出口,在DLLINIT.CPP文件中实现,和进程分离时完成如下动作:
DllMain调用AfxTermExtensionModule(coreDll)清理核心MFC DLL的模块状态;调用AfxTermExtensionModule(coreDll, TRUE)清理OLE私有的模块状态;调用AfxTermLocalData(NULL, TRUE)释放本进程或者线程所有的局部变量。
RawDllMain在DllMain之后调用,它调用AfxTlsRealease;AfxTlsRealease减少对_afxThreadData的引用计数,如果引用数为零,则调用对应的CThreadSlotData析构函数清理_afxThreadData所指对象。
(2)对静态链接到MFC DLL的应用程序
如果是静态链接到MFC DLL的应用程序,由于RawDllMain和DllMain不起作用,将由一个静态变量析构时完成状态的清除:
有一个AFX_TERM_APP_STATE类型的静态变量,在程序结束时将被销毁,导致析构函数被调用,析构函数完成以下动作:
调用AfxTermLocalData(NULL, TRUE)释放本进程(主线程)的所用局部数据。
(3)对于动态链接到MFC DLL的规则DLL
对于动态链接到MFC DLL的规则DLL,将在RawDllMain和DllMain中清理状态对象。这两个函数在DllModule.cpp中定义,是规则DLL的入口和出口。当和进程分离时,分别有如下动作:
DllMain清除该模块的模块-线程状态中的所有临时映射,清除临时MFC对象;调用AfxWinTerm;调用AfxTermExtensionModule(controlDLL, TRUE),释放本DLL模块状态m_libraryList中的所有CDynLinkLibrary对象。
RawDllMain设置线程状态的模块状态指针,使它指向线程状态的m_PrevModuleState所指状态。
(4)对于静态链接到MFC DLL的DLL
对于静态链接到MFC DLL的DLL,只有DllMain会被调用,执行以下动作:
清除该模块的模块-线程状态中的所有临时映射,清除临时MFC对象;调用AfxWinTerm;调用AfxTermLocalData(hInstance, TRUE)清理本DLL模块的当前线程的线程局部数据。
另外,它定义一个_AFX_TERM_DLL_STATE类型的静态变量,在DLL退出时该变量被销毁,导致其析构函数被调用。析构函数完成如下动作:
调用AfxTermateLocalData(NULL, TRUE);调用AfxCriticlTerm结束关键变量;调用AfxTlsRealease。
(5)线程终止时
当使用AFxBeginThread创建的线程终止时,将调用AfxTermThread(HANDLE hInstance)作结束线程的清理工作(参数为NULL):销毁临时MFC对象,销毁本线程的线程局部变量,等等。
另外,当DLL模块和AfxBeginThread创建的线程分离时,也调用AfxTermThread(hInstance),参数是模块的句柄,销毁临时MFC对象,销毁本线程在本DLL创建的线程局部变量,等等。所以,AfxTermThread可能被调用两次。
最后,CThreadLocal和CProcessLocal的实例将被销毁,析构函数被调用:如果MFC线程局部存储空间的槽m_nSlot所指的线程局部对象还没有销毁,则销毁它。
_afxThreadData在MFC DLL的RawDllMain或者随着_AFX_TERM_APP_STATE析构函数的调用,_afxThreadData所指对象被销毁。_afxThreadData所指对象销毁之后,所有的状态相关的内存都被释放。
返回页首 |