MFC教程(9)-- MFC的进程和线程(1)
2007-09-05 00:34
218 查看
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_SU
10379
PPORT
_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的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_SU
10379
PPORT
_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 教程【8_MFC的进程和线程 】
- MFC深入浅出教程之八------MFC的进程和线程
- MFC教程(9)-- MFC的进程和线程(2)
- MFC的进程和线程,非正常终止
- vc/mfc 进程消息队列,线程消息队列,和系统消息队列,该如何处理
- MFC的进程和线程
- 第八讲 MFC的进程和线程
- 猎豹MFC--进程和线程--创建线程AfxBeginThread() SetDlgItemInt()线程暂停继续终止
- MFC的进程和线程
- MFC的进程和线程,非正常终止
- MFC的进程和线程
- vc++笔记-----MFC环境下的多任务、进程和线程
- MFC的进程和线程
- Java并发教程-1进程和线程
- C++/MFC-进程/线程亲缘性
- MFC第二课——Console程序、进程和线程
- MFC的进程与线程
- 廖雪峰Python教程1轮还没学明白的(5) -- 进程和线程 -- 多进程
- 8. MFC的进程和线程
- MFC的进程和线程