⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mfc

📁 很好的MFC书籍
💻
📖 第 1 页 / 共 5 页
字号:
      <P align=justify></P>
      <P align=justify>// Implementation</P>
      <P align=justify>public:</P>
      <P align=justify>static CNoTrackObject* AFXAPI CreateObject()</P>
      <P align=justify>{ return new TYPE; }</P>
      <P align=justify>};</P>
      <P align=justify>类似于线程局部对象,每一个进程局部变量都有一个对应的全局CProcessLocal模板对象。</P>
      <P align=justify></P>
      <LI><A name=_Toc445889108></A><A name=_Toc445782511></A><A 
      name=_Toc452640972></A><A name=_Toc457299073></A><B>状态对象的创建</B> 
      <P></P>
      <OL>
        <P align=justify>
        <LI><B><A name=_Toc457299074></A>状态对象的创建过程</B> 
        <P></P></LI></OL></LI></OL></OL></OL>
<P align=justify>回顾前一节的三个定义:</P>
<DIR>
<P align=justify>CThreadSlotData* _afxThreadData;</P>
<P align=justify>THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)</P>
<P align=justify>PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, 
_afxBaseModuleState)</P></DIR>
<P 
align=justify>第一个仅仅定义了一个指针;第二和第三个定义了一个模板类的实例。相应的CThreadSlotData对象(全局)、_AFX_THREAD_STATE对象(线程局部)以及_AFX_BASE_MODULE_STATE对象(进程局部)并没有创建。当然,模块状态对象的成员模块-线程对象也没有被创建。这些对象要到第一次被访问时,才会被创建,这样做会提高加载DLL的速度。</P>
<P align=justify>下面以一个动态链接到MFC DLL的单模块应用程序为例,说明这些对象的创建过程。</P>
<P align=justify>当第一次访问状态信息时,比如使用 
AfxGetModuleState得到模块状态,导致系列创建过程的开始,如图9-7所示。</P><IMG height=748 hspace=12 
src="MFC教程9_MFC的状态.files/image149.gif" width=397 align=left> 
<P align=justify></P>
<P 
align=justify>首先分析语句pState=_afxThreadState。如果_afxThreadData、线程状态和模块状态还没有创建,该语句可以导致这些数据的创建。</P>
<P 
align=justify>pState声明为CNoTrackObject对象的指针,_afxThreadState声明为一个模板CThreadLocal的实例,pState=_afxThreadData为什么可以通过编译器的检查呢?因为CThreadLocal模板重载了操作符“”*”和“-&gt;”,这两个运算返回CNoTrackObject类型的对象。回顾3.2节CThreadLocalObject、CThreadLocal的定义,这两个操作符运算到最后都是调用CThreadLocalObject的成员函数GetData。</P>
<P align=justify></P>
<UL>
  <P align=justify>
  <LI>创建_afxThreadData所指对象和线程状态 
  <P></P></LI></UL>
<P 
align=justify>CThreadLocalObject::GetData用来获取线程局部变量(这个例子中是线程状态)的值,其参数用来创建动态的线程局部变量。图9-7的上面的虚线框表示其流程:</P>
<P 
align=justify>它检查成员变量m_nSlot是否等于0(线程局部变量是否曾经被分配了MFC线程私有空间槽位),检查全局变量_afxTheadData指针是否为空。如果_afxThreadData空,则创建一个CThreadSlotData类对象,让_afxThreadData指向它,这样本程序的MFC线程局部存储的管理者被创建。如果m_nSlot等于0,则让_afxThreadDtata调用AllocSlot分配一个槽位并把槽号保存在m_nSlot中。</P>
<P 
align=justify>得到了线程局部变量(线程状态)所占用的槽位后,委托_afxThreadData调用GetThreadValue(m_nSlot)得到线程状态值(指针)。如果结果非空,则返回它;如果结果是NULL,则表明该线程状态还没有被创建,于是使用参数创建一个动态的线程状态,并使用SetValue把其指针保存在槽m_nSlot中,返回该指针。</P>
<P align=justify></P>
<UL>
  <P align=justify>
  <LI>创建模块状态 
  <P></P></LI></UL>
<P 
align=justify>得到了线程状态的值后,通过它得到模块状态m_pModuleState。如果m_pModuleState为空,表明该线程状态是才创建的,其许多成员变量还没有赋值,程序的进程模块状态还没有被创建。于是调用函数_afxBaseModule.GetData,导致进程模块状态被创建。</P>
<P align=justify>图9-7的下面一个虚线框表示了CProcessLocalObject::GetData的创建过程:</P>
<P 
align=justify>_afxBaseModule首先检查成员变量m_pObject是否空,如果非空就返回它,即进程模块状态指针;否则,在堆中创建一个动态的_AFX_BASE_MODULE_STATE对象,返回。</P>
<P 
align=justify>从上述两个GetData的实现可以看出,CThreadLocal模板对象负责线程局部变量的创建和管理(查询,修改,删除);CProcessLocal模板对象负责进程局部变量的创建和管理(查询,修改,删除)。</P>
<UL>
  <P align=justify>
  <LI>模块-线程状态的创建 
  <P></P></LI></UL>
<P 
align=justify>模块状态的成员模块-线程状态m_thread的创建类似于线程状态的创建:当第一次访问m_thread所对应的CThreadLocal模板对象时,给m_thread分配MFC线程局部存储的私有槽号m_nSlot,并动态地创建_AFX_MODULE_THREAD_STATE对象,保存对象指针在m_nSlot槽中。</P>
<OL>
  <OL>
    <OL>
      <OL>
        <P align=justify>
        <LI><A name=_Toc457299075></A><B>创建过程所涉及的几个重要函数的算法</B> 
        <P></P></LI></OL></OL></OL></OL>
<P align=justify>创建过程所涉及的几个重要函数的算法描述如下:</P>
<OL>
  <P align=justify>
  <LI>AllocSlot 
  <P></P>
  <P 
  align=justify>AllocSlot用来分配线程的MFC私有存储空间的槽号。由于该函数要修改全局变量_afxThreadData,所以必须使用m_sect关键段对象来同步多个线程对该函数的调用。</P>
  <P align=justify>CThreadSlotData::AllocSlot()</P>
  <P align=justify>{</P>
  <P align=justify>进入关键段代码(EnterCriticalSection(m_sect);)</P>
  <P align=justify>搜索m_pSlotData,查找空槽(SLOT)</P>
  <P align=justify>如果不存在空槽(第一次进入时,肯定不存在)</P>
  <P align=justify>分配或再分配内存以创建新槽,</P>
  <P align=justify>指针m_pSlotData指向分配的地址。</P>
  <P align=justify>得到新槽(SLOT)</P>
  <P align=justify>标志该SLOT为已用</P>
  <P align=justify>记录最新可用的SLOT到成员变量m_nRover中。</P>
  <P align=justify>离开关键段代码(LeaveCriticalSection(m_sect);)</P>
  <P align=justify>返回槽号</P>
  <P align=justify>}</P>
  <P align=justify></P>
  <LI>GetThreadValue 
  <P></P>
  <P 
  align=justify>GetThreadValue用来获取调用线程的第slot个线程局部变量的值。每一个线程局部变量都占用一个且只一个槽位。</P>
  <P align=justify>CThreadSlotData::GetThreadValue(int slot)</P>
  <P align=justify>{</P>
  <P align=justify>//得到一个CThreadData型的指针pData</P>
  <P align=justify>//pData指向MFC线程私有存储空间。</P>
  <P align=justify>//m_tlsIndex在_afxThreadData创建时由构造函数创建</P>
  <P align=justify>pData=(CThreadData*)TlsGetValue(m_tlsIndex),。</P>
  <P align=justify>如果指针空或slot&gt;pData-&gt;nCount, 则返回空。</P>
  <P align=justify>否则,返回pData</P>
  <P align=justify>}</P>
  <P align=justify></P>
  <LI>SetValue 
  <P></P></LI></OL>
<P align=justify>SetValue用来把调用线程的第slot个线程局部变量的值(指针)存放到线程的MFC私有存储空间的第slot个槽位。</P>
<P align=justify>CThreadSlotData::SetValue(int slot, void *pValue)</P>
<P align=justify>{</P>
<P align=justify>//通过TLS索引得到线程的MFC私有存储空间</P>
<DIR>
<P align=justify>pData = (CThreadData*)TlsGetValue(m_tlsIndex)</P>
<P align=justify></P>
<P align=justify>//没有得到值或者pValue非空且当前槽号,即</P>
<P align=justify>//线程局部变量的个数</P>
<P align=justify>//大于使用当前局部变量的线程个数时</P>
<P align=justify>if (pData NULL or slot &gt; pData-&gt;nCount &amp;&amp; 
pValue!=NULL)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>if pData NULL //当前线程第一次访问该线程局部变量</P>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>创建一个CThreadData实例;</P>
<P align=justify>添加到CThreadSlotData::m_list;</P>
<P align=justify>令pData指向它;</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify>按目前为止,线程局部变量的个数为pData-&gt;pData分配或重分配内存,</P>
<P align=justify>用来容纳指向真正线程数据的指针</P>
<P align=justify>调用TlsSetValue(pData)保存pData</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify></P>
<DIR>
<P align=justify>//把指向真正线程数据的pValue保存在pData对应的slot中</P>
<P align=justify>pData-&gt;pData[slot] = pValue</P></DIR>
<P align=justify>}</P>
<OL>
  <OL>
    <P align=justify>
    <LI><A name=_Toc445889109></A><A name=_Toc445782512></A><A 
    name=_Toc452640973></A><A name=_Toc457299076></A><B>管理状态</B> 
    <P></P>
    <P align=justify>在描述了MFC状态的实现机制之后,现在来讨论MFC的状态管理和相关状态的作用。</P>
    <OL>
      <P align=justify>
      <LI><A name=_Toc445889110></A><A name=_Toc445782513></A><A 
      name=_Toc452640974></A><A name=_Toc457299077></A><B>模块状态切换</B> 
      <P></P></LI></OL></LI></OL></OL>
<P align=justify>模块状态切换就是把当前线程的线程状态的m_pModuleState指针指向即将运行模块的模块状态。</P>
<P 
align=justify>MFC使用AFX_MANAGE_STATE宏来完成模块状态的切换,即进入模块时使用当前模板的模板状态,并保存原模板状态;退出模块时恢复原来的模块状态。这相当于状态的压栈和出栈。实现原理如下。</P>
<P align=justify>先看MFC关于AFX_MANAGE_STATE的定义:</P>
<P align=justify>#ifdef _AFXDLL</P>
<DIR>
<P align=justify>struct AFX_MAINTAIN_STATE</P>
<P align=justify>{</P>
<P align=justify>AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pModuleState);</P>
<P align=justify>~AFX_MAINTAIN_STATE();</P>
<P align=justify>protected:</P>
<P align=justify>AFX_MODULE_STATE* m_pPrevModuleState;</P>
<P align=justify>};</P>
<P align=justify>//AFX_MANAGE_STATE宏的定义:</P>
<P align=justify>#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE 
_ctlState(p);</P></DIR>
<P align=justify>#else // _AFXDLL</P>
<DIR>
<P align=justify>#define AFX_MANAGE_STATE(p)</P></DIR>
<P align=justify>#endif //!_AFXDLL</P>
<P align=justify>如果使用MFC 
DLL,MFC提供类AFX_MAINTAIN_STATE来实现状态的压栈和出栈,AFX_MANAGE_SATATE宏的作用是定义一个AFX_MAINTAIN_STATE类型的局部变量_ctlState。</P>
<P 
align=justify>AFX_MAINTAIN_STATE的构造函数在其成员变量m_pPrevModuleState中保存当前的模块状态对象,并把参数指定的模块状态设定为当前模块状态。所以该宏作为入口点的第一条语句就切换了模块状态。</P>
<P 
align=justify>在退出模块时,局部变量_ctlState将自动地销毁,这导致AFX_MAINTAIN_STATE的析构函数被调用,析构函数把保存在m_pPrevModuleState的状态设置为当前状态。</P>
<P align=justify></P>
<P align=justify>AFX_MANAGE_SATATE的参数在不同场合是不一样的,例如,</P>
<UL>
  <P align=justify>
  <LI>DLL的输出函数使用 
  <P></P></LI></UL>
<P align=justify>AFX_MANAGE_SATATE(AfxGetStaticModuleState());</P>
<P align=justify>其中,AfxGetStaticModuleState返回DLL的模块状态afxModuleState。</P>
<UL>
  <P align=justify>
  <LI>窗口函数使用 
  <P></P></LI></UL>
<P align=justify>AFX_MANAGE_STATE(_afxBaseModuleState.GetData());</P>
<P align=justify>其中,_afxBaseModuleState.GetData()返回的是应用程序的全局模块状态。</P>
<P align=justify>OLE使用的模块切换方法有所不同,这里不作讨论。</P>
<P align=justify></P>
<P align=justify>上面讨论了线程执行行不同模块的代码时切换模块状态的情况。在线程创建时怎么处理模块状态呢?</P>
<UL>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -