第9章_MFC的状态


第 9 章 MFC的状态 MFC 定义了多种状态信息,这里要介绍的是模块状态、进程状态、线程状态。这些状态可 以组合在一起,例如 MFC 句柄映射就是模块和线程局部有效的,属于模块-线程状态的一部 分。 9.1 模块状态 这里模块的含义是:一个可执行的程序或者一个使用 MFC DLL 的 DLL,比如一个 OLE 控 件就是一个模块。 一个应用程序的每一个模块都有一个状态,模块状态包括这样一些信息:用来加载资源的 Windows 实例句柄、指向当前 CWinApp 或者 CWinThread 对象的指针、OLE 模块的引用计 数、Windows 对象与相应的 MFC 对象之间的映射。只有单一模块的应用程序的状态如图 9-1 所示。 MFC 应用程序 线程对象 (Thread Object m_pModuleState ) 状态数据 图 9-1 (state data) 单模块状态示意图 m_pModuleState 指针是线程对象的成员变量,指向当前模块状态信息(一个 AFX_MODULE_STATE 结构变量)。当程序运行进入某个特定的模块时,必须保证当前使用 的模块状态是有效的模块状态──是这个特定模块的模块状态。所以,每个线程对象都有一 个指针指向有效的模块状态,每当进入某个模块时都要使它指向有效模块状态,这对维护应 用程序全局状态和每个模块状态的完整性来说是非常重要的。为了作到这一点,每个模块的 所有入口点有责任实现模块状态的切换。模块的入口点包括:DLL 的输出函数;OLE/COM 界面的成员函数;窗口过程。 MFC 应用程序 线程对象 (Thread Object) m_pModuleState 状态数据(state data) 模块 1(Module1) 状 (sta 态数据 te data) 模块 2(Module2) 状 (sta 态数据 te data) 模块 1 范围 模块 2 范围 图 9-2 多模块的状态示意图 在讲述窗口过程和动态链接到 MFC DLL 的规则 DLL 时,曾提到了语句 AFX_MANAGE_STATE(AfxGetStaticModuleState( )),它就是用来在入口点切换模块状态的。 其实现机制将在后面 9.4.1 节讲解。 多个模块状态之间切换的示意图如图 9-2 所示。 图 9-2 中,m_pModuleState 总是指向当前模块的状态。 9.2 模块、进程和线程状态的数据结构 MFC 定义了一系列类或者结构,通过它们来实现状态信息的管理。这一节将描述它们的关 系,并逐一解释它们的数据结构、成员函数等。 9.2.1 层次关系 图 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 显示了这几个类的交互关系。 CNoTrackObject _AFX_THREAD_STATE AFX_MODULE_STATE _AFX_BASE_MODULE_STATE 等 AFX_MODULE_THREAD_STATE 图 9-3 MFC 状态类的层次 线程状态 m_pModuleState m_pPreModuleState 模块状态 线程局部存 储:每个使用该 模块的线程有 一个模块-线程 类的实例: m_thread 模块- 线程 状态: 句柄映射 … 模块状态 线程局部存 储:每个使用该 模块的线程有 一个模块-线程 类的实例: m_thread 模块- 线程 状态: 句柄映射 … 的关系图 9-4 模块、线程、模块-线程状态 从图 9-4 可以看出:首先,每个线程有一个线程状态,线程状态的指针 m_pModuleState 和 m_pPreModuleState 分别指向线程当前运行模块的状态或前一运行模块的状态;其次,每一 个模块状态都有一个线程局部的变量用来存储模块-线程状态。 下面各小节列出状态信息管理所涉及的各个类的定义。 9.2.2 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)。 9.2.3 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 m_classList; // OLE object factories #ifndef _AFX_NO_OLE_SUPPORT #ifdef _AFXDLL COleObjectFactory* m_pFactoryInit; #endif CTypedSimpleList 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 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 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。 9.2.4 _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 应用程序模块的状态信息。 9.2.5 _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 相关的变量,等等。 9.2.6 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 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 如何通过上述这些类来实现其状态的管理。 9.3 线程局部存储机制和状态的实现 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 宏定义的变量,每个进程都有自己独立的拷贝,这个变量在不 同的进程里头可以有不同的取值。 分别解释这三个变量。 9.3.1 CThreadSlotData和_afxThreadData 9.3.1.1 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 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)为每一个线程分配一个 线程私有的存储空间并管理该空间。它把这个空间划分为若干个槽,每个槽放一个线程私有 的数据指针,这样每个线程就可以存放任意个线程私有的数据指针。 9.3.1.2 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。 9.3.1.3 _afxThreadData 进程:TLS 索引(m_tlsIndex) 线程 1 的 TLS 数据 线程 2 的 TLS 数据 线程 n 的 TLS 数据 图 9-5 MFC 的线程存储机制 1 _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 线程局部存储机制总结如下: z 每个线程局部变量(宏 THREAD_LOCAL 定义)占用一个槽,并有一个槽号。。 z 每个线程都有自己的 MFC 局部存储空间(下文多次使用“线程的 MFC 局部存储 空间”,表示和此处相同的概念)。 z 通过 TLS 索引得到的是一个指针 P1,它指向线程的 MFC 局部存储空间。 z 通过指针 P1 和线程局部变量在空间所占用的槽号,得到该槽所存储的线程私有的 数据指针,即真正的线程私有数据的地址 P2; z 从地址 P2 得到数据 D。 这个过程相当于几重间接寻址:先得到 TLS 线程私有数据指针,从 TLS 线程私有数据指针 得到线程的 MFC 线程局部存储空间,再从 MFC 局部存储空间的对应槽得到一个线程私有 的数据指针,从该指针得到最终的线程私有数据。如果没有这种机制,使用 Win32 TLS 只 要一次间接寻址:得到 TLS 线程私有数据指针,从该指针得到最终的线程私有数据。 槽 1 槽 2 槽 3 … 槽 m 槽 i 槽是否被占 用标识 占用该槽的 模块句柄 线程 1 数据 指向下一线程数据 m_pSlotData m_list 线程 i 数据的个数 线程 i 的数据 1 每个 的结构 CThr 线程数据 eadData 线程 2 数据 线程 3 数据 … 线程 i 数据 线程 k 数据 图 9-6 MFC 的线程存储机制 2 … … 每个槽的 结构 CSlotData … 线程 i 的数 堆中存储的数据 据 p … 线程 i 的数据 m 9.3.2 线程状态_afxThreadState 从上一节知道了 MFC 的线程局部存储机制。但有一点还不清楚,即某个线程局部变量所占 用的槽号是怎么保存的呢?关于这点可从线程局部的线程状态变量_afxThreadState 的实现 来分析 MFC 的作法。变量_afxThreadState 的定义如下: THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState) THREAD_LOCAL 是一个宏,THREAD_LOCAL(class_name, ident_name)宏展开后如下: AFX_DATADEF CThreadLocal 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 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 记录了线程局部变量对象的槽号。 9.3.3 进程模块状态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 ident_name; 这里,CProcessLocal 是一个类模板,从 CProcessLocalObject 类继承。 CProcessLocalObject 和 CProcessLocal 的定义如下: class CProcessLocalObject { public: // Attributes CNoTrackObject* GetData(CNoTrackObject* (AFXAPI* pfnCreateObject)()); // Implementation CNoTrackObject* volatile m_pObject; ~CProcessLocalObject(); }; template 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 模板对象。 9.3.4 状态对象的创建 9.3.4.1 状态对象的创建过程 回顾前一节的三个定义: CThreadSlotData* _afxThreadData; THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState) PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState) 第一个仅仅定义了一个指针;第二和第三个定义了一个模板类的实例。相应的 CThreadSlot Data 对象(全局)、_AFX_THREAD_STATE 对象(线程局部)以及_AFX_BASE_MODULE _STATE 对象(进程局部)并没有创建。当然,模块状态对象的成员模块-线程对象也没有被 创建。这些对象要到第一次被访问时,才会被创建,这样做会提高加载 DLL 的速度。 下面以一个动态链接到 MFC DLL 的单模块应用程序为例,说明这些对象的创建过程。 当第一次访问状态信息时,比如使用 AfxGetModuleState 得到模块状态,导致系列创建过程 的开始,如图 9-7 所示。 访问线程状态 pState = _afxThreadState 创建 CThreadSlotData 针于_afxThreadData 中; 的动态实例并存放其指 创建线程局部的_AFX_TH 对象并保存其指针 pValue ERAD_STATE 于 m_nSlot 槽 _afxThreadData->SetValue(m_nSlot,pValue) 得到了线程状态对象 afxThreadSta 线程对象的模块状态空 pState->pModul te,返回 eState 得到线程当前模块状态 访问进程模块状态 _afxBaseModuleSta 如果 m_pObject 空(第一次访问) te.GetData() 创建进程状态对象: _AFX_BASE_MODULE_S 的实例_afxBaseModuledSt TATE ate 得到了进程程状态对象 afxBaseModuleState 得到模块状态 pState Y YN N 图 9-7 线程、模块状态的创建过程 YN YN 分配存储槽:m_nSlot=_afxThreadData->AllocSlot() _afxThreadData 空(首次引用其所指对象 如 m_nSlot 槽值 pValue 空 (本线程首次访问该局部变量 ) ) _m_nSlot=0(还未分配 MFC 线程局部存储槽) YN 首先分析语句 pState=_afxThreadState。如果_afxThreadData、线程状态和模块状态还没有创 建,该语句可以导致这些数据的创建。 pState 声明为 CNoTrackObject 对象的指针,_afxThreadState 声明为一个模板 CThreadLocal 的实例,pState=_afxThreadData 为什么可以通过编译器的检查呢?因为 CThreadLocal 模板 重载了操作符“”*”和“->”,这两个运算返回 CNoTrackObject 类型的对象。回顾 3.2 节 CThreadLocalObject 、 CThreadLocal 的定义,这两个操作符运算到最后都是调用 CThreadLocalObject 的成员函数 GetData。 z 创建_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 中,返回该指针。 z 创建模块状态 得到了线程状态的值后,通过它得到模块状态 m_pModuleState。如 果 m_pModuleState 为空, 表明该线程状态是才创建的,其许多成员变量还没有赋值,程序的进程模块状态还没有被创 建。于是调用函数_afxBaseModule.GetData,导致进程模块状态被创建。 图 9-7 的下面一个虚线框表示了 CProcessLocalObject::GetData 的创建过程: _afxBaseModule 首先检查成员变量 m_pObject 是否空,如果非空就返回它,即进程模块状态 指针;否则,在堆中创建一个动态的_AFX_BASE_MODULE_STATE 对象,返回。 从上述两个 GetData 的实现可以看出,CThreadLocal 模板对象负责线程局部变量的创建和管 理(查询,修改,删除);CProcessLocal 模板对象负责进程局部变量的创建和管理(查询,修 改,删除)。 z 模块-线程状态的创建 模块状态的成员模块-线程状态 m_thread 的创建类似于线程状态的创建:当第一次访问 m_thread 所对应的 CThreadLocal 模板对象时,给 m_thread 分配 MFC 线程局部存储的私有 槽号 m_nSlot,并动态地创建_AFX_MODULE_THREAD_STATE 对象,保存对象指针在 m_nSlot 槽中。 9.3.4.2 创建过程所涉及的几个重要函数的算法 创建过程所涉及的几个重要函数的算法描述如下: (1) AllocSlot AllocSlot 用来分配线程的 MFC 私有存储空间的槽号。由于该函数要修改全局变量 _afxThreadData,所以必须使用 m_sect 关键段对象来同步多个线程对该函数的调用。 CThreadSlotData::AllocSlot() { 进入关键段代码(EnterCriticalSection(m_sect);) 搜索 m_pSlotData,查找空槽(SLOT) 如果不存在空槽(第一次进入时,肯定不存在) 分配或再分配内存以创建新槽, 指针 m_pSlotData 指向分配的地址。 得到新槽(SLOT) 标志该 SLOT 为已用 记录最新可用的 SLOT 到成员变量 m_nRover 中。 离开关键段代码(LeaveCriticalSection(m_sect);) 返回槽号 } (2) GetThreadValue GetThreadValue 用来获取调用线程的第 slot 个线程局部变量的值。每一个线程局部变量都占 用一个且只一个槽位。 CThreadSlotData::GetThreadValue(int slot) { //得到一个 CThreadData 型的指针 pData //pData 指向 MFC 线程私有存储空间。 //m_tlsIndex 在_afxThreadData 创建时由构造函数创建 pData=(CThreadData*)TlsGetValue(m_tlsIndex),。 如果指针空或 slot>pData->nCount, 则返回空。 否则,返回 pData } (3) 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 } 9.4 管理状态 在描述了 MFC 状态的实现机制之后,现在来讨论 MFC 的状态管理和相关状态的作用。 9.4.1 模块状态切换 模块状态切换就是把当前线程的线程状态的 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 的参数在不同场合是不一样的,例如, z DLL 的输出函数使用 AFX_MANAGE_SATATE(AfxGetStaticModuleState()); 其中,AfxGetStaticModuleState 返回 DLL 的模块状态 afxModuleState。 z 窗口函数使用 AFX_MANAGE_STATE(_afxBaseModuleState.GetData()); 其中,_afxBaseModuleState.GetData()返回的是应用程序的全局模块状态。 OLE 使用的模块切换方法有所不同,这里不作讨论。 上面讨论了线程执行行不同模块的代码时切换模块状态的情况。在线程创建时怎么处理模块 状态呢? z 一个进程(使用 MFC 的应用程序)的主线程创建线程模块状态和进程模块状态,前 者是_AFX_THREAD_STATE 类的实例,后者是_AFX_BASE_MODULE_STATE 类的实 例。 z 当进程的新的线程被创建时,它创建自己的线程状态,继承父线程的模块状态。 在线程的入口函数_AfxThreadEntry 完成这样的处理,该函数的描述见 8.5.3 节。 9.4.2 扩展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 时才有用。下面,对这些代码进行分析和解释 9.4.2.1 _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 的初始化函数将使用扩展模块状态信息。下面,讨论初始化函数的作用。 9.4.2.2 扩展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 m_classList; #ifndef _AFX_NO_OLE_SUPPORT CTypedSimpleList 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 模块的输出函数或者输出类。 9.4.3 核心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 的模块状态,见下一节的讨论。 9.4.4 动态链接的规则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 是静态链接的,所以不必考虑状态切换。 9.5 状态信息的作用 在分析了 MFC 模块状态的实现基础和管理机制之后,现在对状态信息的作用进行专门的讨 论。 9.5.1.1 模块信息的保存和管理 传统上,线程状态、模块状态等包含的信息是全局变量,但是为了支持 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 对象链接到主模块的模块状态。 资源句柄 实例句柄 应用程序名 是否是 DLL 模块 是否系统模块 … CRuntimeClass 对象列表 COleObjectFactory 对象列表 CDynLinkLibrary 对象列表 … 模块-线程状态 模块状态 DLL 模块句柄 DLL 资源句柄 是否系统模块 CRuntimeClass COleObjectFacto 指向下一个同类对象 对象列表 ry 对象列表 DLL1 的对象 1 DLL2 的对象 2 … DLLi 的对象 i CDynLinkLi … DLLn 的对象 n brary 对象列表 图 9-8 模块状态的一个用途 一个 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 表搜索相应的对象。下面两节举例说明。 9.5.2 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 对象都会被搜索到。 9.5.3 模块信息的显示 遍历模块状态和 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 函数的处理,只不过增加了模块名和模块句柄 信息。 9.5.4 模块-线程状态的作用 由模块-线程状态类的定义可知,一个模块-线程状态包含了几类 Windows 对象—MFC 对象 的映射。下面讨论它们的作用。 9.5.4.1 只能访问本线程MFC对象的原因 MFC 规定: (1) 不能从一个非 MFC 线程创建和访问 MFC 对象 如果一个线程被创建时没有用到 CWinThread 对象,比如,直接使用“C”的_beginthread 或 者_beginthreadex创建的线程,则该线程不能访问MFC 对象;换句话说,只有通过CWinThread 创建 MFC 线程对象和 Win32 线程,才可能在创建的线程中使用 MFC 对象。 (2) 一个线程仅仅能访问它所创建的 MFC 对象 这两个规定的原因是: 为了防止多个线程并发地访问同一个 MFC 对象,MFC 对象和 Windows 对象之间有一个一 一对应的关系,这种关系以映射的形式保存在创建线程的当前模块的模块-线程状态信息中。 当一个线程使用某个 MFC 对象指针 P 时,ASSERT_VALID(P)将验证当前线程的当前模块是 否有 Windows 句柄和 P 对应,即是否创建了 P 所指的 Windows 对象,验证失败导致 ASSERT 断言中断程序的执行。如果一个线程要使用其他线程的 Windows 对象,则必须传递 Windows 对象句柄,不能传递 MFC 对象指针。 当然一般来说,MFC 应用程序仅仅在 Debug 版本下才检查这种映射关系,所以访问其他线 程的 MFC 对象的程序在 Realease 版本下表面上不会有问题,但是 MFC 对象被并发访问的 后果是不可预见的。 9.5.4.2 实现MFC对象和Windows对象之间的映射 MFC 提供了几个函数完成 MFC 对象和 Windows 对象之间的映射或者解除这种映射关系, 以及从 MFC 对象得到 Windows 对象或者从 Windows 对象得到或创建相应的 MFC 对象。 每一个 MFC 对象类都有成员函数 Attach 和 Detach,FromHandle 和 FromHandlePermanent, AssertValid。这些成员函数的形式如下: z Attach(HANDLE Windows_Object_Handle) 例如:CWnd 类的是 Attach(HANLDE hWnd),CDC 类的是 Attach(HDC hDc)。 Attach 用来把一个句柄永久性(Perment)地映射到一个 MFC 对象上:它把一个 Windows 对象 捆绑(Attach)到一个 MFC 对象上,MFC 对象的句柄成员变量赋值为 Windows 对象句柄,该 MFC 对象应该已经存在,但是句柄成员变量为空。 z Detach() Detach 用来取消 Windows 对象到 MFC 对象的永久性映射。如果该 Windows 对象有一个临 时的映射存在,则 Detach 不理会它。MFC 让线程的 Idle 清除临时映射和临时 MFC 对象。 z FromHandle(HANDLE Windows_Object) 它是一个静态成员函数。如果该 Windows 对象没有映射到一个 MFC 对象,FromHandle 则 创建一个临时的 MFC 对象,并把 Windows 对象映射到临时的 MFC 对象上,然后返回临时 MFC 对象。 z FromHandlePermanent(HANDLE Windows_Object) 它是一个静态成员函数。如果该 Windows 对象没有永久地映射到一个 MFC 对象上,则返回 NULL,否则返回对应的 MFC 对象。 z AssertValid() 它是从 CObject 类继承来的虚拟函数。MFC 覆盖该函数,实现了至少一个功能:判断当前 MFC 对象的指针 this 是否映射到一个对应的可靠的 Windows 对象。 图 9-9 示意了 MFC 对映射结构的实现层次,对图 9-9 解释如下。 AssertValid: CWnd::Asse …. 第一层,映射操作界面: rtValid 创建或得到一个映 射: AfxMapHWND AfxMapHDC … Attach/Detach CWnd::Attach CWnd::Detach … CGdiObject::Att ach CGdiObject::De tach … FromHandle/ FromHandle CWnd::From CWnd::From … Perment: Handle HandlePerment CHandleMap 永久性映射 CHandleMap CHandleMap::m_permanentMap CHandleMap::m_t 临时性映射 emporaryMap CMapPtrToPtr: 插入、删除、查询映射操作 第三层、指针-指针映射结构的底层实现 第二层,Windows Object-MFC OBJECT 的映射结构 CHandleMap 的操作 构造函数、添加或删除一个映射、查找一个映射等 图 9-9 MFC 对象和 Windows 对象之间的映射的实现 删除临时映射: CWnd::DeleteTemp Map … 图中上面的虚线框表示使用映射关系的高层调用,包括上面讲述的几类函数。MFC 和应用 程序通过它们创建、销毁、使用映射关系。 图中中间的虚线框表示 MFC 使用 CHandleMap 类实现对映射关系的管理。一个 CHandleMap 对象可以通过两个成员变量来管理两种映射数据:临时映射和永久映射。模块-线程状态给 每一类 MFC 对象分派一个 CHandleMap 对象来管理其映射数据(见模块-线程类的定义),例 如 m_pmapHWND 所指对象用来保存 CWnd 对象(或派生类对象)和 Windows window 之间 的映射。 下面的虚线框表示映射关系的最底层实现,MFC 使用通用类 CMapPtrToPtr 来管理 MFC 对 象指针和 Windows 句柄之间的映射数据。 对本节总结如下: z MFC 的映射数据保存在模块-线程状态中,是线程和模块局部的。每个线程管理自 己映射的数据,其他线程不能访问到本线程的映射数据,也就不允许使用本线程的 MFC 对象。 z 每一个 MFC 对象类(CWnd、CDC 等)负责创建或者管理这类线程-模块状态的对 应 CHandleMap 类对象。例如,CWnd::Attach 创建一个永久性的映射保存在 m_pmapHwnd 所指对象中,如果 m_pmapHand 还没有创建,则使用 AfxMapHWND 创建相应的 CHandleMap 对象。 z 映射分两类:永久性的或者临时的。 9.5.4.3 临时对象的处理 在 2.4 节就曾经提到了临时对象,现在是深入了解它们的时候了。 第一, 临时对象指 MFC 对象,是 MFC 或者程序员使用 FromHandle 或者 SelectObject 等 从一个 Windows 对象句柄创建的对应的 MFC 对象。 第二, 在模块-线程状态中,临时 MFC 对象的映射是和永久映射分开保存的。 第三, 临时 MFC 对象在使用完毕后由 MFC 框架自动删除,MFC 在线程的 Idle 处理中删 除本线程的临时 MFC 对象,为了防止并发修改,通过线程状态 m_nTempMapLock (等于 0,可以修改,大于 0,等待)来同步。所以,临时 MFC 对象不能保存备用。 9.6 状态对象的删除和销毁 至此,本章讨论了 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 所指对象销毁之后,所有的状态 相关的内存都被释放。
还剩29页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

xwsn007

贡献于2012-12-19

下载需要 5 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf