【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

楼主: abc

MFC 教程

[复制链接]
 楼主| 发表于 2006-12-8 10:39:00 | 显示全部楼层
<OL>
<LI>
<><B>Socket的使用</B> </P>
<p></LI></OL>
< align=justify>WinSock以DLL的形式提供,在调用任何WinSock API之前,必须调用函数WSAStartup进行初始化,最后,调用函数WSACleanUp作清理工作。</P>
< align=justify>MFC使用函数AfxSocketInit包装了函数WSAStartup,在WinSock应用程序的初始化函数IninInstance中调用AfxSocketInit进行初始化。程序不必调用WSACleanUp。</P>
< align=justify></P>
< align=justify>Socket是网络通信过程中端点的抽象表示。Socket在实现中以句柄的形式被创建,包含了进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。</P>
< align=justify>要使用socket,首先必须创建一个socket;然后,按要求配置socket;接着,按要求通过socket接收和发送数据;最后,程序关闭此socket。</P>
<UL>
< align=justify>
<LI>为了创建socket,使用socket函数得到一个socket句柄:
<p></LI></UL>
< align=justify>socket_handle = socket(protocol_family. Socket_type, protocol);</P>
< align=justify>其中:protocol_family指定socket使用的协议,取值PF_INET,表示Internet(TCP/IP)协议族;Socket_type指socket面向连接或者使用数据报;第三个参数表示使用TCP或者UDP协议。</P>
< align=justify>当一个socket被创建时,WinSock将为一个内部结构分配内存,在此结构中保存此socket的信息,到此,socket连接使用的协议已经确定。</P>
<UL>
<P align=justify>
<LI>创建了socket之后,配置socket。
<p></LI></UL>
<P align=justify>对于面向连接的客户,WinSock自动保存本地IP地址和选择协议端口,但是必须使用connect函数配置远地IP地址和远地协议端口:</P>
<P align=justify>result = connect(socket_handle, remote_socket_address, address_length)</P>
<P align=justify>remote_socket_address是一个指向特定socket结构的指针,该地址结构为socket保存了地址族、协议端口、网络主机地址。</P>
<P align=justify>面向连接的服务器则使用bind指定本地信息,使用listen和accept获取远地信息。</P>
<P align=justify>使用数据报的客户或者服务器使用bind给socket指定本地信息,在发送或者接收数据时指定远地信息。</P>
<P align=justify>bind给socket指定一个本地IP地址和协议端口,如下:</P>
<P align=justify>result = bind( socket_hndle, local_socket_address, address_length)</P>
<P align=justify>参数类型同connect。</P>
<P align=justify>函数listen监听bind指定的端口,如果有远地客户请求连接,使用accept接收请求,创建一个新的socket,并保存信息。</P>
<P align=justify>socket_new = accept(socket_listen, socket_address, address_length)</P>
<UL>
<P align=justify>
<LI>在socket配置好之后,使用socket发送或者接收数据。
<p></LI></UL>
<P align=justify>面向连接的socket使用send发送数据,recv接收数据;</P>
<P align=justify>使用数据报的socket使用sendto发送数据,recvfrom接收数据。</P>
<OL>
<OL>
<P align=justify>
<LI><B>MFC对WinSockt API的封装</B>
<p>
<P align=justify>MFC提供了两个类CAsyncSocket和CSocket来封装WinSock API,这给程序员提供了一个更简单的网络编程接口。</P>
<P align=justify>CAsyncSocket在较低层次上封装了WinSock API,缺省情况下,使用该类创建的socket是非阻塞的socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻塞操作。</P>
<P align=justify>CSocket建立在CAsyncSocket的基础上,是CAsyncSocket的派生类。也就是缺省情况下使用该类创建的socket是非阻塞的socket,但是CSocket的网络I/O是阻塞的,它在完成任务之后才返回。CSocket的阻塞不是建立在“阻塞”socket的基础上,而是在“非阻塞”socket上实现的阻塞操作,在阻塞期间,CSocket实现了本线程的消息循环,因此,虽然是阻塞操作,但是并不影响消息循环,即用户仍然可以和程序交互。</P>
<OL>
<P align=justify>
<LI><B>CAsyncSocket</B>
<p>
<P align=justify>CAsyncSocket封装了低层的WinSock API,其成员变量m_hSocket保存其对应的socket句柄。使用CAsyncSocket的方法如下:</P>
<P align=justify>首先,在堆或者栈中构造一个CAsyncSocket对象,例如:</P>
<P align=justify>CAsyncSocket sock;或者</P>
<P align=justify>CAsyncSocket *pSock = new CAsyncSocket;</P>
<P align=justify>其次,调用Create创建socket,例如:</P>
<P align=justify>使用缺省参数创建一个面向连接的socket</P>
<P align=justify>sock.Create() </P>
<P align=justify>指定参数参数创建一个使用数据报的socket,本地端口为30</P>
<P align=justify>pSocket.Create(30, SOCK_DGRM);</P>
<P align=justify>其三,如果是客户程序,使用Connect连接到远地;如果是服务程序,使用Listen监听远地的连接请求。</P>
<P align=justify>其四,使用成员函数进行网络I/O。</P>
<P align=justify>最后,销毁CAsyncSocket,析构函数调用Close成员函数关闭socket。</P>
<P align=justify></P>
<P align=justify>下面,分析CAsyncSocket的几个函数,从中可以看到它是如何封装低层的WinSock API,简化有关操作的;还可以看到它是如何实现非阻塞的socket和非阻塞操作。</P>
<P align=justify></P>
<LI><B>socket对象的创建和捆绑</B>
<p>
<P align=justify>(1)Create函数</P>
<P align=justify>首先,讨论Create函数,分析socket句柄如何被创建并和CAsyncSocket对象关联。Create的实现如下:</P>
<P align=justify>BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,</P>
<P align=justify>long lEvent, LPCTSTR lpszSocketAddress)</P>
<P align=justify>{</P>
<P align=justify>if (Socket(nSocketType, lEvent))</P>
<P align=justify>{</P>
<P align=justify>if (Bind(nSocketPort,lpszSocketAddress))</P>
<P align=justify>return TRUE;</P>
<P align=justify>int nResult = GetLastError();</P>
<P align=justify>Close();</P>
<P align=justify>WSASetLastError(nResult);</P>
<P align=justify>}</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>参数1表示本socket的端口,缺省是0,如果要创建数据报的socket,则必须指定一个端口号。</P>
<P align=justify>参数2表示本socket的类型,缺省是SOCK_STREAM,表示面向连接类型。</P>
<P align=justify>参数3是屏蔽位,表示希望对本socket监测的事件,缺省是FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE。</P>
<P align=justify>参数4表示本socket的IP地址字符串,缺省是NULL。</P>
<P align=justify>Create调用Socket函数创建一个socket,并把它捆绑在this所指对象上,监测指定的网络事件。参数2和3被传递给Socket函数,如果希望创建数据报的socket,不要使用缺省参数,指定参数2是SOCK_DGRM。</P>
<P align=justify>如果上一步骤成功,则调用bind给新的socket分配端口和IP地址。</P>
<P align=justify>(2)Socket函数</P>
<P align=justify>接着,分析Socket函数,其实现如下:</P>
<P align=justify>BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,</P>
<P align=justify>int nProtocolType, int nAddressFormat)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(m_hSocket == INVALID_SOCKET);</P>
<P align=justify></P>
<P align=justify>m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);</P>
<P align=justify>if (m_hSocket != INVALID_SOCKET)</P>
<P align=justify>{</P>
<P align=justify>CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);</P>
<P align=justify>return AsyncSelect(lEvent);</P>
<P align=justify>}</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>参数1表示Socket类型,缺省值是SOCK_STREAM。</P>
<P align=justify>参数2表示希望监测的网络事件,缺省值同Create,指定了全部事件。</P>
<P align=justify>参数3表示使用的协议,缺省是0。实际上,SOCK_STREAM类型的socket使用TCP协议,SOCK_DGRM的socket则使用UDP协议。</P>
<P align=justify>参数4表示地址族(地址格式),缺省值是PF_INET(等同于AF_INET)。对于TCP/IP来说,协议族和地址族是同值的。</P>
<P align=justify>在socket没有被创建之前,成员变量m_hSocket是一个无效的socket句柄。Socket函数把协议族、socket类型、使用的协议等信息传递给WinSock API函数socket,创建一个socket。如果创建成功,则把它捆绑在this所指对象。</P>
<P align=justify>(3)捆绑(Attatch)</P>
<P align=justify>捆绑过程类似于其他Windows对象,将在模块线程状态的WinSock映射中添加一对新的映射:this所指对象和新创建的socket对象的映射。</P>
<P align=justify>另外,如果本模块线程状态的“socket窗口”没有创建,则创建一个,该窗口在异步操作时用来接收WinSock的通知消息,窗口句柄保存到模块线程状态的m_hSocketWindow变量中。函数AsyncSelect将指定该窗口为网络事件消息的接收窗口。</P>
<P align=justify>函数AttachHandle的实现在此不列举了。</P>
<P align=justify>(4)指定要监测的网络事件</P>
<P align=justify>在捆绑完成之后,调用AsyncSelect指定新创建的socket将监测的网络事件。AsyncSelect实现如下:</P>
<P align=justify>BOOL CAsyncSocket::AsyncSelect(long lEvent)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(m_hSocket != INVALID_SOCKET);</P>
<P align=justify></P>
<P align=justify>_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;</P>
<P align=justify>ASSERT(pState-&gt;m_hSocketWindow != NULL);</P>
<P align=justify></P>
<P align=justify>return WSAAsyncSelect(m_hSocket, pState-&gt;m_hSocketWindow,</P>
<P align=justify>WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;</P>
<P align=justify>}</P>
<P align=justify>函数参数lEvent表示希望监视的网络事件。</P>
<P align=justify>_ afxSockThreadState得到的是当前的模块线程状态,m_ hSocketWindow是本模块在当前线程的“socket窗口”,指定监视m_hSocket的网络事件,如指定事件发生,给窗口m_hSocketWindow发送WM_SOCKET_NOTIFY消息。</P>
<P align=justify>被指定的网络事件对应的网络I/O将是异步操作,是非阻塞操作。例如:指定FR_READ导致Receive是一个异步操作,如果不能立即读到数据,则返回一个错误WSAEWOULDBLOCK。在数据到达之后,WinSock通知窗口m_hSocketWindow,导致OnReceive被调用。</P>
<P align=justify>指定FR_WRITE导致Send是一个异步操作,即使数据没有送出也返回一个错误WSAEWOULDBLOCK。在数据可以发送之后,WinSock通知窗口m_hSocketWindow,导致OnSend被调用。</P>
<P align=justify>指定FR_CONNECT导致Connect是一个异步操作,还没有连接上就返回错误信息WSAEWOULDBLOCK,在连接完成之后,WinSock通知窗口m_hSocketWindow,导致OnConnect被调用。</P>
<P align=justify>对于其他网络事件,就不一一解释了。</P>
<P align=justify>所以,使用CAsyncSocket时,如果使用Create缺省创建socket,则所有网络I/O都是异步操作,进行有关网络I/O时则必须覆盖以下的相关函数:</P>
<P align=justify>OnAccept、OnClose、OnConnect、OnOutOfBandData、OnReceive、OnSend。</P>
<P align=justify>(5)Bind函数</P>
<P align=justify>经过上述过程,socket创建完毕,下面,调用Bind函数给m_hSocket指定本地端口和IP地址。Bind的实现如下:</P>
<P align=justify>BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress)</P>
<P align=justify>{</P>
<P align=justify>USES_CONVERSION;</P>
<P align=justify></P>
<P align=justify>//使用WinSock的地址结构构造地址信息</P>
<P align=justify>SOCKADDR_IN sockAddr;</P>
<P align=justify>memset(&amp;sockAddr,0,sizeof(sockAddr));</P>
<P align=justify></P>
<P align=justify>//得到地址参数的值</P>
<P align=justify>LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress);</P>
<P align=justify>//指定是Internet地址类型</P>
<P align=justify>sockAddr.sin_family = AF_INET;</P>
<P align=justify></P>
<P align=justify>if (lpszAscii == NULL)</P>
<P align=justify>//没有指定地址,则自动得到一个本地IP地址</P>
<P align=justify>//把32比特的数据从主机字节序转换成网络字节序</P>
<P align=justify>sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>//得到地址</P>
<P align=justify>DWORD lResult = inet_addr(lpszAscii);</P>
<P align=justify>if (lResult == INADDR_NONE)</P>
<P align=justify>{</P>
<P align=justify>WSASetLastError(WSAEINVAL);</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>sockAddr.sin_addr.s_addr = lResult;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//如果端口为0,则WinSock分配一个端口(1024—5000)</P>
<P align=justify>//把16比特的数据从主机字节序转换成网络字节序</P>
<P align=justify>sockAddr.sin_port = htons((u_short)nSocketPort);</P>
<P align=justify></P>
<P align=justify>//Bind调用WinSock API函数bind</P>
<P align=justify>return Bind((SOCKADDR*)&amp;sockAddr, sizeof(sockAddr));</P>
<P align=justify>}</P>
<P align=justify>其中:函数参数1指定了端口;参数2指定了一个包含本地地址的字符串,缺省是NULL。</P>
<P align=justify>函数Bind首先使用结构SOCKADDR_IN构造地址信息。该结构的域sin_family表示地址格式(TCP/IP同协议族),赋值为AF_INET(Internet地址格式);域sin_port表示端口,如果参数1为0,则WinSock分配一个端口给它,范围在1024和5000之间;域sin_addr是表示地址信息,它是一个联合体,其中s_addr表示如下形式的字符串,“28.56.22.8”。如果参数没有指定地址,则WinSock自动地得到本地IP地址(如果有几个网卡,则使用其中一个的地址)。</P>
<P align=justify>(6)总结Create的过程</P>
<P align=justify>首先,调用socket函数创建一个socket;然后把创建的socket对象映射到CAsyncSocket对象(捆绑在一起),指定本socket要通知的网络事件,并创建一个“socket窗口”来接收网络事件消息,最后,指定socket的本地信息。</P>
<P align=justify>下一步,是使用成员函数Connect连接远地主机,配置socket的远地信息。函数Connect类似于Bind,把指定的远地地址转换成SOCKADDR_IN对象表示的地址信息(包括网络字节序的转换),然后调用WinSock函数Connect连接远地主机,配置socket的远地端口和远地IP地址。</P>
<P align=justify></P></LI></OL></LI></OL></OL>
 楼主| 发表于 2006-12-8 10:39:35 | 显示全部楼层
<OL>
<LI>
<><B>异步网络事件的处理</B> </P>
<p></LI>
<LI>
<DIV align=justify>当网络事件发生时,“socket窗口”接收WM_SOCKET_NOTIFY消息,消息处理函数OnSocketNotify被调用。“socket窗口”的定义和消息处理是MFC实现的,这里不作详细的讨论。</DIV></LI>
<LI>
<DIV align=justify>OnSocketNotify回调CAsyncSocket的成员函数DoCallBack,DoCallBack调用事件处理函数,如OnRead、OnWrite等。摘录DoCallBack的一段代码如下:</DIV></LI>
<LI>
<DIV align=justify>switch (WSAGETSELECTEVENT(lParam))</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>case FD_READ:</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>DWORD nBytes;</DIV></LI>
<LI>
<DIV align=justify>//得到可以一次读取的字节数</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;IOCtl(FIONREAD, &amp;nBytes);</DIV></LI>
<LI>
<DIV align=justify>if (nBytes != 0)</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnReceive(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>}</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify>case FD_WRITE:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnSend(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify>case FD_OOB:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnOutOfBandData(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify>case FD_ACCEPT:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnAccept(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify>case FD_CONNECT:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnConnect(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify>case FD_CLOSE:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;OnClose(nErrorCode);</DIV></LI>
<LI>
<DIV align=justify>break;</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>lParam是WM_SOCKET_NOFITY的消息参数,OnSocketNotify传递给函数DoCallBack,表示通知事件。</DIV></LI>
<LI>
<DIV align=justify>函数IOCtl是CAsyncSocket的成员函数,用来对socket的I/O进行控制。这里的使用表示本次调用Receive函数至多可以读nBytes个字节。</DIV></LI>
<LI>
<DIV align=justify>从上面的讨论可以看出,从创建socket到网络I/O,CAsyncSocket直接封装了低层的WinSock API,简化了WinSock编程,实现了一个异步操作的界面。如果希望某个操作是阻塞操作,则在调用Create时不要指定该操作对应的网络事件。例如,希望Connect和Send是阻塞操作,在任务完成之后才返回,则可以使用如下的语句:</DIV></LI>
<LI>
<DIV align=justify>pSocket-&gt;Create(0, SOCK_STREAM, </DIV></LI>
<LI>
<DIV align=justify>FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE);</DIV></LI>
<LI>
<DIV align=justify>这样,在Connect和Send时,如果是用户界面线程的话,可能阻塞线程消息循环。所以,最好在工作者线程中使用阻塞操作。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>CSocket</B>
<p>
< align=justify>如果希望在用户界面线程中使用阻塞socket,则可以使用CSocket。它在非阻塞socket基础之上实现了阻塞操作,在阻塞期间实现了消息循环。</P>
< align=justify>对于CSocket,处理网络事件通知的函数OnAccept、OnClose、OnReceive仍然可以使用,OnConnect、OnSend在CSocket中永远不会被调用,另外OnOutOfBandData在CSocket中不鼓励使用。</P>
< align=justify>CSocket对象在调用Connect、Send、Accept、Close、Receive等成员函数后,这些函数在完成任务之后(连接被建立、数据被发送、连接请求被接收、socket被关闭、数据被读取)之后才会返回。因此,Connect和Send不会导致OnConnect和OnSend被调用。如果覆盖虚拟函数OnReceive、OnAccept、OnClose,不主动调用Receive、Accept、Close,则在网络事件到达之后导致对应的虚拟函数被调用,虚拟函数的实现应该调用Receive、Accept、Close来完成操作。下面,就一个函数Receive来考察CSocket如何实现阻塞操作和消息循环的。</P>
< align=justify>int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags)</P>
< align=justify>{</P>
< align=justify>//m_pbBlocking是CSocket的成员变量,用来标识当前是否正在进行</P>
< align=justify>//阻塞操作。但不能同时进行两个阻塞操作。</P>
< align=justify>if (m_pbBlocking != NULL)</P>
< align=justify>{</P>
<P align=justify>WSASetLastError(WSAEINPROGRESS);</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>//完成数据读取</P>
<P align=justify>int nResult;</P>
<P align=justify>while ((nResult = CAsyncSocket::Receive(lpBuf, nBufLen, nFlags)) </P>
<P align=justify>== SOCKET_ERROR)</P>
<P align=justify>{</P>
<P align=justify>if (GetLastError() == WSAEWOULDBLOCK)</P>
<P align=justify>{</P>
<P align=justify>//进入消息循环,等待网络事件FD_READ</P>
<P align=justify>if (!PumpMessages(FD_READ))</P>
<P align=justify>return SOCKET_ERROR;</P>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>return SOCKET_ERROR;</P>
<P align=justify>}</P>
<P align=justify>return nResult;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>参数1指定一个缓冲区保存读取的数据;参数2指定缓冲区的大小;参数3取值MSG_PEEK(数据拷贝到缓冲区,但不从输入队列移走),或者MSG_OOB(处理带外数据),或者MSG_PEEK|MSG_OOB。</P>
<P align=justify>Receive函数首先判断当前CSocket对象是否正在处理一个阻塞操作,如果是,则返回错误WSAEINPROGRESS;否则,开始数据读取的处理。</P>
<P align=justify>读取数据时,如果基类CAsyncSocket的Receive读取到了数据,则返回;否则,如果返回一个错误,而且错误号是WSAEWOULDBLOCK,则表示操作阻塞,于是调用PumpMessage进入消息循环等待数据到达(网络事件FD_READ发生)。数据到达之后退出消息循环,再次调用CAsyncSocket的Receive读取数据,直到没有数据可读为止。</P>
<P align=justify>PumpMessages是CSocket的成员函数,它完成以下工作:</P>
<P align=justify>(1)设置m_pbBlocking,表示进入阻塞操作。</P>
<P align=justify>(2)进行消息循环,如果有以下事件发生则退出消息循环:收到指定定时器的定时事件消息WM_TIMER,退出循环,返回TRUE;收到发送给本socket的消息WM_SOCKET_NOTIFY,网络事件FD_CLOSE或者等待的网络事件发生,退出循环,返回TRUE;发送错误或者收到WM_QUIT消息,退出循环,返回FALSE;</P>
<P align=justify>(3)在消息循环中,把WM_SOCKET_DEAD消息和发送给其他socket的通知消息WM_SOCKET_NOFITY放进模块线程状态的通知消息列表m_listSocketNotifications,在阻塞操作完成之后处理;对其他消息,则把它们送给目的窗口的窗口过程处理。</P>
<P align=justify></P>
<LI><B>CSocketFile</B>
<p></LI></OL>
<P align=justify>MFC还提供了一个网络编程模式,可以充分利用CSocket的特性。该模式的基础是CSocketFile类。使用方法如下:</P>
<P align=justify>首先,构造一个CSocket对象;调用Create函数创建一个socket对象(SOCK_STREAM类型)。</P>
<P align=justify>接着,如果是客户程序,调用Connect连接到远地主机;如果是服务器程序,先调用Listen监听socket端口,收到连接请求后调用Accept接收请求。</P>
<P align=justify>然后,创建一个和CSocket对象关联的CSocketFile对象,创建一个和CSocketFile对象关联的CArchive对象,指定CArchive对象是用于读或者写。如果既要读又要写,则创建两个CArchive对象。</P>
<P align=justify>创建工作完成之后,使用CArchive对象在客户和服务器之间传送数据</P>
<P align=justify>使用完毕,销毁CArchive对象、CSocketFile对象、CSocket对象。</P>
<P align=justify>从前面的章节可以知道,CArchive可以以一个CFile对象为基础,通过&lt;&lt;和&gt;&gt;操作符完成对文件的二进制流的操作。所以可以从CFile派生一个类,实现CFile的操作界面(Read和Write)。由于CSocket提供了阻塞操作,所以完全可以像读写文件一样读写socket数据。</P>
<P align=justify>下面,分析CSocketFile的设计和实现。</P>
<OL>
<P align=justify>
<LI>CSocketFile的构造函数和析构函数的实现
<p></LI></OL>
<UL>
<P align=justify>
<LI>构造函数的实现
<p></LI></UL>
<P align=justify>CSocketFile::CSocketFile(CSocket* pSocket, BOOL bArchiveCompatible)</P>
<P align=justify>{</P>
<P align=justify>m_pSocket = pSocket;</P>
<P align=justify>m_bArchiveCompatible = bArchiveCompatible;</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>ASSERT(m_pSocket != NULL);</P>
<P align=justify>ASSERT(m_pSocket-&gt;m_hSocket != INVALID_SOCKET);</P>
<P align=justify></P>
<P align=justify>int nType = 0;</P>
<P align=justify>int nTypeLen = sizeof(int);</P>
<P align=justify>ASSERT(m_pSocket-&gt;GetSockOpt(SO_TYPE,&amp;nType,&amp;nTypeLen));</P>
<P align=justify>ASSERT(nType == SOCK_STREAM);</P>
<P align=justify>#endif // _DEBUG</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>构造函数的参数1指向关联的CSocket对象,被保存在成员变量m_pSocket中;</P>
<P align=justify>参数2指定该对象是否和一个CArchive对象关联(不关联则独立使用),被保存在成员变量bArchiveCompatible中。</P>
<P align=justify>Degug部分用于检测m_pSocket是否是SOCK_STREAM类型。</P>
<UL>
<P align=justify>
<LI>析构函数的实现
<p></LI></UL>
<P align=justify>CSocketFile::~CSocketFile()</P>
<P align=justify>{</P>
<P align=justify>}</P>
<P align=justify>(2)CSocketFile的读写的实现</P>
<P align=justify>分析CSocketFile如何用文件的读写实现网络I/O。</P>
<UL>
<P align=justify>
<LI>文件读的实现
<p></LI></UL>
<P align=justify>UINT CSocketFile::Read(void* lpBuf, UINT nCount)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(m_pSocket != NULL);</P>
<P align=justify></P>
<P align=justify>int nRead;</P>
<P align=justify></P>
<P align=justify>//CSocketFile对象独立使用</P>
<P align=justify>if (!m_bArchiveCompatible)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>int nLeft = nCount;</P>
<P align=justify>PBYTE pBuf = (PBYTE)lpBuf;</P>
<P align=justify></P>
<P align=justify>//读完nCount个字节的数据</P>
<P align=justify>while(nLeft &gt; 0)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//CSocket的Receive,阻塞操作,读取到数据才继续</P>
<P align=justify>nRead = m_pSocket-&gt;Receive(pBuf, nLeft);</P>
<P align=justify>if (nRead == SOCKET_ERROR)</P>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>int nError = m_pSocket-&gt;GetLastError();</P>
<P align=justify>AfxThrowFileException(CFileException::generic, nError);</P>
<P align=justify>ASSERT(FALSE);</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify>else if (nRead == 0)</P>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>return nCount - nLeft;</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>nLeft -= nRead;</P>
<P align=justify>pBuf += nRead;</P></DIR>
<P align=justify>}</P>
<P align=justify>return nCount - nLeft;</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//和一个CArchive对象关联使用</P>
<P align=justify>//读取数据,能读多少是多少</P>
<P align=justify>nRead = m_pSocket-&gt;Receive(lpBuf, nCount, 0);</P>
<P align=justify>if (nRead == SOCKET_ERROR)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>int nError = m_pSocket-&gt;GetLastError();</P>
<P align=justify>AfxThrowFileException(CFileException::generic, nError);</P>
<P align=justify>ASSERT(FALSE);</P></DIR>
<P align=justify>}</P>
<P align=justify>return nRead;</P>
<P align=justify>}</P>
<UL>
<P align=justify>
<LI>文件写的实现
<p></LI></UL>
<P align=justify>void CSocketFile::Write(const void* lpBuf, UINT nCount)</P>
<P align=justify>{</P>
<P align=justify>ASSERT (m_pSocket!=NULL);</P>
<P align=justify></P>
<P align=justify>//CSocket的函数Send,阻塞操作,发送完毕才继续</P>
<P align=justify>int nWritten = m_pSocket-&gt;Send(lpBuf, nCount);</P>
<P align=justify>if (nWritten == SOCKET_ERROR)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>int nError = m_pSocket-&gt;GetLastError();</P>
<P align=justify>AfxThrowFileException(CFileException::generic, nError);</P></DIR>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>从CSockefFile的读写实现可以看出,CSocketFile如果独立使用,在Read操作时可能出现无限等待,因为数据是分多个消息多次送达的,没有读取到指定长度的数据并不表示数据读取完毕。但是和CArchive配合使用,则仅仅读取到数据就返回。至于数据是否读取完毕,可以使用CArchive的IsBufferEmpty函数来判断。</P>
<P align=justify>其他CFile界面,CSocketFile没有实现。</P>
<P align=justify>从CScocketFile的设计和实现来看,CSocketFile是使用CSocket的一个很好的例子,也是使用CFile的一个例子。</P>
 楼主| 发表于 2006-12-8 10:15:16 | 显示全部楼层
<OL>
<LI>
<>OnFileOpen </P>
<p>
< align=justify>分析了 OnFileNew后,现在分析CWinApp::OnFileOpen(),其流程如图5-6所示。</P><IMG src="http://www.vczx.com/tutorial/mfc/image123.gif" align=left>
<>CWinApp::OnFileOpen和OnFileNew类似,不过,第二步须得到一个要打开的文件的名称,第三步调用的是应用程序对象的OpenDocumentFile,而不是文档模板对象的该函数。</P>
< align=justify></P>
<LI>应用程序对象的OpenDocumentFile
<p>
< align=justify>分析应用程序的打开文档函数: CWinApp::OpenDocumentFile(LPCSTR name),其流程如图5-7所示。</P><IMG src="http://www.vczx.com/tutorial/mfc/image124.gif" align=left>
<> </P>
<>应用程序对象把打开文件操作委托给文档模板管理器,后者又委托给文档模板对象来执行。如果是SDI程序,则委托给单文档对象;如果是MDI程序,则委托给多文档对象──这是由指针所指对象的实际类型决定的,因为该函数是一个虚拟函数。</P>
< align=justify></P>
<LI>文档模板的OpenDocumentFile
<p>
< align=justify>不论是FileNew还是FileOpen,最后的操作都归结到由文档模板来打开文件(文件名空则创建文件)。</P>
< align=justify>CSingleDocTemplate::OpenDocumentFile(lpcstr name,BOOL visible)的流程见图5-8。有一点需要指出的是:创建了一个文档对象,并不等于打开了一个文档(件)或者创建了一个新文档(件)。</P>
<P>图5-8显示的流程大致可以描述如下:</P>
<P align=justify>如果已经有文档打开,则保存当前的文档;否则,文档对象还没有创建,需要创建一个新的文档对象。因为这时边框窗口还没有生成,所以还要创建边框窗口对象(MFC对象)和边框窗口。MFC边框窗口对象动态创建,HWND边框窗口由LoadFrame创建。MFC边框窗口被创建时,CFrameWnd的缺省构造函数被调用,它把正创建的对象(this所指)加入到模块-线程状态的边框窗口列表m_frameList之首。</P>
<P align=justify>边框窗口创建过程中由CreateView动态创建MFC视对象和HWND视窗口。</P>
<P align=justify>接着,如果没有指定要打开的文件名,则创建一个新的文件;否则,则打开文件,并使用序列化机制读入文件内容。</P>
<P align=justify>通过上述过程,动态地创建了MFC边框窗口对象、视对象、文档对象以及对应的Windows对象,并填写了有关对象的成员变量,建立起这些MFC对象的关系。</P>
<P align=justify></P>
<LI>打开文件过程中所涉及的消息处理函数和虚拟函数
<p></LI></OL>
<P align=justify>图5-8描述的整个过程中系列消息处理函数和虚拟函数被调用。例如:在Windwos边框窗口和视窗口被创建时会产生WM_CREATE等消息,导致OnCreate等消息处理函数的调用,CFrameWnd和CView都覆盖了该函数,所以在边框窗口和视窗口的创建中,同样的消息调用了不同的处理函数CFrameWnd::OnCreate和CView::OnCreate。</P>
<P align=justify>图5-8涉及的几个虚拟函数的流程分别由图5-9、图5-10图解。图5-9表示CDocument的OnNewDocument的流程;图5-10表示CDocument的OpenDocument的流程。这两个函数分别在创建新文档或者打开一个文档时被调用。从流程可以看出,对于OpenDocument函数,MFC的缺省实现主要用来设置修改标识、序列化读入打开文档的内容。图5-10显示了序列化的操作过程:</P>
<P align=justify>首先,使用文档对象打开或者创建的文件句柄创建一个用于读出数据的CArchive对象loadarchive;然后使用它通过Serialize进行序列化操作,完毕,CArchive对象被自动销毁,文件句柄被关闭。</P>
<P align=justify>从这些图中可以看到何时、何处调用了什么消息处理函数和虚拟函数,这些函数用来作了什么事情。必要的话,程序员可以在派生类覆盖它们。</P>
<P align=justify>在创建工作完成之后,进行初始化,使用文档对象的数据来更新视和显示窗口。</P>
<P align=justify></P>
<P align=justify>至此,本节描述了MFC的SDI程序从分析命令行到创建或打开文件的处理过程,文档对象已经动态创建。总结如下:</P>
<P align=justify>命令行分析→应用程序的FileNew→文档模板的OpenDocumentFile(NULL)→文档的OnNewDocument</P>
<P align=justify>命令行分析→应用程序的FileOPen→文档模板的OpenDocumentFile(filename)→文档的OpenDocument</P>
<P align=justify>边框窗口对象、视对象的动态创建和对应 Windows对象的创建从LoadFrame开始,这些将在下一节论述。</P><IMG src="http://www.vczx.com/tutorial/mfc/image125.gif" align=left>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image126.gif" align=left>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image127.gif" align=left>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image128.gif" align=left>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>SDI边框窗口的创建</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>第三步是创建SDI边框窗口。</P>
<P align=justify>图5-8已经分析了创建SDI边框窗口的时机和创建方法,下面,从LoadFrame开始分析整个窗口创建过程。</P>
<OL>
<P align=justify>
<LI>CFrameWnd:oadFrame
<p>
<P align=justify>CFrameWnd:oadFrame的流程如图5-11所示,其原型如下:</P>
<P align=justify>BOOL CFrameWnd:oadFrame(UINT nIDResource, </P>
<P align=justify>DWORD dwDefaultStyle,</P>
<P align=justify>CWnd* pParentWnd,</P>
<P align=justify>CCreateContext* pContext)</P>
<P align=justify>第一个参数是和该框架相关的资源ID,包括字符串、快捷键、菜单、像标等;</P>
<P align=justify>第二个参数指定框架窗口的“窗口类”和窗口风格;此处创建SDI窗口时和缺省值相同,为WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE;</P>
<P align=justify>第三个参数指定框架窗口的父窗口,此处和缺省值相同,为NULL;</P>
<P align=justify>第四个参数指定创建的上下文,如图5-8所示由CreateNewFrame生成了该变量并传递给LoadFrame。其缺省值为NULL。</P>
<P align=justify>创建上下文结构的定义:</P>
<P align=justify>struct CCreateContext</P>
<P align=justify>{</P>
<P align=justify>CRuntimeClass* m_pNewViewClass; //View的动态创建信息</P>
<P align=justify>CDocument*m_pCurrentDoc;//指向一文档对象,将和新创建视关联</P>
<P align=justify></P>
<P align=justify>//用来创建MDI子窗口的信息(C MDIChildFrame:oadFrame使用)</P>
<P align=justify>CDocTemplate* m_pNewDocTemplate;</P>
<P align=justify></P>
<P align=justify>// for sharing view/frame state from the original view/frame</P>
<P align=justify>CView* m_pLastView;</P>
<P align=justify>CFrameWnd* m_pCurrentFrame;</P>
<P align=justify>};</P>
<P align=justify>这里,传递给LoadFrame的CCreateContext变量是:</P>
<P align=justify>(视的动态创建信息,新创建的文档对象,当前文档模板,NULL,NULL)。</P>
<P align=justify>其中,“新创建的文档对象”就是图 5-8中创建的那个文档对象。从此图中还可以看到,LoadFrame被CreateNewFrame调用,CreateNewFrame是文档模板的成员函数,被文档模板的成员函数OpenDocumentFile所调用,所以,LoadFrame间接地被文档模板调用,“当前文档模板”就是调用它的模板对象。顺便指出,对SDI程序来说是这样的,对MDI程序有所不同。“视的动态创建信息”也是文档模板传递过来的。</P><IMG src="http://www.vczx.com/tutorial/mfc/image129.gif" align=left>
<P align=justify></P>
<P align=justify>对图5-11的说明:</P>
<P align=justify>在创建边框窗口之前,先注册“窗口类”。LoadFrame注册了两个“窗口类”,一个为边框窗口,一个为视窗口。关于“窗口类”注册,见2.2.1节。</P>
<P align=justify>注册窗口类之后,创建边框窗口,并加载资源。创建边框窗口使用了CFrameWnd的Create虚拟函数,最终调用::CreateEx创建窗口。::CreateEx有11个参数,其最后一个参数就是文档模板传递给LoadFrame的CCreateContext类型的指针,该指针将被传递给窗口过程,进一步由Windows传递给OnCreate函数。顺便指出,创建完毕的边框窗口的窗口过程是统一的MFC窗口过程。</P>
<P align=justify>创建边框窗口时,发送消息WM_NCCREATE和WM_CREATE,导致对应的消息处理函数OnNcCreate和OnCreate被调用。CWnd提供了OnNcCreate处理非客户区创建消息,CFrameWnd没有处理该消息,但是提供了OnCreate处理消息WM_CREATE。OnCreate将创建视对象和视窗口。</P>
<P align=justify></P>
<LI>CFrameWnd::OnCreate
<p></LI></OL>
<P align=justify>按创建工作的进度,现在要讨论边框窗口创建消息(WM_CREATE)的处理了,处理函数是CFrameWnd的OnCreate,其原型如下:</P>
<P align=justify>int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)</P>
<P align=justify>其中,参数指向一个CreateStruct结构(关于CreateStruct的描述见4.4.1节),它包含了窗口创建参数的副本,也就是说CreaeEx窗口创建函数的11个参数被对应地复制到该结构的11个域,例如它的第一个成员就可以转换成CCreateContext类型的指针。</P>
<P align=justify>函数OnCreate处理WM_CREATE消息,它从lpcs指向的结构中分离出lpCreateParams并把它转换成为CCreateContext类型的指针pContext,然后,调用OnCreateHelp(lpcs,pContext),把创建工作委派给它完成。</P>
<P align=justify>CFrameWnd::OnCreateHelp的原型如下,流程见图5-11。</P>
<P align=justify>int CFrameWnd::OnCreateHelp(LPCREATESTRUCT lpcs, </P>
<DIR>
<P align=justify>CCreateContext* pContext)</P><IMG src="http://www.vczx.com/tutorial/mfc/image130.gif" align=left></DIR>
<P align=justify>说明:由于CFrameWnd覆盖了消息处理函数OnCreate来处理WM_CREATE消息,所以CWnd就失去了处理该消息的机会,除非CFrameWnd::OnCreate主动调用基类的该消息处理函数。图5-11展示了对CWnd::OnCreate的调用。</P>
<P align=justify>在边框窗口被创建之后,调用虚拟函数OnCreateClient(lpcs,pContext),它的缺省实现将创建视对象和视窗口。</P>
<P align=justify>最后,在状态栏显示“Ready”字样,调用RecalcLayout安排工具栏等的位置。关于WM_SETMESSAGESTRING消息和RecalcLayout函数,见工具栏有关13.2.3节。</P>
<P align=justify>到此,SDI的边框窗口已经被创建。下一节将描述视的创建。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>视的创建</B>
<p>
<P align=justify>第四步,创建视。</P>
<P align=justify>如前一节所述,若CFrameWnd::OnCreateClient(lpcs,pContext)判断pContext包含了视的动态创建信息,则调用函数CreateView创建视对象和视窗口。CreateView的原型如下,其流程如图5-13所示。</P>
<P align=justify>CWnd * CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)</P></LI></OL></OL></OL></OL>
 楼主| 发表于 2006-12-8 10:15:35 | 显示全部楼层
<OL>
<LI>
<DIV align=justify>其中:</DIV></LI>
<LI>
<DIV align=justify>第一个参数是创建上下文;</DIV></LI>
<LI>
<DIV align=justify>第二个参数是创建视 (子窗口)的ID,缺省是AFX_IDW_PANE_FIRST,这里等同缺省值。</DIV></LI>
<LI><IMG src="http://www.vczx.com/tutorial/mfc/image131.gif" align=left> </LI>
<LI>
<DIV align=justify>说明:</DIV></LI>
<LI>
<DIV align=justify>CreateView调用了CWnd的Create函数创建HWND视窗口,视的子窗口ID是AFX_IDW_PANE_FIRST,父窗口是创建它的边框窗口。创建视窗口时的WM_CREATE、WM_NCCREATE消息分别被CView、CWnd的相关消息处理函数处理。处理情况如图5-13所述,这里不作进一步的讨论。</DIV></LI>
<LI>
<DIV align=justify>到此,文档对象、边框窗口对象、视窗口对象已经创建,文件已经打开或者创建,边框窗口、视窗口已经创建。现在,是显示和定位窗口、显示文档数据的时候了,这些通过调用CFrameWnd的虚拟函数InitialUpdateFrame完成,如图5-8所示。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>窗口初始化</B>
<p>
< align=justify>这是第五步,初始化边框窗口、视窗口等。</P>
< align=justify>InitialUpdateFrame的原型如下:</P>
< align=justify>void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)</P>
< align=justify>其中:</P>
< align=justify>第一个参数是当前的文档对象;</P>
< align=justify>第二个参数表示边框窗口是否应该可见并激活。</P>
< align=justify>该函数是在文档模板的OpenDocumentFile中调用的,传递过来的第一个参数是刚才创建的文档,第二个参数是TRUE,见图5-8。</P>
< align=justify>InitialUpdateFrame的处理过程参见图5-14,解释如下:</P>
< align=justify>首先,如果当前边框窗口没有活动视,则获取ID为AFX_IDW_PANE_FIRST的视pView。如果该视不存在,则pView=NULL;否则(pView!=NULL),调用成员函数SetActiveView(pView,FALSE)把它设置为活动视,参数2为FALSE表示并不通知它成为活动视(见图5-14)。</P>
< align=justify>然后,如果InitialUpdateFrame的参数bMakeVisible为TRUE,则给所有边框窗口的视发送WM_INITIALUPDATE消息,通知它们在显示之前使用文档数据来初始化视。这导致视类的虚拟函数OnInitUpdate被调用,该函数又调用OnUpdate来完成初始化。其他子窗口(如状态栏、工具栏)也将收到WM_INITIALUPDATE消息,导致它们更新状态。</P>
<P align=justify>其三,调用pView-&gt;OnActivateFrame(WA_INACTIVE,this)给活动视(若存在的话)一个机会来保存当前焦点。这里,解释这个函数:</P>
<P align=justify>void CView::OnActivateFrame( UINT nState,CFrameWnd* pFrameWnd );</P>
<P align=justify>其中,参数1取值为WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE,具体见消息WM_ACTIVE的解释;参数2指向被激活的框架窗口。</P>
<P align=justify>视对象通过该虚拟函数在它所属的边框窗口被激活或者失去激活时作一些特别的处理,例如,CFormView用它来保存或者恢复输入焦点控制。</P>
<P align=justify>其四,在OnActivateFrame之后,调用成员函数ActivateFrame激活框架窗口。这个过程将产生一个消息WM_ACTIVE(处理该消息的过程在下一节作解释),它导致OnActiveTopLevel和OnActive被调用。接着,如果活动视非空,则调用成员函数OnActivateView激活它。</P>
<P align=justify>至此,参数bMakeVisible为TRUE时显示窗口的处理完毕。</P>
<P align=justify>最后,如果参数pDoc非空,则更新边框窗口计数,更新边框窗口的标题。更新边框窗口计数是为了在一个文档对应多个视的情况下,给显示同一文档的不同文档边框窗口编号,编号从1开始,保存在边框窗口的成员变量m_nWindow里。例如有两个边框对应一个文档tt1,则它们的标题分别为“tt1:1”、“tt1:2”。如果只有一个文档只对应一个边框窗口,则成员变量m_nWindow等于-1,标题不含编号,如“tt1”。</P>
<P align=justify>当然,对于SDI应用程序,不存在一个文档对应多个视的情况。上述情况是针对MDI应用程序而言的。SDI应用程序执行该过程时,相当于MDI程序的一个特例。</P>
<P align=justify>图 5-14涉及的一些函数由图5-15、5-15图解。</P><IMG src="http://www.vczx.com/tutorial/mfc/image132.gif" align=left>
<P align=justify></P>
<P align=justify> </P>
<P align=justify>图5-14中的函数SetActiveView的图解如图5-15所示,其原型如下,:</P>
<P align=justify>void CFrameWnd::SetActiveView(CView * pViewNew, BOOL bNotify = TRUE)</P>
<P align=justify>其中:</P>
<P align=justify>参数1指向被设置的视对象,若非视类型的对象,则为NULL;</P>
<P align=justify>参数 2表示是否通知被设置的视。</P><IMG src="http://www.vczx.com/tutorial/mfc/image133.gif" align=left>
<P align=justify>图5-15中的变量m_pViewActive是CFrameWnd的成员变量,用来保存边框窗口的活动视。</P>
<P align=justify>图5-15中的流程可以概括为:Deactivate当前视(m_pViewActive非空时);设置当前活动视;若参数bNotify为TRUE,通知pViewNew被激活。</P>
<P align=justify></P>
<P align=justify>图5-14中的函数ActivateFrame图解如图5-16所示,其原型如下,:</P>
<P align=justify>void CFrameWnd::ActivateFrame(UINT nCmdShow)</P>
<P align=justify>参数nCmdShow用来传递给CWnd::ShowWindow,指定显示窗口的方式。参数缺省为1,图5-14调用时设置为-1。</P>
<P align=justify>该函数用来激活(Activate)和恢复(Restore)边框窗口,使得它对用户可见可用。在初始化、OLE事件、DDE事件等需要显示边框窗口的地方调用。图5-16表示的MFC缺省实现是激活边框窗口并把它放到顶层。</P>
<P align=justify>程序员可以覆盖该虚拟函数ActivateFrame来控制边框窗口怎样被激活。</P>
<P align=justify>图5-16中的函数BringToTop是CFrameWnd内部使用的成员函数(protected)。它调用::BringWindowToTop把窗口放到Z轴上的顶层。</P>
<P align=justify>至此,边框窗口初始化的过程已经描述清楚,视的初始化见下一节。</P><IMG src="http://www.vczx.com/tutorial/mfc/image134.gif" align=left>
<P align=justify></P></LI></OL>
<LI>
<OL>
<LI><B>视的初始化</B> </LI>
<LI></LI>
<LI>
<DIV align=justify>第六步,在边框窗口初始化之后,初始化视。</DIV></LI>
<LI>
<DIV align=justify>如图5-14所示,视、工具条窗口处理消息WM_INITAILUPDATE(MFC内部消息),完成初始化。这里只讨论视的消息处理函数,其原型如下:</DIV></LI>
<LI>
<DIV align=justify>void CView::OnInitialUpdate()</DIV></LI>
<LI>
<DIV align=justify>图5-14对该函数的注释说明了该函数的特殊之处。其缺省实现是调用OnUpdate(NULL, 0, NULL)更新视。可以覆盖OnInitialUpdate实现自己的初始化。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>OnUpdate是一个虚拟函数,其原型如下:</DIV></LI>
<LI>
<DIV align=justify>void CView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)</DIV></LI>
<LI>
<DIV align=justify>其中:</DIV></LI>
<LI>
<DIV align=justify>参数1指向修改文档数据的视;若更新所有的视,设为NULL;</DIV></LI>
<LI>
<DIV align=justify>参数2是一个包含了修改信息的long型变量;</DIV></LI>
<LI>
<DIV align=justify>参数3指向一个包含修改信息的对象(从CObject派生的对象)。</DIV></LI>
<LI>
<DIV align=justify>参数2、参数3是在文档更新对应视的时候传递过来的。</DIV></LI>
<LI>
<DIV align=justify>该函数用来更新显示视窗口,反映文档的变化,在MFC中,它为函数CView::OnInitialUpdate和CDocument::UpdateAllViews所调用。其缺省实现是使整个客户区无效。在下一次收到WM_PAINT消息时,重绘无效区。</DIV></LI>
<LI>
<DIV align=justify>工具条的初始化见讨论第13章。</DIV></LI></OL>
<P align=justify></P></LI>
 楼主| 发表于 2006-12-8 10:15:59 | 显示全部楼层
<OL>
<LI>
<><B>激活边框窗口(处理WM_ACTIVE)</B> </P>
<p></LI></OL>
< align=justify>第七步,在窗口初始化完成之后,激活并显示出来。</P>
< align=justify>下面讨论边框窗口激活时的处理(对WM_ACTIVE的处理)。</P>
<OL>
< align=justify>
<LI>WM_ACTIVE的消息参数
<p>
< align=justify>wParam的低阶word指示窗口是被激活还是失去激活:WA_ACTIVE,被鼠标点击以外的方法激活;WA_CLICKACTIVE,由鼠标点击激活;WA_INACTIVE,失去激活;</P>
< align=justify>wParam的高阶word指示窗口是否被最小化;非零表示最小化;</P>
< align=justify>lPararm表示将激活的窗口句柄(WA_INACTIVE),或者将失去激活的窗口句柄(WA_CLICKACTIVE、WA_ACTIVE)。</P>
< align=justify></P>
< align=justify>在标准Windows消息处理的章节中,曾指出处理WM_ACTIVE消息时,先要调用一个函数_AfxHandleActivate,此函数的原型如下:</P>
< align=justify>static void AFXAPI _AfxHandleActivate(CWnd* pWnd, </P>
<P align=justify>WPARAM nState,CWnd* pWndOther)</P>
<P align=justify>其中:</P>
<P align=justify>参数1是接收消息的窗口;</P>
<P align=justify>参数2是窗口状态,为WM_ACTIVE的消息参数wParam;</P>
<P align=justify>参数3是WM_ACTIVE的消息参数lParam表示的窗口。</P>
<P align=justify>_AfxHandleActivate是MFC内部使用的函数,声明和实现均在WinCore.CPP文件中。实现如下:</P>
<P align=justify>如果pWnd指向的窗口不是子窗口,而且pWnd和pWndOther窗口的顶级父窗口(TopLevelParent)不是同一窗口,则发送MFC定义的消息WM_ACTIVATETOPLEVEL给pWnd的顶级窗口,消息参数wParam是nState,消息参数lParam指向一个长度为二的数组,数组里存放pWnd和pWndOther所指窗口的句柄。否则,_AfxHandleActivate不作什么。</P>
<P align=justify>从这里可以看出:只有顶层的主边框窗口能处理WM_ACTIVE消息,事实上,Windows系统只会给顶层的非子窗口发送WM_ACTIVE消息。</P>
<P align=justify></P>
<LI>WM_ACTIVATETOPLEVEL消息的处理
<p>
<P align=justify>CWnd及派生类CFrameWnd实现了对WM_ACTIVATETOPLEVEL消息的处理,分别解释如下:</P>
<P align=justify>消息处理函数CWnd::OnActivateTopLevel如果失去激活,则取消工具栏的提示(TOOLTIP)。</P>
<P align=justify>消息处理函数CFrameWnd::OnActivateTopLevel调用CWnd的OnActivateTopLevel;如果接收WM_ACTIVE消息的窗口是线程主窗口,则使得其活动的视窗口变成非活动的(OnActive(FALSE, pActiveView,pActiveView)。</P>
<P align=justify>从这里可以知道,在顶层窗口接收到WM_ACTIVE消息后,MFC会进行一些固定的处理,然后才调用WM_ACTIVE消息处理函数。</P>
<P align=justify></P>
<LI>WM_ACTIVE消息的处理
<p></LI></OL>
<P align=justify>在_AfxHandleActivate和WM_ACTIVATETOPLEVEL消息处理完之后,才是对WM_ACTIVE的处理。CWnd和CFrameWnd都实现了消息处理。</P>
<P align=justify>CWnd的消息处理函数:</P>
<P align=justify>void CWnd::OnActive(UINT nState, CWnd* pWndOther, BOOL bMinimized)</P>
<P align=justify>其中:</P>
<P align=justify>参数1取值为WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE;</P>
<P align=justify>参数2指向激活或者失去激活的窗口,具体同WM_ACTIVE消息;</P>
<P align=justify>参数3表示是否最小化。</P>
<P align=justify>此函数的实现是调用Default(),作缺省处理。</P>
<P align=justify></P>
<P align=justify>CFrameWnd的消息处理函数:</P>
<P align=justify>void CFrameWnd::OnActive(UINT nState,CWnd* pWndOther, BOOL bMinimized)</P>
<P align=justify>首先调用CWnd::OnActivate。</P>
<P align=justify>如果活动视非空,消息是WA_ACTIVE/WA_CLICKACTIVE,并且不是最小化,则重新激活当前视,调用了以下函数:</P>
<P align=justify>pActiveView-&gt;OnActiveView(TRUE,pActiveView,pActiveView);</P>
<P align=justify>并且,如果活动视非空,通知它边框窗口状态的变化(激活/失去激活),调用以下函数:</P>
<P align=justify>pActiveView-&gt;OnActivateFrame(nState, this)。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>SDI流程的回顾</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>从InitialInstance开始,首先应用程序对象创建文档模板,文档模板创建文档对象、打开或创建文件;然后,文档模板创建边框窗口对象和边框窗口;接着边框窗口对象创建视对象和视窗口。这些创建是以应用程序的文档模板为中心进行的。在创建这些MFC对象的同时,建立了它们之间的关系。创建这些之后,进行初始化,激活主边框窗口,把边框窗口、视窗口显示出来。</P>
<P align=justify>这样,一个SDI应用程序就完成了启动过程,等待着用户的交互或者输入。</P>
<P align=justify>5.3.4节将在SDI程序启动流程的基础之上,介绍MDI应用程序的启动流程。两者的相同之处可以这样类比:创建SDI边框窗口、视、文档的过程和创建MDI文档边框窗口、视、文档的过程类似。不同之处主要表现在:主边框窗口的创建不一样;MDI有文档边框窗口的创建,SDI没有;SDI只能一个文档、一个视;MDI可能多文档、多个视。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>MDI程序的对象创建</B>
<p></LI></OL></OL></OL>
<P align=justify>MDI应用程序对象的InitialInstance函数一般含有以下代码:</P>
<P align=justify>//第一部分:创建和添加模板</P>
<P align=justify>CMultiDocTemplate* pDocTemplate;</P>
<P align=justify>pDocTemplate = new CMultiDocTemplate(</P>
<DIR>
<P align=justify>IDR_TTTYPE,</P>
<P align=justify>RUNTIME_CLASS(CTtDoc),</P>
<P align=justify>RUNTIME_CLASS(CChildFrame),//custom MDI child frame</P>
<P align=justify>RUNTIME_CLASS(CTtView));</P></DIR>
<P align=justify>AddDocTemplate(pDocTemplate);</P>
<P align=justify></P>
<P align=justify>//第二部分:创建MFC框架窗口对象和Windows主边框窗口</P>
<P align=justify>// 创建主MDI边框窗口</P>
<P align=justify>CMainFrame* pMainFrame = new CMainFrame;</P>
<P align=justify>if (!pMainFrame-&gt;LoadFrame(IDR_MAINFRAME))</P>
<DIR>
<P align=justify>return FALSE;</P></DIR>
<P align=justify>m_pMainWnd = pMainFrame;</P>
<P align=justify></P>
<P align=justify>//第三部分:处理命令行,命令行空则执行OnFileNew创建新文档</P>
<P align=justify>//分析命令行</P>
<P align=justify>CCommandLineInfo cmdInfo;</P>
<P align=justify>ParseCommandLine(cmdInfo);</P>
<P align=justify>// 处理命令行命令</P>
<P align=justify>if (!ProcessShellCommand(cmdInfo))</P>
<DIR>
<P align=justify>return FALSE;</P>
<P align=justify></P></DIR>
<P align=justify>第四部分:显示和更新主框架窗口</P>
<P align=justify>// 主窗口已被初始化,现在显示和更新主窗口</P>
<P align=justify>pMainFrame-&gt;ShowWindow(m_nCmdShow);</P>
<P align=justify>pMainFrame-&gt;UpdateWindow();</P>
<P align=justify></P>
<P align=justify>SDI应用程序对象的InitialInstance和SDI应用程序对象的InitialInstance比较,有以下的相同和不同之处。相同之处在于:</P>
<P align=justify>创建和添加模板;处理命令行。</P>
<P align=justify>不同之处在于:</P>
<UL>
<P align=justify>
<LI>创建的模板类型不同。SDI使用单文档模板,边框窗口类从CFrameWnd派生;MDI使用多文档模板,边框窗口类从CMDIChildWnd派生.
<p>
<P align=justify></P>
<LI>主窗口类型不同。SDI的是从CFrameWnd派生的类;MDI的是从CMDIFrameWnd派生的类。
<p>
<P align=justify></P>
<LI>主框架窗口的创建方式不同。SDI在创建或者打开文档时动态创建主窗口对象,然后加载主窗口(LoadFrame)并初始化;MDI使用第二部分的语句来创建动态主窗口对象和加载主窗口,第四部分语句显示、更新主窗口。
<p>
<P align=justify></P>
<LI>命令行处理的用途不一样。SDI一定要有命令行处理部分的代码,因为它导致了主窗口的创建;MDI可以去掉这部分代码,因为它的主窗口的创建、显示等由第二、四部分的语句来处理。
<p></LI></UL>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>有别于SDI的主窗口加载过程</B>
<p>
<P align=justify>和SDI应用程序一样,MDI应用程序使用LoadFrame加载主边框窗口,但因为LoadFrame的虚拟属性,所以MDI调用了CMDIFrameWnd的LoadFrame函数,而不是CFrameWnd的LoadFrame。</P>
<P align=justify>LoadFrame的参数1指定了资源ID,其余几个参数取缺省值。和SDI相比,第四个创建上下文参数为NULL,因为MDI主窗口不需要文档、视等的动态创建信息。</P>
<P align=justify>图 5-17图解了CMdiFrameWnd:oadFrame的流程:</P><IMG src="http://www.vczx.com/tutorial/mfc/image135.gif" align=left>
<P align=justify></P>
<P align=justify>首先,用同样的参数调用基类CFrameWnd的LoadFrame,其流程如图5-11所示,但由于参数4表示的创建上下文为空,所以,CFrameWnd:oadFrame在加载了菜单和快捷键之后,给所有子窗口发送WM_INITUPDATE消息。</P>
<P align=justify>另外,WM_CREATE消息怎样处理呢?由于CMDIFrameWnd没有覆盖OnCreate,所以还是由基类CFrameWnd::OnCreate处理。但是它调用虚拟函数OnCreateClient(见图5-12)时,由于CMDIFrameWnd覆盖了该函数,所以动态约束的结果是CMDIFrameWnd::OnCreateClient被调用,它和基类的OnCreateClient不同,后者CreateView创建MFC视对象和视窗口,前者调用虚拟函数CreateClient创建MDI客户窗口。MDI客户窗口负责创建和管理MDI子窗口。</P>
<P align=justify></P>
<P align=justify>CreateClient是CMDIFrameWnd的虚拟函数,其原型如下:</P>
<P align=justify>BOOL CMDIFrameWnd::CreateClient(</P>
<P align=justify>LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu);</P>
<P align=justify>该函数主要用来创建MDI客户区窗口。它使用Windows系统预定义的“mdiclient”窗口类来创建客户区窗口,保存该窗口句柄在CMDIFrameWnd的成员变量m_hWndMDIClient中。调用::CreateWindowEx创建客户窗口时传递给它的窗口创建数据参数(第11个参数)是一个CLIENTCREATESTRUCT结构类型的参数,该结构指定了一个菜单和一个子窗口ID:</P>
<P align=justify>typedef struct tagCLIENTCREATESTRUCT{</P>
<P align=justify>HMENU hWindowMenu;</P>
<P align=justify>UINT idFirstChild;</P>
<P align=justify>}CLIENTCREATESTRUCT;</P>
<P align=justify>hWindowMenu表示主边框窗口菜单栏上的“Windows弹出菜单项”的句柄。MDICLIENT类客户窗口在创建MDI子窗口时,把每一个子窗口的标题加在这个弹出菜单的底部。idFirstChild是第一个被创建的MDI子窗口的ID号,第二个MDI子窗口ID号为idFirstChild+1,依此类推。</P>
<P align=justify>这里,hWindowMenu的指定不是必须的,程序员可以在MDI子窗口激活时进行菜单的处理;idFirstChild的值是AFX_IDM_FIRST_MDICHILD。</P>
<P align=justify>综合地讲,CMDIFrameWnd:oadFrame完成创建MDI主边框窗口和MDI客户区窗口的工作。</P>
<P align=justify>创建了MDI边框窗口和客户区窗口之后,接着是处理WM_INITUPDATE消息,进行初始化。但是按SDI应用程序的讨论顺序,下一节先讨论MDI子窗口的创建。</P>
<P align=justify></P>
<LI><B>MDI子窗口、视、文档的创建</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>和SDI应用程序类似,MDI应用程序通过文档模板来动态创建MDI子窗口、视、文档对象。不同之处在于:这里使用了多文档模板,调用的是CMDIChildWnd(或派生类)的消息处理函数和虚拟函数,如果它覆盖了CFrameWnd的有关函数的话。</P>
<P align=justify>还是以处理标准命令消息ID_FILE_NEW的OnFileNew为例。</P>
<P align=justify>表示OnFileNew的图5-5、表示OnFileOpen的图5-6在多文档应用程序中仍然适用,但表示OpenDocumentFile的图5-8有所不同,其第三步中地单文档模板应当换成多文档模板,关于这一点,参阅图5-8的说明。</P>
<P align=justify>(1)多文档模板的OpenDocumentFile</P>
<P align=justify>MDI的OpenDocumentFile的原型如下:</P>
<P align=justify>CDocument* CMultiDocTemplate::OpenDocumentFile(</P>
<DIR>
<P align=justify>LPCTSTR lpszPathName, BOOL bMakeVisible);</P></DIR>
<P align=justify>它的原型和单文档模板的该函数原型一样,但处理流程比图5-8要简单些:</P>
<DIR>
<P align=justify>第一,不用检查是否已经打开了文档;</P></DIR>
<P align=justify>第二,不用判断是否需要创建框架窗口或者文档对象,因为不论新建还是打开文档都需要创建新的文档框架窗口(MDI子窗口)和文档对象。</P>
<P align=justify>除了这两点,其他处理步骤基本相同,调用同样名字的函数来创建文档对象和MDI子窗口。虽然是名字相同的函数,但是参数的值可能有异,又由于C++的虚拟机制和MFC消息映射机制,这些函数可能来自不同层次类的成员函数,因而导致有不同的处理过程和结果,即SDI创建了CFrameWnd类型的对象和边框窗口;MDI则创建了CMDIChildWnd类型的对象和边框窗口。不同之处解释如下:</P>
<P align=justify>(2)CMDIChildWnd的虚拟函数LoadFrame</P>
<P align=justify>CMDIChildWnd:oadFrame代替了图5-8中的CFrameWnd:oadFrame,两者流程大致相同,可以参见图5-11。但是它们用来创建窗口的函数不同。前者调用了函数CMDIChildWnd::Create(参数1…参数6);后者调用了CFrameWnd::Create(参数1…参数7)。</P>
<P align=justify>这两个窗口创建函数,虽然都是虚拟函数,但是有很多不同之处:</P>
<UL>
<P align=justify>
<LI>前者是CMDIChildWnd定义的虚拟函数,后者是CWnd定义的虚拟函数;
<p>
<P align=justify></P>
<LI>前者在参数中指定了父窗口,即主创建窗口,后者的父窗口参数为NULL;
<p>
<P align=justify></P>
<LI>前者指定了WS_CHILD风格,创建的是子窗口,后者创建一个顶层窗口;
<p>
<P align=justify></P>
<LI>前者给客户窗口m_hWndMDIClient(CMDIFrameWnd的成员变量)发送WM_MDICREATE消息让客户窗口来创建MDI子窗口(主边框窗口的子窗口是客户窗口,客户窗口的子窗口是MDI子窗口),后者调用::CreateEx函数来创建边框窗口;
<p>
<P align=justify></P>
<LI>前者的窗口创建数据是指向MDICREATESTRUCT结构的指针,该结构的最后一个域存放一个指向CCreateContext结构的指针,后者是指向CCreateContext结构的指针。
<p></LI></UL>
<P align=justify>MDICREATESTRUCT结构的定义如下:</P>
<P align=justify>typedef struct tagMDICREATESTRUCT { // mdic </P>
<P align=justify>LPCTSTR szClass; </P>
<P align=justify>LPCTSTR szTitle; </P>
<P align=justify>HANDLE hOwner; </P>
<P align=justify>int x; </P>
<P align=justify>int y; </P>
<P align=justify>int cx; </P>
<P align=justify>int cy; </P>
<P align=justify>DWORD style; </P>
<P align=justify>LPARAM lParam; </P>
<P align=justify>}MDICREATESTRUCT; </P>
<P align=justify>该结构的用处和CREATESTRUCT类似,只是它仅用于MDI子窗口的创建上,用来保存创建MDI子窗口时的窗口创建数据。域lParam保存一个指向CCreateContext结构的指针。</P>
<OL>
<P align=justify>
<LI>WM_CREATE的处理函数不同
<p></LI></OL>
<P align=justify>创建MDI子窗口时发送的WM_CREATE消息由CMDIChildWnd的成员函数OnCreate(LPCREATESTRUCT lpCreateStruct)处理。</P>
<P align=justify>OnCreate函数仅仅从lpCreateStruct指向的数据中取出窗口创建数据,即指向MDICREATESTRUCT结构的指针,并从该结构得到指向CCreateContext结构的指针pContext,然后调用虚拟函数OnCreateHelper(lpCreateStruct,pContext)。</P>
<P align=justify>此处动态约束的结果是调用了CFrameWnd的成员函数OnCreateHelper。SDI应用程序的OnCreate也调用了CFrameWnd::OnCreateHelper,所以后面的处理(创建视等)可参见SDI的流程了。</P>
<P align=justify>待MDI子窗口、视、文档对象创建完毕,多文档模板的OpenDocumentFile也调用InitialUpdateFrame来进行初始化。</P>
 楼主| 发表于 2006-12-8 10:16:25 | 显示全部楼层
<OL>
<OL>
<OL>
<OL>
< align=justify>
<LI><B>MDI子窗口的初始化和窗口的激活</B>
<p></LI></OL></OL></OL></OL>
< align=justify>(1)MDI子窗口的初始化</P>
< align=justify>完成了 MDI子窗口、视、文档的创建之后,多文档模板的OpenDocumenFile调用边框窗口的虚拟函数InitialUpdateFrame进行初始化,该函数流程参见图5-14。不过,这里this指针指向CMDIChildWnd对象,由于C++虚拟函数的动态约束,初始化过程调用了CMDIChildWnd的ActivateFrame函数(不是CFrameWnd的ActivateFrame),来显示MDI子窗口,更新菜单等等,见图5-18。</P>
<><IMG src="http://www.vczx.com/tutorial/mfc/image136.gif" align=left> </P>
<DIR>
< align=justify>图5-18的说明:</P></DIR>
< align=justify>第一,调用基类CFrameWnd的ActivateFrame显示窗口时,由于当前窗口是文档边框窗口,所以没有发送WM_ACTIVATE消息,而是发送消息WM_MDIACTIVATE。</P>
< align=justify>第二,由于Windows不处理MDI子窗口的激活,所以必须由MFC或者程序员来完成。当一个激活的MDI子窗口被隐藏后从可见变成不可见,但它仍然是活动的,这时需要把下一文档边框窗口激活以便用户看到的就是激活的窗口。在没有其他文档边框窗口时,则把该隐藏的文档边框窗口标记为“伪失去激活”。当一个文档边框窗口从不可见变成可见时,检查变量m_bPseudoInactive,若真则该窗口从Windows角度看仍然是激活的,只需要调用OnMDIActivate把它改成“MFC激活”。OnMDIActivate把变量m_bPseudoInactive的值改变为FALSE。</P>
< align=justify>至此,MDI子窗口初始化调用描述完毕。初始化将导致MDI窗口被显示、激活。下面讨论MDI子窗口的激活。</P>
< align=justify>(2)MDI子窗口的激活</P>
< align=justify>通过给客户窗口发送消息WM_MDIACTIVATE来激活文档边框窗口。客户窗口发送WM_MDIACTIVATE消息给将被激活或者取消激活的MDI子窗口(文档边框窗口),这些子窗口调用消息处理函数OnMDIActivate响应该消息WM_MDIACTIVATE。关于MDI消息,见表5-12。</P>
<P align=justify>用户转向一个子窗口(包括文档边框窗口)导致它的顶层(TOP LEVEL)边框窗口收到WM_ACTIVATE消息而被激活,子窗口是文档边框窗口的话将收到WM_MDIACTIVATE消息。</P>
<P align=justify>但是,一个边框窗口被其他方式激活时,它的文档边框窗口不会收到WM_MDIACTIVATE消息,而是最近一次被激活的文档边框窗口收到WM_NCACTIVATE消息。该消息由CWnd:efault缺省处理,用来重绘文档边框窗口的标题栏、边框等等。</P>
<P align=justify></P>
<P align=justify>MDI子窗口用OnMDIActiveate函数处理WM_MDIACTIVATE消息。其原型如下:</P>
<P align=justify>void CMDIChildWnd::OnMDIActivate( BOOL bActivate,</P>
<DIR>
<P align=justify>CWnd* pActivateWnd,CWnd* pDeactivateWnd );</P></DIR>
<P align=justify>其中:</P>
<P align=justify>参数1表示是激活(TRUE),还是失去激活(FALSE);</P>
<P align=justify>参数2表示将被激活的MDI子窗口;</P>
<P align=justify>参数3表示将被失去激活的MDI子窗口;</P>
<P align=justify>简单地说,该函数把m_bPseudoInactive的值改变为FALSE,调用成员函数OnActivateView通知失去激活的子窗口的视它将失去激活,调用成员函数OnActivateView通知激活子窗口的视它将被激活。</P>
<P align=justify></P>
<P align=justify>至于MDI主边框窗口,它还是响应WM_ACTIVATE消息而被激活或相反。CMDIFrameWnd没有提供该消息的处理函数,它调用基类CFrameWnd的处理函数OnActivate。</P>
<P align=justify>现在,MDI应用程序的启动过程描述完毕。</P>
<P align=center>表5-12 MDI消息</P>
<P align=center>
<CENTER>
<TABLE cellSpacing=1 cellPadding=7 width=487 border=1>

<TR>
<TD vAlign=top width="41%">
<P align=center>消息 </P></TD>
<TD vAlign=top width="59%">
<P align=center>说明 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIACTIVATE </P></TD>
<TD vAlign=top width="59%">
<P>激活MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDICASCADE </P></TD>
<TD vAlign=top width="59%">
<P>CASCADE排列MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDICREATE </P></TD>
<TD vAlign=top width="59%">
<P>创建MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIDESTROY </P></TD>
<TD vAlign=top width="59%">
<P>销毁MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIGETACTIVE </P></TD>
<TD vAlign=top width="59%">
<P>得到活动的MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIICONARRANGE </P></TD>
<TD vAlign=top width="59%">
<P>安排最小化了的MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIMAXIMIZE </P></TD>
<TD vAlign=top width="59%">
<P>MDI Child窗口最大化 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDINEXT </P></TD>
<TD vAlign=top width="59%">
<P>激活Z轴顺序的下一MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIREFRESHMENU </P></TD>
<TD vAlign=top width="59%">
<P>根据当前MDI Child窗口更新菜单 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDIRESTORE </P></TD>
<TD vAlign=top width="59%">
<P>恢复MDI Child窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDISETMENU </P></TD>
<TD vAlign=top width="59%">
<P>根据当前MDI Child窗口设置菜单 </P></TD></TR>
<TR>
<TD vAlign=top width="41%">
<P>WM_MDITITLE </P></TD>
<TD vAlign=top width="59%">
<P>TITLE安排MDI Child窗口 </P></TD></TR></TABLE></CENTER>
 楼主| 发表于 2006-12-8 10:18:37 | 显示全部楼层
<OL start=6>
< align=justify>
<LI><B>应用程序的退出</B>
<p>
< align=justify>一个Windows应用程序启动之后,一般是进入消息循环,等待或者处理用户的输入,直到用户关闭应用程序窗口,退出应用程序为止。</P>
< align=justify>例如,用户按主窗口的关闭按钮,或者选择执行系统菜单“关闭”,或者从“文件”菜单选择执行“退出”,都会导致主窗口被关闭。</P>
< align=justify>当用户从“文件”菜单选择执行“退出”时,将发送MFC标准命令消息ID_APP_EXIT。MFC实现了函数CWinApp::OnAppExit()来完成对该命令消息的缺省处理。</P>
< align=justify>void CWinApp::OnAppExit()</P>
< align=justify>{</P>
< align=justify>// same as double-clicking on main window close box</P>
< align=justify>ASSERT(m_pMainWnd != NULL);</P>
< align=justify>m_pMainWnd-&gt;SendMessage(WM_CLOSE);</P>
< align=justify>}</P>
<P align=justify>可以看出,其实现是向主窗口发送WM_CLOSE消息。主窗口处理完WM_CLOSE消息之后,关闭窗口,发送WM_QUIT消息,退出消息循环(见图5-3),进而退出整个应用程序。</P>
<OL>
<P align=justify>
<LI><B>边框窗口对WM_CLOSE的处理</B>
<p></LI></OL></LI></OL>
<P align=justify>MFC提供了函数CFrameWnd::OnClose来处理各类边框窗口的关闭:不仅包括SDI的边框窗口(从CFrameWnd派生),而且包括MDI的主边框窗口(从CMDIFrameWnd派生)或者文档边框窗口(从CMDIChildWnd派生)的关闭。</P>
<P align=justify>该函数的原型如下,流程如图6-1所示:</P>
<P align=justify>void CFrameWnd::OnClose()</P>
<P align=justify>从图6-1中可以看出,它首先判断是否可以关闭窗口(m_lpfnCloseProc是函数指针类型的成员变量,用于打印预览等情况下),然后,根据具体情况进行处理:</P>
<UL>
<P align=justify>
<LI>如果是主窗口被关闭,则关闭程序的所有文档,销毁所有窗口,退出程序;
<p>
<P align=justify></P>
<LI>如果不是主窗口被关闭,则是文档边框窗口被关闭,又分两种情况:若该窗口所显示的文档被且仅被该窗口显示,则关闭文档和文档窗口并销毁窗口;若该窗口显示的文档还被其他文档边框窗口所显示,则仅仅关闭和销毁文档窗口。
<p></LI></UL><IMG src="http://www.vczx.com/tutorial/mfc/image137.gif" align=left>
<P align=justify></P>
<P align=justify> </P>
<P align=justify>下面是处理 WM_CLOSE消息中涉及的一些函数。</P><IMG src="http://www.vczx.com/tutorial/mfc/image138.gif" align=left>
<UL>
<P align=justify>
<LI>BOOL CDocument::SaveModified()
<p></LI></UL>
<P align=justify>该虚拟函数的缺省实现:首先调用IsModifed判断文档是否被修改,没有修改就返回,否则继续。</P>
<P align=justify>当询问用户是否保存被修改的文档时,若用户表示“cancel”,返回FALSE;若用户表示“no”,则返回TRUE;若用户表示“yes”,则存盘失败返回FALSE,存盘成功返回TRUE。存盘处理首先要得到被保存文件的名称,然后调用虚拟函数OnSaveDocument完成存盘工作,并使用SetModifidFlag(FALSE)设置文档为没有修改。</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)
<p></LI></UL>
<P align=justify>该函数是虚拟函数,用来保存文件。其实现的功能和OpOpenDocument相反,但处理流程类似,描述如下:</P>
<DIR>
<P align=justify>根据lpszPathName打开文件pFile;</P>
<P align=justify>使用pFile构造一个用于写入数据的CArchive对象,此对象用来保存数据到文件;</P>
<P align=justify>设置鼠标为时间瓶形状;</P>
<P align=justify>使用Serialize函数完成序列化写;</P>
<P align=justify>完毕,恢复鼠标的形状。</P>
<P align=justify></P></DIR>
<UL>
<P align=justify>
<LI>CWinApp::SaveAllModified()
<p></LI></UL>
<DIR>
<P align=justify>CWinApp::CloseAllDocuments(BOOL bEndSession)</P></DIR>
<P align=justify>这两个函数都遍历模板管理器列表,并分别对列表中的模板管理器对象逐个调用CDocManager的同名成员函数:</P>
<P align=justify>CDocManager::SaveAllModified()</P>
<P align=justify>CDocManager::CloseAllDocuments(BOOL bEndSession)</P>
<P align=justify>这两个函数都遍历其文档模板列表,并分别对列表中的模板对象逐个调用CDocTemplate的同名成员函数:</P>
<P align=justify>CDocTemplate::SaveAllModified()</P>
<P align=justify>CDocTemplate::CloseAllDocuments(BOOL bEndSession)</P>
<P align=justify>这两个函数都遍历其文档列表,并分别对列表中的文档对象逐个调用CDocuemnt的成员函数:</P>
<P align=justify>CDocument::SaveModified()</P>
<P align=justify>CDocument::OnCloseDocument()</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>CDocument::SaveModified()
<p></LI></UL>
<P align=justify>CDocument::OnCloseDocument()</P>
<P align=justify>CDocument::SaveModified前面已作了解释。OnCloseDocument是一个虚拟函数,其流程如图6-2所示。</P>
<P align=justify>通过文档对象所对应的视,得到所有显示该文档的边框窗口的指针:在SDI程序关闭窗口时,获取的是主边框窗口;在MDI程序关闭窗口时,获取的是MDI子窗口。</P>
<P align=justify>然后,关闭并销毁对应的边框窗口。</P>
<P align=justify>如果文档对象的 m_bAutoDelete为真,则销毁文档对象自身。</P><IMG src="http://www.vczx.com/tutorial/mfc/image139.gif" align=left>
<P align=justify> </P>
<OL>
<OL>
<P align=justify>
<LI><B>窗口的销毁过程</B>
<p>
<OL>
<P align=justify>
<LI><B>DestroyWindow</B>
<p>
<P align=justify>从图6-1、图6-2可以看出,销毁窗口是通过调用DestroyWindow来完成的。DestroyWindow是CWnd类的一个虚拟函数。CWnd实现了该函数,而CMDIChildWnd覆盖了该函数。</P>
<P align=justify>(1)CWnd:estroyWindow()</P>
<P align=justify>主要就是调用:estroyWindow销毁m_hWnd(必须非空),同时销毁其菜单、定时器,以及完成其他清理工作。</P>
<P align=justify>:estroyWindow使将被销毁的窗口失去激活、失去输入焦点,并发送WM_DESTROY、WM_NCDESTROY消息到该窗口及其各级子窗口。如果被销毁的窗口是子窗口且没有设置WM_NOPARENTNOTFIY风格,则给其父窗口发送WM_PARENTNOFITY消息。</P>
<P align=justify>(2)CMDIChildWnd:estroyWindow()</P>
<P align=justify>因为MDI子窗口不能使用:estroyWindows来销毁,所以CMdiChildWnd覆盖了该函数,CMDIChildWnd主要是调用成员函数MDIDestroy给客户窗口(父窗口)发送消息WM_MDIDESTROY,让客户窗口来销毁自己。</P>
<P align=justify></P>
<LI><B>处理WM_DESTROY消息</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>消息处理函数OnDestroy处理WM_DESTROY消息,CFrameWnd、CMDIChildWnd、CWnd、CView及其派生类(如CEditView等等)、CControlBar等都提供了对该消息的处理函数。这里,主要解释边框、文档边框、视类的消息处理函数OnDestroy。</P>
<OL>
<P align=justify>
<LI>CWnd::OnDestroy()
<p>
<P align=justify>调用缺省处理函数Default()。</P>
<P align=justify></P>
<LI>CFrameWnd::OnDestroy()
<p>
<P align=justify>首先,销毁工具栏的窗口;然后,设置菜单为缺省菜单;接着,如果要销毁的是主边框窗口,则通知HELP程序本应用程序将退出,没有其他程序使用WINHELP则关闭WINHELP;最后调用CWnd::OnDestroy。</P>
<P align=justify></P>
<LI>CMDIFrameWnd::OnDestroy()
<p>
<P align=justify>首先,调整客户窗口的边界类型;然后,调用基类CframeWnd的OnDestroy。这时,MDI子窗口的工具栏窗口列表为空,故没有工具栏窗口可以销毁。</P>
<P align=justify></P>
<LI>CView::OnDestroy()
<p></LI></OL>
<P align=justify>首先,判断自身是否是边框窗口的活动视,如果是则调用边框窗口的SetActivateView使自己失去激活;然后,调用基类CWnd的OnDestroy。</P>
 楼主| 发表于 2006-12-8 10:19:30 | 显示全部楼层
<OL>
<OL>
<OL>
< align=justify>
<LI><B>处理WM_NCDESTROY消息</B>
<p></LI></OL></OL></OL>
< align=justify>窗口的非客户区被销毁时,窗口接收WM_NCDESTROY消息,由OnNcDestroy处理WM_NCDESTROY消息。在MFC中,OnNcDestroy是Windows窗口被销毁时调用的最后一个成员函数。</P>
< align=justify>CWnd、CView的某些派生类提供了对该消息的处理函数,这里只讨论CWnd的实现。</P>
<OL>
< align=justify>
<LI>CWnd::OnNcDestroy()
<p>
< align=justify>首先判断当前线程的主窗口是否是该窗口,如果是且模块非DLL,则发送WM_QUIT消息,使得程序结束;</P>
< align=justify>然后,判断当前线程的活动窗口是否是该窗口,如果是则设置活动窗口为NULL;</P>
< align=justify>接着,清理Tooltip窗口,调用Default由Windows缺省处理WM_NCDESTROY消息,UNSUBCLASS,把窗口句柄和MFC窗口对象分离(Detach);</P>
< align=justify>最后,调用虚拟函数PostNcDestoy。</P>
< align=justify></P>
<LI>ostNcDestoy
<p></LI></OL>
<P align=justify>CWnd、CFrameWnd、CView、CControlBar等都覆盖了该函数。文档边框窗口和边框窗口都使用CFrameWnd:ostNcDestroy。</P>
<UL>
<P align=justify>
<LI>CWnd:ostNcDestroy()
<p></LI></UL>
<P align=justify>MFC缺省实现空</P>
<UL>
<P align=justify>
<LI>void CFrameWnd:ostNcDestroy()
<p></LI></UL>
<P align=justify>调用delete this销毁自身这个MFC对象。</P>
<UL>
<P align=justify>
<LI>void CView:ostNcDestroy()
<p></LI></UL>
<P align=justify>调用delete this销毁自身这个MFC对象。</P>
<OL>
<P align=justify>
<LI>析构函数
<p></LI></OL>
<P align=justify>delete this导致析构函数的调用。需要提到的是CFrameWnd和CView的析构函数。</P>
<UL>
<P align=justify>
<LI>CFrameWnd::~CFrameWnd()
<p></LI></UL>
<P align=justify>边框窗口在创建时,把自身加入到模块-线程状态的边框窗口列表m_frameList中。现在,从列表中移走该窗口对象。</P>
<P align=justify>必要的话,删除m_phWndDisable数组。</P>
<UL>
<P align=justify>
<LI>CView::~CView()
<p></LI></UL>
<P align=justify>在视创建时,把自身加入到文档对象的视列表中。现在,从列表中移走该视对象。</P>
<P align=justify>应用程序调用CloseAllDocument关闭文档时。参数为FALSE,它实际上并没有把视从列表中清除,而最后的清除是由析构函数来完成的。</P>
<P align=justify></P>
<P align=justify>至此,边框窗口关闭的过程讨论完毕。下面,结合具体情况──SDI窗口的关闭、MDI主窗口的关闭、MDI子窗口的关闭──描述对WM_CLOSE消息的处理。</P>
<OL>
<OL>
<P align=justify>
<LI><B>SDI窗口、MDI主、子窗口的关闭</B>
<p></LI></OL></OL>
<P align=justify>参考图6-1分析SDI窗口、MDI主、子窗口的关闭流程。</P>
<OL>
<P align=justify>
<LI>SDI窗口的关闭
<p>
<P align=justify>在这种情况下,主窗口将被关闭。首先,关闭应用程序的文档对象。文档对象的虚拟函数OnCloseDocument调用时销毁了主窗口(Windows窗口和MFC窗口对象),同时也导致视、工具条窗口的销毁。主窗口销毁后,应用程序的主窗口对象为空,故发送WM_QUIT消息结束程序。</P>
<P align=justify></P>
<LI>MDI主窗口的关闭
<p>
<P align=justify>首先,关闭应用程序的所有文档对象。文档对象的OnCloseDocument函数关闭文档时,将销毁文档对象对应的文档边框窗口和它的视窗口。这样,所有的MDI子窗口(包括其子窗口视)被销毁,但应用程序的主窗口还在。接着,调用DestroyWindow成员函数销毁主窗口自身,DestroyWindow发现被销毁的是应用程序的主窗口,于是发送WM_QUIT消息结束程序。</P>
<P align=justify></P>
<LI>MDI子窗口(文档边框窗口)的关闭
<p></LI></OL>
<P align=justify>在这种情况下,被关闭的不是主窗口。判断与该文档边框窗口对应的文档对象是否还被其他一个或者多个文档边框窗口使用,如果是,则仅仅销毁该文档边框窗口(包括其子窗口视);否则,关闭文档,文档对象的OnCloseDocument将销毁该文档边框窗口(包括其子窗口视)。</P>
<OL start=7>
<P align=justify>
<LI><B>MFC的DLL</B>
<p>
<P align=justify>一般的,在介绍Windows编程的书中讲述DLL的有关知识较多,而介绍MFC的书则比较少地提到。即使使用MFC来编写动态链接库,对于初步接触DLL的程序员来说,了解DLL的背景知识是必要的。另外,MFC提供了新的手段来帮助编写DLL程序。所以,本节先简洁的介绍有关概念。</P>
<OL>
<P align=justify>
<LI><B>DLL的背景知识</B>
<p></LI></OL></LI></OL>
<OL>
<P align=justify>
<LI>静态链接和动态链接
<p></LI></OL>
<P align=justify>当前链接的目标代码(.obj)如果引用了一个函数却没有定义它,链接程序可能通过两种途径来解决这种从外部对该函数的引用:</P>
<UL>
<P align=justify>
<LI>静态链接
<p></LI></UL>
<P align=justify>链接程序搜索一个或者多个库文件(标准库.lib),直到在某个库中找到了含有所引用函数的对象模块,然后链接程序把这个对象模块拷贝到结果可执行文件(.exe)中。链接程序维护对该函数的所有引用,使它们指向该程序中现在含有该函数拷贝的地方。</P>
<UL>
<P align=justify>
<LI>动态链接
<p></LI></UL>
<P align=justify>链接程序也是搜索一个或者多个库文件(输入库.lib),当在某个库中找到了所引用函数的输入记录时,便把输入记录拷贝到结果可执行文件中,产生一次对该函数的动态链接。这里,输入记录不包含函数的代码或者数据,而是指定一个包含该函数代码以及该函数的顺序号或函数名的动态链接库。</P>
<P align=justify>当程序运行时,Windows装入程序,并寻找文件中出现的任意动态链接。对于每个动态链接,Windows装入指定的DLL并且把它映射到调用进程的虚拟地址空间(如果没有映射的话)。因此,调用和目标函数之间的实际链接不是在链接应用程序时一次完成的(静态),相反,是运行该程序时由Windows完成的(动态)。</P>
<P align=justify>这种动态链接称为加载时动态链接。还有一种动态链接方式下面会谈到。</P>
<OL>
<P align=justify>
<LI>动态链接的方法
<p></LI></OL>
<P align=justify>链接动态链接库里的函数的方法如下:</P>
<UL>
<P align=justify>
<LI>加载时动态链接(Load_time dynamic linking)
<p></LI></UL>
<P align=justify>如上所述。Windows搜索要装入的DLL时,按以下顺序:</P>
<P align=justify>应用程序所在目录→当前目录→Windows SYSTEM目录→Windows目录→PATH环境变量指定的路径。</P>
<UL>
<P align=justify>
<LI>运行时动态链接(Run_time dynamic linking)
<p></LI></UL>
<P align=justify>程序员使用LoadLibrary把DLL装入内存并且映射DLL到调用进程的虚拟地址空间(如果已经作了映射,则增加DLL的引用计数)。首先,LoadLibrary搜索DLL,搜索顺序如同加载时动态链接一样。然后,使用GetProcessAddress得到DLL中输出函数的地址,并调用它。最后,使用FreeLibrary减少DLL的引用计数,当引用计数为0时,把DLL模块从当前进程的虚拟空间移走。</P>
<OL>
<P align=justify>
<LI>输入库(.lib):
<p>
<P align=justify>输入库以.lib为扩展名,格式是COFF(Common object file format)。COFF标准库(静态链接库)的扩展名也是.lib。COFF格式的文件可以用dumpbin来查看。</P>
<P align=justify>输入库包含了DLL中的输出函数或者输出数据的动态链接信息。当使用MFC创建DLL程序时,会生成输入库(.lib)和动态链接库(.dll)。</P>
<P align=justify></P>
<LI>输出文件(.exp)
<p>
<P align=justify>输出文件以.exp为扩展名,包含了输出的函数和数据的信息,链接程序使用它来创建DLL动态链接库。</P>
<P align=justify></P>
<LI>映像文件(.map)
<p>
<P align=justify>映像文件以.map为扩展名,包含了如下信息:</P>
<P align=justify>模块名、时间戳、组列表(每一组包含了形式如section:ffset的起始地址,长度、组名、类名)、公共符号列表(形式如section:ffset的地址,符号名,虚拟地址flat address,定义符号的.obj文件)、入口点如section:ffset、fixup列表。</P>
<P align=justify></P>
<LI>lib.exe工具
<p>
<P align=justify>它可以用来创建输入库和输出文件。通常,不用使用lib.exe,如果工程目标是创建DLL程序,链接程序会完成输入库的创建。</P>
<P align=justify>更详细的信息可以参见MFC使用手册和文档。</P>
<P align=justify></P>
<LI>链接规范(Linkage Specification )
<p>
<P align=justify>这是指链接采用不同编程语言写的函数(Function)或者过程(Procedure)的链接协议。MFC所支持的链接规范是“C”和“C++”,缺省的是“C++”规范,如果要声明一个“C”链接的函数或者变量,则一般采用如下语法:</P>
<P align=justify>#if defined(__cplusplus)</P>
<P align=justify>extern "C"</P>
<P align=justify>{</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>//函数声明(function declarations)</P>
<P align=justify>…</P>
<P align=justify>//变量声明(variables declarations)</P>
<P align=justify>#if defined(__cplusplus)</P>
<P align=justify>}</P>
<P align=justify>#endif</P>
<P align=justify>所有的C标准头文件都是用如上语法声明的,这样它们在C++环境下可以使用。</P>
<P align=justify></P>
<LI>修饰名(Decoration name)
<p></LI></OL>
<P align=justify>“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。</P>
<P align=justify>修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。</P>
<OL>
<OL>
<P align=justify>
<LI><B>调用约定</B>
<p></LI></OL></OL>
<P align=justify>调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:</P>
<OL>
<P align=justify>
<LI>_cdecl
<p>
<P align=justify>按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。</P>
<P align=justify>如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。</P>
<P align=justify>这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。</P>
<P align=justify></P>
<LI>_stdcall
<p>
<P align=justify>按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。</P>
<P align=justify>所有的Win32 API函数都遵循该约定。</P>
<P align=justify></P>
<LI>_fastcall
<p>
<P align=justify>头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。</P>
<P align=justify>未来的编译器可能使用不同的寄存器来存放参数。</P>
<P align=justify></P>
<LI>thiscall
<p>
<P align=justify>仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。</P>
<P align=justify></P>
<LI>naked call
<p>
<P align=justify>采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。</P>
<P align=justify>naked call不是类型修饰符,故必须和_declspec共同使用,如下:</P>
<P align=justify>__declspec( naked ) int func( formal_parameters )</P>
<P align=justify>{</P>
<P align=justify>// Function body</P>
<P align=justify>}</P>
<P align=justify></P>
<LI>过时的调用约定
<p></LI></OL>
<P align=justify>原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:</P>
<P align=justify>#define CALLBACK __stdcall</P>
<P align=justify>#define WINAPI __stdcall</P>
<P align=justify>#define WINAPIV __cdecl</P>
<P align=justify>#define APIENTRY WINAPI</P>
<P align=justify>#define APIPRIVATE __stdcall</P>
<P align=justify>#define PASCAL __stdcall</P>
 楼主| 发表于 2006-12-8 10:19:50 | 显示全部楼层
< align=justify>表7-1显示了一个函数在几种调用约定下的修饰名(表中的“C++”函数指的是“C++”全局函数,不是成员函数),函数原型是void CALLTYPE test(void),CALLTYPE可以是_cdecl、_fastcall、_stdcall。</P>
< align=center>表7-1 不同调用约定下的修饰名</P>
< align=center>
<CENTER>
<TABLE cellSpacing=1 cellPadding=7 width=487 border=1>

<TR>
<TD vAlign=top width="24%">
< align=justify>调用约定 </P></TD>
<TD vAlign=top width="33%">
< align=justify>extern “C”或.C文件 </P></TD>
<TD vAlign=top width="43%">
< align=justify>.cpp, .cxx或/TP编译开关 </P></TD></TR>
<TR>
<TD vAlign=top width="24%">
< align=justify>_cdecl </P></TD>
<TD vAlign=top width="33%">
< align=justify>_test </P></TD>
<TD vAlign=top width="43%">
< align=justify>?test@@ZAXXZ </P></TD></TR>
<TR>
<TD vAlign=top width="24%">
< align=justify>_fastcall </P></TD>
<TD vAlign=top width="33%">
<P align=justify>@test@0 </P></TD>
<TD vAlign=top width="43%">
<P align=justify>?test@@YIXXZ </P></TD></TR>
<TR>
<TD vAlign=top width="24%">
<P align=justify>_stdcall </P></TD>
<TD vAlign=top width="33%">
<P align=justify>_test@0 </P></TD>
<TD vAlign=top width="43%">
<P align=justify>?test@@YGXXZ </P></TD></TR></TABLE></CENTER>
<p>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>MFC的DLL应用程序的类型</B>
<p></LI></OL></OL></OL>
<OL>
<P align=justify>
<LI>静态链接到MFC的规则DLL应用程序
<p>
<P align=justify>该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。输入函数有如下形式:</P>
<P align=justify>extern "C" EXPORT YourExportedFunction( );</P>
<P align=justify>如果没有extern “C”修饰,输出函数仅仅能从C++代码中调用。</P>
<P align=justify>DLL应用程序从CWinApp派生,但没有消息循环。</P>
<P align=justify></P>
<LI>动态链接到MFC的规则DLL应用程序
<p>
<P align=justify>该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。但是,所有从DLL输出的函数应该以如下语句开始:</P>
<P align=justify>AFX_MANAGE_STATE(AfxGetStaticModuleState( ))</P>
<P align=justify>此语句用来正确地切换MFC模块状态。关于MFC的模块状态,后面第9章有详细的讨论。</P>
<P align=justify>其他方面同静态链接到MFC的规则DLL应用程序。</P>
<P align=justify></P>
<LI>扩展DLL应用程序
<p></LI></OL>
<P align=justify>该类DLL应用程序动态链接到MFC,它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。和规则DLL相比,有以下不同:</P>
<OL>
<P align=justify>
<LI>它没有一个从CWinApp派生的对象;
<p>
<P align=justify></P>
<LI>它必须有一个DllMain函数;
<p>
<P align=justify></P>
<LI>DllMain调用AfxInitExtensionModule函数,必须检查该函数的返回值,如果返回0,DllMmain也返回0;
<p>
<P align=justify></P>
<LI>如果它希望输出CRuntimeClass类型的对象或者资源(Resources),则需要提供一个初始化函数来创建一个CDynLinkLibrary对象。并且,有必要把初始化函数输出。
<p>
<P align=justify></P>
<LI>使用扩展DLL的MFC应用程序必须有一个从CWinApp派生的类,而且,一般在InitInstance里调用扩展DLL的初始化函数。
<p></LI></OL>
<P align=justify>为什么要这样做和具体的代码形式,将在后面9.4.2节说明。</P>
<P align=justify></P>
<P align=justify>MFC类库也是以DLL的形式提供的。通常所说的动态链接到MFC 的DLL,指的就是实现MFC核心功能的MFCXX.DLL或者MFCXXD.DLL(XX是版本号,XXD表示调试版)。至于提供OLE(MFCOXXD.DLL或者MFCOXX0.DLL)和NET(MFCNXXD.DLL或者MFCNXX.DLL)服务的DLL就是动态链接到MFC核心DLL的扩展DLL。</P>
<P align=justify>其实,MFCXX.DLL可以认为是扩展DLL的一个特例,因为它也具备扩展DLL的上述特点。</P>
<OL>
<OL>
<P align=justify>
<LI><B>DLL的几点说明</B>
<p></LI></OL></OL>
<OL>
<P align=justify>
<LI>DLL应用程序的入口点是DllMain。
<p>
<P align=justify>对程序员来说,DLL应用程序的入口点是DllMain。</P>
<P align=justify>DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。</P>
<P align=justify>DllMain的函数原型符合DllEntryPoint的要求,有如下结构:</P>
<P align=justify>BOOL WINAPI DllMain (HANDLE hInst, </P>
<P align=justify>ULONG ul_reason_for_call,LPVOID lpReserved)</P>
<P align=justify>{</P>
<P align=justify>switch( ul_reason_for_call ) {</P>
<P align=justify>case DLL_PROCESS_ATTACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_THREAD_ATTACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_THREAD_DETACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_PROCESS_DETACH:</P>
<P align=justify>...</P>
<P align=justify>}</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>参数1是模块句柄;</P>
<P align=justify>参数2是指调用DllMain的类别,四种取值:新的进程要访问DLL;新的线程要访问DLL;一个进程不再使用DLL(Detach from DLL);一个线程不再使用DLL(Detach from DLL)。</P>
<P align=justify>参数3保留。</P>
<P align=justify>如果程序员不指定DllMain,则编译器使用它自己的DllMain,该函数仅仅返回TRUE。</P>
<P align=justify>规则DLL应用程序使用了MFC的DllMain,它将调用DLL程序的应用程序对象(从CWinApp派生)的InitInstance函数和ExitInstance函数。</P>
<P align=justify>扩展DLL必须实现自己的DllMain。</P>
<P align=justify></P>
<LI>_DllMainCRTStartup
<p>
<P align=justify>为了使用“C”运行库(CRT,C Run time Library)的DLL版本(多线程),一个DLL应用程序必须指定_DllMainCRTStartup为入口函数,DLL的初始化函数必须是DllMain。</P>
<P align=justify>_DllMainCRTStartup完成以下任务:当进程或线程捆绑(Attach)到DLL时为“C”运行时的数据(C Runtime Data)分配空间和初始化并且构造全局“C++”对象,当进程或者线程终止使用DLL(Detach)时,清理C Runtime Data并且销毁全局“C++”对象。它还调用DllMain和RawDllMain函数。</P>
<P align=justify>RawDllMain在DLL应用程序动态链接到MFC DLL时被需要,但它是静态的链接到DLL应用程序的。在讲述状态管理时解释其原因。</P>
<P align=justify></P>
<LI>DLL的函数和数据
<p>
<P align=justify>DLL的函数分为两类:输出函数和内部函数。输出函数可以被其他模块调用,内部函数在定义它们的DLL程序内部使用。</P>
<P align=justify>虽然DLL可以输出数据,但一般的DLL程序的数据仅供内部使用。</P>
<P align=justify></P>
<P align=justify></P>
<LI>DLL程序和调用其输出函数的程序的关系
<p></LI></OL>
<P align=justify>DLL模块被映射到调用它的进程的虚拟地址空间。</P>
<P align=justify>DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。</P>
<P align=justify>DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。</P>
<P align=justify>DLL使用调用进程的栈。</P>
<P align=justify>DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。</P>
<OL>
<OL>
<P align=justify>
<LI><B>输出函数的方法</B>
<p></LI></OL></OL>
<OL>
<P align=justify>
<LI>传统的方法
<p>
<P align=justify>在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:</P>
<P align=justify>entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]</P>
<P align=justify>其中:</P>
<P align=justify>entryname是输出的函数或者数据被引用的名称;</P>
<P align=justify>internalname同entryname;</P>
<P align=justify>@ordinal表示在输出表中的顺序号(index);</P>
<P align=justify>NONAME仅仅在按顺序号输出时被使用(不使用entryname);</P>
<P align=justify>DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。</P>
<P align=justify>上述各项中,只有entryname项是必须的,其他可以省略。</P>
<P align=justify>对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。</P>
<P align=justify>如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。</P>
<P align=justify></P>
<LI>在命令行输出
<p>
<P align=justify>对链接程序LINK指定/EXPORT命令行参数,输出有关函数。</P>
<P align=justify></P>
<LI>使用MFC提供的修饰符号_declspec(dllexport)
<p></LI></OL>
<P align=justify>在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。MFC提供了一些宏,就有这样的作用,如表7-2所示。</P>
<P align=center>表7-2 MFC定义的输入输出修饰符</P>
<P align=left>
<TABLE cellSpacing=1 cellPadding=7 width=406 border=1>

<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>宏名称 </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>宏内容 </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_CLASS_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_API_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_DATA_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_CLASS_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_API_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_DATA_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_CLASS </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_CLASS_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_CLASS_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_API </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_API_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_API_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_DATA </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_DATA_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_DATA_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_DATADEF </P></TD>
<TD vAlign=top width="55%"> </TD></TR></TABLE>
<p>
<P align=justify></P>
<P align=justify>像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。</P>
<P align=justify>要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:</P>
<P align=justify>class AFX_EXT_CLASS CTextDoc : public CDocument</P>
<P align=justify>{</P>
<P align=justify>…</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>extern "C" AFX_EXT_API void WINAPI InitMYDLL();</P>
<P align=justify></P>
<P align=justify>这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。</P>
<P align=justify>在“C++”下定义“C”函数,需要加extern “C”关键词。输出的“C”函数可以从“C”代码里调用。</P>
 楼主| 发表于 2006-12-8 10:21:26 | 显示全部楼层
<OL start=8>
< align=justify>
<LI><B>MFC的进程和线程</B>
<p>
<OL>
< align=justify>
<LI><B>Win32的进程和线程概念</B>
<p></LI></OL></LI></OL>
< align=justify>进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其他操作系统资源(如进程创建的文件、管道、同步对象等)组成。一个应用程序可以有一个或多个进程,一个进程可以有一个或多个线程,其中一个是主线程。</P>
< align=justify>线程是操作系统分时调度分配CPU时间的基本实体。一个线程可以执行程序的任意部分的代码,即使这部分代码被另一个线程并发地执行;一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。</P>
< align=justify>之所以有线程这个概念,是因为以线程而不是进程为调度对象效率更高:</P>
<UL>
< align=justify>
<LI>由于创建新进程必须加载代码,而线程要执行的代码已经被映射到进程的地址空间,所以创建、执行线程的速度比进程更快。
<p>
< align=justify></P>
<LI>一个进程的所有线程共享进程的地址空间和全局变量,所以简化了线程之间的通讯。
<p></LI></UL>
<OL>
<OL>
< align=justify>
<LI><B>Win32的进程处理简介</B>
<p>
< align=justify>因为MFC没有提供类处理进程,所以直接使用了Win32 API函数。</P>
<OL>
< align=justify>
<LI><B>进程的创建</B>
<p>
<P align=justify>调用CreateProcess函数创建新的进程,运行指定的程序。CreateProcess的原型如下:</P>
<P align=justify>BOOL CreateProcess(</P>
<P align=justify>LPCTSTR lpApplicationName,</P>
<P align=justify>LPTSTR lpCommandLine,</P>
<P align=justify>LPSECURITY_ATTRIBUTES lpProcessAttributes,</P>
<P align=justify>LPSECURITY_ATTRIBUTES lpThreadAttributes,</P>
<P align=justify>BOOL bInheritHandles,</P>
<P align=justify>DWORD dwCreationFlags,</P>
<P align=justify>LPVOID lpEnvironment,</P>
<P align=justify>LPCTSTR lpCurrentDirectory,</P>
<P align=justify>LPSTARTUPINFO lpStartupInfo,</P>
<P align=justify>LPPROCESS_INFORMATION lpProcessInformation</P>
<P align=justify>);</P>
<P align=justify>其中:</P>
<P align=justify>lpApplicationName指向包含了要运行模块名字的字符串。</P>
<P align=justify>lpCommandLine指向命令行字符串。</P>
<P align=justify>lpProcessAttributes描述进程的安全性属性,NT下有用。</P>
<P align=justify>lpThreadAttributes描述进程初始线程(主线程)的安全性属性,NT下有用。</P>
<P align=justify>bInHeritHandles表示子进程(被创建的进程)是否可以继承父进程的句柄。可以继承的句柄有线程句柄、有名或无名管道、互斥对象、事件、信号量、映像文件、普通文件和通讯端口等;还有一些句柄不能被继承,如内存句柄、DLL实例句柄、GDI句柄、URER句柄等等。</P>
<P align=justify>子进程继承的句柄由父进程通过命令行方式或者进程间通讯(IPC)方式由父进程传递给它。</P>
<P align=justify>dwCreationFlags表示创建进程的优先级类别和进程的类型。创建进程的类型分控制台进程、调试进程等;优先级类别用来控制进程的优先级别,分Idle、Normal、High、Real_time四个类别。</P>
<P align=justify>lpEnviroment指向环境变量块,环境变量可以被子进程继承。</P>
<P align=justify>lpCurrentDirectory指向表示当前目录的字符串,当前目录可以继承。</P>
<P align=justify>lpStartupInfo指向StartupInfo结构,控制进程的主窗口的出现方式。</P>
<P align=justify>lpProcessInformation指向PROCESS_INFORMATION结构,用来存储返回的进程信息。</P>
<P align=justify>从其参数可以看出创建一个新的进程需要指定什么信息。</P>
<P align=justify>从上面的解释可以看出,一个进程包含了很多信息。若进程创建成功的话,返回一个进程信息结构类型的指针。进程信息结构如下:</P>
<P align=justify>typedef struct _PROCESS_INFORMATION {</P>
<P align=justify>HANDLE hProcess; </P>
<P align=justify>HANDLE hThread; </P>
<P align=justify>DWORD dwProcessId; </P>
<P align=justify>DWORD dwThreadId; </P>
<P align=justify>}PROCESS_INFORMATION; </P>
<P align=justify>进程信息结构包括进程句柄,主线程句柄,进程ID,主线程ID。</P>
<P align=justify></P>
<LI><B>进程的终止</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>进程在以下情况下终止:</P>
<UL>
<P align=justify>
<LI>调用ExitProcess结束进程;
<p>
<P align=justify></P>
<LI>进程的主线程返回,隐含地调用ExitProcess导致进程结束;
<p>
<P align=justify></P>
<LI>进程的最后一个线程终止;
<p>
<P align=justify></P>
<LI>调用TerminateProcess终止进程。
<p>
<P align=justify></P>
<LI>当要结束一个GDI进程时,发送WM_QUIT消息给主窗口,当然也可以从它的任一线程调用ExitProcess。
<p></LI></UL>
<OL>
<OL>
<P align=justify>
<LI><B>Win32的线程</B>
<p>
<OL>
<P align=justify>
<LI><B>线程的创建</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>使用CreateThread函数创建线程,CreateThread的原型如下:</P>
<P align=justify>HANDLE CreateThread(</P>
<P align=justify>LPSECURITY_ATTRIBUTES lpThreadAttributes,</P>
<P align=justify>DWORD dwStackSize,</P>
<P align=justify>LPTHREAD_START_ROUTINE lpStartAddress,</P>
<P align=justify>LPVOID lpParameter,</P>
<P align=justify>DWORD dwCreationFlags, // creation flags</P>
<P align=justify>LPDWORD lpThreadId</P>
<P align=justify>);</P>
<P align=justify>其中:</P>
<P align=justify>lpThreadAttributes表示创建线程的安全属性,NT下有用。</P>
<P align=justify>dwStackSize指定线程栈的尺寸,如果为0则与进程主线程栈相同。</P>
<P align=justify>lpStartAddress指定线程开始运行的地址。</P>
<P align=justify>lpParameter表示传递给线程的32位的参数。</P>
<P align=justify>dwCreateFlages表示是否创建后挂起线程(取值CREATE_SUSPEND),挂起后调用ResumeThread继续执行。</P>
<P align=justify>lpThreadId用来存放返回的线程ID。</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>线程的优先级别
<p></LI></UL>
<P align=justify>进程的每个优先级类包含了五个线程的优先级水平。在进程的优先级类确定之后,可以改变线程的优先级水平。用SetPriorityClass设置进程优先级类,用SetThreadPriority设置线程优先级水平。</P>
<P align=justify>Normal级的线程可以被除了Idle级以外的任意线程抢占。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>线程的终止</B>
<p></LI></OL></OL></OL>
<P align=justify>以下情况终止一个线程:</P>
<UL>
<P align=justify>
<LI>调用了ExitThread函数;
<p>
<P align=justify></P>
<LI>线程函数返回:主线程返回导致ExitProcess被调用,其他线程返回导致ExitThread被调用;
<p>
<P align=justify></P>
<LI>调用ExitProcess导致进程的所有线程终止;
<p>
<P align=justify></P>
<LI>调用TerminateThread终止一个线程;
<p>
<P align=justify></P>
<LI>调用TerminateProcess终止一个进程时,导致其所有线程的终止。
<p></LI></UL>
<P align=justify>当用TerminateProcess或者TerminateThread终止进程或线程时,DLL的入口函数DllMain不会被执行(如果有DLL的话)。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>线程局部存储</B>
<p></LI></OL></OL></OL>
<P align=justify>如果希望每个线程都可以有线程局部(Thread local)的静态存储数据,可以使用TLS线程局部存储技术。TLS为进程分配一个TLS索引,进程的每个线程通过这个索引存取自己的数据变量的拷贝。</P>
<P align=justify>TLS对DLL是非常有用的。当一个新的进程使用DLL时,在DLL入口函数DllMain中使用TlsAlloc分配TLS索引,TLS索引就作为进程私有的全局变量被保存;以后,当该进程的新的线程使用DLL时(Attahced to DLL),DllMain给它分配动态内存并且使用TlsSetValue把线程私有的数据按索引保存。DLL函数可以使用TlsGetValue按索引读取调用线程的私有数据。</P>
<P align=justify>TLS函数如下:</P>
<UL>
<P align=justify>
<LI>DWORD TlsAlloc()
<p></LI></UL>
<P align=justify>在进程或DLL初始化时调用,并且把返回值(索引值)作为全局变量保存。</P>
<UL>
<P align=justify>
<LI>BOOL TlsSetValue(
<p></LI></UL>
<DIR>
<P align=justify>DWORD dwTlsIndex, //TLS index to set value for </P>
<P align=justify>LPVOID lpTlsValue //value to be stored </P></DIR>
<P align=justify>);</P>
<P align=justify>其中:</P>
<P align=justify>dwTlsIndex是TlsAlloc分配的索引。</P>
<P align=justify>lpTlsValue是线程在TLS槽中存放的数据指针,指针指向线程要保存的数据。</P>
<P align=justify>线程首先分配动态内存并保存数据到此内存中,然后调用TlsSetValue保存内存指针到TLS槽。</P>
<UL>
<P align=justify>
<LI>LPVOID TlsGetValue(
<p></LI></UL>
<P align=justify>DWORD dwTlsIndex // TLS index to retrieve value for</P>
<P align=justify>);</P>
<P align=justify>其中:</P>
<P align=justify>dwTlsIndex是TlsAlloc分配的索引。</P>
<P align=justify>当要存取保存的数据时,使用索引得到数据指针。</P>
<UL>
<P align=justify>
<LI>BOOL TlsFree(
<p></LI></UL>
<P align=justify>DWORD dwTlsIndex // TLS index to free</P>
<P align=justify>);</P>
<P align=justify>其中:</P>
<P align=justify>dwTlsIndex是TlsAlloc分配的索引。</P>
<P align=justify>当每一个线程都不再使用局部存储数据时,线程释放它分配的动态内存。在TLS索引不再需要时,使用TlsFree释放索引。</P>
<OL>
<OL>
<P align=justify>
<LI><B>线程同步</B>
<p>
<P align=justify>同步可以保证在一个时间内只有一个线程对某个资源(如操作系统资源等共享资源)有控制权。共享资源包括全局变量、公共数据成员或者句柄等。同步还可以使得有关联交互作用的代码按一定的顺序执行。</P>
<P align=justify>Win32提供了一组对象用来实现多线程的同步。</P>
<P align=justify>这些对象有两种状态:获得信号(Signaled)或者没有或则信号(Not signaled)。线程通过Win32 API提供的同步等待函数(Wait functions)来使用同步对象。一个同步对象在同步等待函数调用时被指定,调用同步函数地线程被阻塞(blocked),直到同步对象获得信号。被阻塞的线程不占用CPU时间。</P>
<OL>
<P align=justify>
<LI><B>同步对象</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>同步对象有:Critical_section(关键段),Event(事件),Mutex(互斥对象),Semaphores(信号量)。</P>
<P align=justify>下面,解释怎么使用这些同步对象。</P>
<OL>
<P align=justify>
<LI>关键段对象:
<p>
<P align=justify>首先,定义一个关键段对象cs:</P>
<P align=justify>CRITICAL_SECTION cs;</P>
<P align=justify>然后,初始化该对象。初始化时把对象设置为NOT_SINGALED,表示允许线程使用资源:</P>
<P align=justify>InitializeCriticalSection(&amp;cs);</P>
<P align=justify>如果一段程序代码需要对某个资源进行同步保护,则这是一段关键段代码。在进入该关键段代码前调用EnterCriticalSection函数,这样,其他线程都不能执行该段代码,若它们试图执行就会被阻塞。</P>
<P align=justify>完成关键段的执行之后,调用LeaveCriticalSection函数,其他的线程就可以继续执行该段代码。如果该函数不被调用,则其他线程将无限期的等待。</P>
<P align=justify></P>
<LI>事件对象
<p>
<P align=justify>首先,调用CreateEvent函数创建一个事件对象,该函数返回一个事件句柄。然后,可以设置(SetEvent)或者复位(ResetEvent)一个事件对象,也可以发一个事件脉冲(PlusEvent),即设置一个事件对象,然后复位它。复位有两种形式:自动复位和人工复位。在创建事件对象时指定复位形式。。</P>
<P align=justify>自动复位:当对象获得信号后,就释放下一个可用线程(优先级别最高的线程;如果优先级别相同,则等待队列中的第一个线程被释放)。</P>
<P align=justify>人工复位:当对象获得信号后,就释放所有可利用线程。</P>
<P align=justify>最后,使用CloseHandle销毁创建的事件对象。</P>
<P align=justify></P>
<LI>互斥对象
<p>
<P align=justify>首先,调用CreateMutex创建互斥对象;然后,调用等待函数,可以的话利用关键资源;最后,调用RealseMutex释放互斥对象。</P>
<P align=justify>互斥对象可以在进程间使用,但关键段对象只能用于同一进程的线程之间。</P>
<P align=justify></P>
<LI>信号量对象
<p>
<P align=justify>在Win32中,信号量的数值变为0时给以信号。在有多个资源需要管理时可以使用信号量对象。</P>
<P align=justify>首先,调用CreateSemaphore创建一个信号量;然后,调用等待函数,如果允许的话,则利用关键资源;最后,调用RealeaseSemaphore释放信号量对象。</P>
<P align=justify></P>
<LI>此外,还有其他句柄可以用来同步线程:
<p></LI></OL>
<P align=justify>文件句柄(FILE HANDLES)</P>
<P align=justify>命名管道句柄(NAMED PIPE HANDELS)</P>
<P align=justify>控制台输入缓冲区句柄(CONSOLE INPUT BUFFER HANDLES)</P>
<P align=justify>通讯设备句柄(COMMUNICTION DEVICE HANDLES)</P>
<P align=justify>进程句柄(PROCESS HANDLES)</P>
<P align=justify>线程句柄(THREAD HANDLES)</P>
<P align=justify>例如,当一个进程或线程结束时,进程或线程句柄获得信号,等待该进程或者线程结束的线程被释放。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>等待函数</B>
<p></LI></OL></OL></OL>
<P align=justify>Win32提供了一组等待函数用来让一个线程阻塞自己的执行。等待函数分三类:</P>
<OL>
<P align=justify>
<LI>等待单个对象的(FOR SINGLE OBJECT):
<p>
<P align=justify>这类函数包括:</P>
<P align=justify>SignalObjectAndWait</P>
<P align=justify>WaitForSingleObject</P>
<P align=justify>WaitForSingleObjectEx</P>
<P align=justify>函数参数包括同步对象的句柄和等待时间等。</P>
<P align=justify>在以下情况下等待函数返回:</P>
<P align=justify>同步对象获得信号时返回;</P>
<P align=justify>等待时间达到了返回:如果等待时间不限制(Infinite),则只有同步对象获得信号才返回;如果等待时间为0,则在测试了同步对象的状态之后马上返回。</P>
<P align=justify></P>
<LI>等待多个对象的(FOR MULTIPLE OBJECTS)
<p>
<P align=justify>这类函数包括:</P>
<P align=justify>WaitForMultipleObjects</P>
<P align=justify>WaitForMultipleObjectsEx</P>
<P align=justify>MsgWaitForMultipleObjects</P>
<P align=justify>MsgWaitForMultipleObjectsEx</P>
<P align=justify>函数参数包括同步对象的句柄,等待时间,是等待一个还是多个同步对象等等。</P>
<P align=justify>在以下情况下等待函数返回:</P>
<P align=justify>一个或全部同步对象获得信号时返回(在参数中指定是等待一个或多个同步对象);</P>
<P align=justify>等待时间达到了返回:如果等待时间不限制(Infinite),则只有同步对象获得信号才返回;如果等待时间为0,则在测试了同步对象的状态之后马上返回。</P>
<P align=justify></P>
<LI>可以发出提示的函数(ALTERABLE)
<p></LI></OL>
<P align=justify>这类函数包括:</P>
<P align=justify>MsgWaitForMultipleObjectsEx</P>
<P align=justify>SignalObjectAndWait</P>
<P align=justify>WaitForMultipleObjectsEx</P>
<P align=justify>WaitForSingleObjectEx</P>
<P align=justify>这些函数主要用于重叠(Overlapped)的I/O(异步I/O)。</P>
 楼主| 发表于 2006-12-8 10:22:40 | 显示全部楼层
<OL>
<OL>
< align=justify>
<LI><B>MFC的线程处理</B>
<p>
< align=justify>在Win32 API的基础之上,MFC提供了处理线程的类和函数。处理线程的类是CWinThread,函数是AfxBeginThread、AfxEndThread等。</P>
< align=justify>表5-6解释了CWinThread的成员变量和函数。</P>
< align=justify>CWinThread是MFC线程类,它的成员变量m_hThread和m_hThreadID是对应的Win32线程句柄和线程ID。</P>
< align=justify>MFC明确区分两种线程:用户界面线程(User interface thread)和工作者线程(Worker thread)。用户界面线程一般用于处理用户输入并对用户产生的事件和消息作出应答。工作者线程用于完成不要求用户输入的任务,如耗时计算。</P>
< align=justify>Win32 API并不区分线程类型,它只需要知道线程的开始地址以便它开始执行线程。MFC为用户界面线程特别地提供消息泵来处理用户界面的事件。CWinApp对象是用户界面线程对象的一个例子,CWinApp从类CWinThread派生并处理用户产生的事件和消息。</P>
<OL>
< align=justify>
<LI><B>创建用户界面线程</B>
<p></LI></OL></LI></OL></OL>
< align=justify>通过以下步骤创建一个用户界面线程:</P>
<UL>
< align=justify>
<LI>从CWinThread派生一个有动态创建能力的类。使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来支持动态创建。
<p>
< align=justify></P>
<LI>覆盖CWinThread的一些虚拟函数,可以覆盖的函数见表5-4关于CWinThread的部分。其中,函数InitInstance是必须覆盖的,ExitInstance通常是要覆盖的。
<p>
<P align=justify></P>
<LI>使用AfxBeginThread创建MFC线程对象和Win32线程对象。如果创建线程时没有指定CREATE_SUSPENDED,则开始执行线程。
<p>
<P align=justify></P>
<LI>如果创建线程是指定了CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。
<p></LI></UL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>创建工作者线程</B>
<p>
<P align=justify>程序员不必从CWinThread派生新的线程类,只需要提供一个控制函数,由线程启动后执行该函数。</P>
<P align=justify>然后,使用AfxBeginThread创建MFC线程对象和Win32线程对象。如果创建线程时没有指定CREATE_SUSPENDED(创建后挂起),则创建的新线程开始执行。</P>
<P align=justify>如果创建线程是指定了CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。</P>
<P align=justify>虽然程序员没有从CWinThread派生类,但是MFC给工作者线程提供了缺省的CWinThread对象。</P>
<P align=justify></P>
<LI><B>AfxBeginThread</B>
<p></LI></OL></OL></OL>
<P align=justify>用户界面线程和工作者线程都是由AfxBeginThread创建的。现在,考察该函数:MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,分别有如下的原型和过程:</P>
<OL>
<P align=justify>
<LI>用户界面线程的AfxBeginThread
<p>
<P align=justify>用户界面线程的AfxBeginThread的原型如下:</P>
<P align=justify>CWinThread* AFXAPI AfxBeginThread(</P>
<P align=justify>CRuntimeClass* pThreadClass,</P>
<P align=justify>int nPriority, </P>
<P align=justify>UINT nStackSize, </P>
<P align=justify>DWORD dwCreateFlags,</P>
<P align=justify>LPSECURITY_ATTRIBUTES lpSecurityAttrs)</P>
<P align=justify>其中:</P>
<P align=justify>参数1是从CWinThread派生的RUNTIME_CLASS类;</P>
<P align=justify>参数2指定线程优先级,如果为0,则与创建该线程的线程相同;</P>
<P align=justify>参数3指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;</P>
<P align=justify>参数4是一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。</P>
<P align=justify>参数5表示线程的安全属性,NT下有用。</P>
<P align=justify></P>
<LI>工作者线程的AfxBeginThread
<p>
<P align=justify>工作者线程的AfxBeginThread的原型如下:</P>
<P align=justify>CWinThread* AFXAPI AfxBeginThread(</P>
<P align=justify>AFX_THREADPROC pfnThreadProc, </P>
<P align=justify>LPVOID pParam,</P>
<P align=justify>int nPriority, </P>
<P align=justify>UINT nStackSize, </P>
<P align=justify>DWORD dwCreateFlags,</P>
<P align=justify>LPSECURITY_ATTRIBUTES lpSecurityAttrs)</P>
<P align=justify>其中:</P>
<P align=justify>参数1指定控制函数的地址;</P>
<P align=justify>参数2指定传递给控制函数的参数;</P>
<P align=justify>参数3、4、5分别指定线程的优先级、堆栈大小、创建标识、安全属性,含义同用户界面线程。</P>
<P align=justify></P>
<LI>AfxBeginThread创建线程的流程
<p></LI></OL>
<P align=justify>不论哪个AfxBeginThread,首先都是创建MFC线程对象,然后创建Win32线程对象。在创建MFC线程对象时,用户界面线程和工作者线程的创建分别调用了不同的构造函数。用户界面线程是从CWinThread派生的,所以,要先调用派生类的缺省构造函数,然后调用CWinThread的缺省构造函数。图8-1中两个构造函数所调用的CommonConstruct是MFC内部使用的成员函数。</P><IMG src="http://www.vczx.com/tutorial/mfc/image140.gif" align=left>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>CreateThread和_AfxThreadEntry</B>
<p>
<P align=justify>MFC使用CWinThread::CreateThread创建线程,不论对工作者线程或用户界面线程,都指定线程的入口函数是_AfxThreadEntry。_AfxThreadEntry调用AfxInitThread初始化线程。</P>
<P align=justify>CreateThread和_AfxThreadEntry在线程的创建过程中使用同步手段交互等待、执行。CreateThread由创建线程执行,_AfxThreadEntry由被创建的线程执行,两者通过两个事件对象(hEvent和hEvent2)同步:</P>
<P align=justify>在创建了新线程之后,创建线程将在hEvent事件上无限等待直到新线程给出创建结果;新线程在创建成功或者失败之后,触发事件hEvent让父线程运行,并且在hEven2上无限等待直到父线程退出CreateThread函数;父线程(创建线程)因为hEvent的置位结束等待,继续执行,退出CreateThread之前触发hEvent2事件;新线程(子线程)因为hEvent2的置位结束等待,开始执行控制函数(工作者线程)或者进入消息循环(用户界面线程)。</P>
<P align=justify>MFC在线程创建中使用了如下数据结构:</P>
<P align=justify>struct _AFX_THREAD_STARTUP</P>
<P align=justify>{</P>
<P align=justify>//传递给线程启动的参数(IN)</P>
<P align=justify>_AFX_THREAD_STATE* pThreadState;//父线程的线程状态</P>
<P align=justify>CWinThread* pThread; //新创建的MFC线程对象</P>
<P align=justify>DWORD dwCreateFlags; //线程创建标识</P>
<P align=justify>_PNH pfnNewHandler; //新线程的句柄</P>
<P align=justify>HANDLE hEvent; //同步事件,线程创建成功或失败后置位</P>
<P align=justify>HANDLE hEvent2; //同步事件,新线程恢复执行后置位</P>
<P align=justify></P>
<P align=justify>//返回给创建线程的参数,在新线程恢复执行后赋值</P>
<P align=justify>BOOL bError; //如果创建发生错误,TRUE</P>
<P align=justify>};</P>
<P align=justify>该结构作为线程开始函数的参数被传递给_beginthreadex函数来创建和启动线程。_beginthreadex函数是“C”的线程创建函数,具有如下原型:</P>
<P align=justify>unsigned long _beginthreadex(</P>
<P align=justify>void *security,</P>
<P align=justify>unsigned stack_size,</P>
<P align=justify>unsigned ( __stdcall *start_address )( void * ),</P>
<P align=justify>void *arglist,</P>
<P align=justify>unsigned initflag,</P>
<P align=justify>unsigned *thrdaddr );</P>
<P align=justify></P>
<P align=justify>图8-2描述了上述过程。图中表示,_AfxThreadEntry在启动线程时,将创建本线程的线程状态,并且继承父线程的模块状态。关于MFC状态,见第9章。</P>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image141.gif" align=left>
<P align=justify></P>
<P align=justify> </P><IMG src="http://www.vczx.com/tutorial/mfc/image142.gif" align=left>
<P align=justify> </P>
<P align=justify></P>
<LI><B>线程的结束</B>
<p>
<P align=justify>从图8-2可以看出,AfxEndThread用来结束调用它的线程:它将清理本线程创建的MFC对象和释放线程局部存储分配的内存空间;调用CWinThread的虚拟函数Delete;调用“C”的结束线程函数_endthreadex释放分配给线程的资源,但是不关闭线程句柄。</P>
<P align=justify>CWinThread:elete的缺省实现是:如果本线程的成员函数m_bDelete为TRUE,则调用“C”运算符号delete销毁MFC线程对象自身(delete this),这将导致线程对象的析构函数被调用。若析构函数检测线程句柄非空则调用CloseHandle关闭它。</P>
<P align=justify>通常,让m_bDelete为TRUE以便自动地销毁线程对象,释放内存空间(MFC内存对象在堆中分配)。但是,有时候,在线程结束之后(Win32线程已经不存在)保留MFC线程对象是有用的,当然程序员自己最后要记得销毁该线程对象。</P>
<P align=justify></P>
<LI><B>实现线程的消息循环</B>
<p></LI></OL></OL></OL>
<P align=justify>在MFC中,消息循环是由线程完成的。一般地,可以使用MFC缺省的消息循环(即使用函数CWindThrad::Run),但是,有些时候需要程序员自己实现一个线程的消息循环,比如在用户界面线程进行一个长时间计算处理或者等待另一个线程时。一般有如下形式:</P>
<P align=justify>while ( bDoingBackgroundProcessing) </P>
<P align=justify>{</P>
<P align=justify>MSG msg;</P>
<P align=justify>while ( :eekMessage( &amp;msg, NULL,0, 0, PM_NOREMOVE ) )</P>
<P align=justify>{</P>
<DIR>
<P align=justify>if ( !PumpMessage( ) )</P>
<P align=justify>{</P>
<DIR>
<P align=justify>bDoingBackgroundProcessing = FALSE; </P>
<P align=justify>:ostQuitMessage( );</P>
<P align=justify>break;</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>// let MFC do its idle processing</P>
<P align=justify>LONG lIdle = 0;</P>
<P align=justify>while ( AfxGetApp()-&gt;OnIdle(lIdle++ ) );</P>
<P align=justify>// Perform some background processing here </P>
<P align=justify>// using another call to OnIdle</P>
<P align=justify>}</P>
<P align=justify>该段代码的解释参见图5-3对线程的Run函数的图解。</P>
<P align=justify></P>
<P align=justify>程序员实现线程的消息循环有两个好处,一是顾及了MFC的Idle处理机制;二是在长时间的处理中可以响应用户产生的事件或者消息。</P>
<P align=justify>在同步对象上等待其他线程时,也可以使用同样的方式,只要把条件</P>
<P align=justify>bDoingBackgroundProcessing</P>
<P align=justify>换成如下形式:</P>
<P align=justify>WaitForSingObject(hHandleOfEvent,0) == WAIT_TIMEOUT</P>
<P align=justify>即可。</P>
<P align=justify>MFC处理线程和进程时还引入了一个重要的概念:状态,如线程状态(Thread State)、进程状态(Process State)、模块状态(Module State)等。由于这个概念在MFC中占有重要地位,涉及的内容比较多,所以专门在下一章来讲述它。</P>
 楼主| 发表于 2006-12-8 10:23:15 | 显示全部楼层
<STRONG>MFC的状态</STRONG>
<p>
< align=justify>MFC定义了多种状态信息,这里要介绍的是模块状态、进程状态、线程状态。这些状态可以组合在一起,例如MFC句柄映射就是模块和线程局部有效的,属于模块-线程状态的一部分。</P>
<OL>
< align=justify>
<LI><B>模块状态</B>
<p>
< align=justify>这里模块的含义是:一个可执行的程序或者一个使用MFC DLL的DLL,比如一个OLE控件就是一个模块。</P>
< align=justify>一个应用程序的每一个模块都有一个状态,模块状态包括这样一些信息:用来加载资源的 Windows实例句柄、指向当前CWinApp或者CWinThread对象的指针、OLE模块的引用计数、Windows对象与相应的MFC对象之间的映射。只有单一模块的应用程序的状态如图9-1所示。</P><IMG src="http://www.vczx.com/tutorial/mfc/image143.gif" align=left>
< align=justify>m_pModuleState 指针是线程对象的成员变量,指向当前模块状态信息(一个AFX_MODULE_STATE结构变量)。当程序运行进入某个特定的模块时,必须保证当前使用的模块状态是有效的模块状态──是这个特定模块的模块状态。所以,每个线程对象都有一个指针指向有效的模块状态,每当进入某个模块时都要使它指向有效模块状态,这对维护应用程序全局状态和每个模块状态的完整性来说是非常重要的。为了作到这一点,每个模块的所有入口点有责任实现模块状态的切换。模块的入口点包括:DLL的输出函数;OLE/COM界面的成员函数;窗口过程。</P><IMG src="http://www.vczx.com/tutorial/mfc/image144.gif" align=left>
< align=justify>在讲述窗口过程和动态链接到MFC DLL的规则DLL时,曾提到了语句AFX_MANAGE_STATE(AfxGetStaticModuleState( )),它就是用来在入口点切换模块状态的。其实现机制将在后面9.4.1节讲解。</P>
< align=justify>多个模块状态之间切换的示意图如图9-2所示。</P>
< align=justify>图9-2中,m_pModuleState总是指向当前模块的状态。</P>
< align=justify></P>
<LI><B>模块、进程和线程状态的数据结构</B>
<p>
< align=justify>MFC定义了一系列类或者结构,通过它们来实现状态信息的管理。这一节将描述它们的关系,并逐一解释它们的数据结构、成员函数等。</P>
<OL>
<P align=justify>
<LI><B>层次关系</B>
<p>
<P align=justify>图9-3显示了线程状态、模块状态、线程-模块状态等几个类的层次关系:</P>
<P align=justify>线程状态用类_AFX_THREAD_STATE描述,模块状态用类AFX_MODULE_STATE描述,模块-线程状态用类AFX_MODULE_THREAD_STATE描述。这些类从类CNoTrackObject派生。进程状态类用_AFX_BASE_MODULE_STATE描述,从模块状态类AFX_MODULE_STATE派生。进程状态是了一个可以独立执行的MFC应用程序的模块状态。还有其他状态如DLL的模块状态等也从模块状态类_AFX_MODULE_STATE派生。</P>
<P align=justify>图9-4显示了这几个类的交互关系。</P>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image145.gif" align=left>
<P align=justify></P><IMG src="http://www.vczx.com/tutorial/mfc/image146.gif" align=left>
<P align=justify></P>
<P align=justify>从图9-4可以看出:首先,每个线程有一个线程状态,线程状态的指针m_pModuleState和m_pPreModuleState分别指向线程当前运行模块的状态或前一运行模块的状态;其次,每一个模块状态都有一个线程局部的变量用来存储模块-线程状态。</P>
<P align=justify>下面各小节列出状态信息管理所涉及的各个类的定义。</P>
<P align=justify></P>
<LI><B>CNoTrackObject类</B>
<p>
<P align=justify>在图9-3中, CnoTrackObject是根类,所有状态类都是从它这里派生的,其定义如下:</P>
<P align=justify>class CNoTrackObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>void* PASCAL operator new(size_t nSize);</P>
<P align=justify>void PASCAL operator delete(void*);</P>
<P align=justify></P>
<P align=justify>#if defined(_DEBUG) &amp;&amp; !defined(_AFX_NO_DEBUG_CRT)</P>
<P align=justify>void* PASCAL operator new(size_t nSize, LPCSTR, int);</P>
<P align=justify>#endif</P>
<P align=justify>virtual ~CNoTrackObject() { }</P>
<P align=justify>};</P>
<P align=justify>该类的析构函数是虚拟函数;而且,CNoTrackObject重载new操作符用来分配内存,重载delete操作符号用来释放内存,内部通过LocalAlloc/LocalFree提供了一个低层内存分配器(Low_level alloctor)。</P>
<P align=justify></P>
<LI><B>AFX_MODULE_STATE类</B>
<p>
<P align=justify>AFX_MODULE_STATE类的定义如下:</P>
<P align=justify>// AFX_MODULE_STATE (global data for a module)</P>
<P align=justify>class AFX_MODULE_STATE : public CNoTrackObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>AFX_MODULE_STATE(BOOL bDLL,WNDPROC pfnAfxWndProc,</P>
<P align=justify>DWORD dwVersion);</P>
<P align=justify>AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, </P>
<P align=justify>DWORD dwVersion,BOOL bSystem);</P>
<P align=justify>#else</P>
<P align=justify>AFX_MODULE_STATE(BOOL bDLL);</P>
<P align=justify>#endif</P>
<P align=justify>~AFX_MODULE_STATE();</P>
<P align=justify></P>
<P align=justify>CWinApp* m_pCurrentWinApp;</P>
<P align=justify>HINSTANCE m_hCurrentInstanceHandle;</P>
<P align=justify>HINSTANCE m_hCurrentResourceHandle;</P>
<P align=justify>LPCTSTR m_lpszCurrentAppName;</P>
<P align=justify>BYTE m_bDLL;// TRUE if module is a DLL, FALSE if it is an EXE</P>
<P align=justify>//TRUE if module is a "system" module, FALSE if not</P>
<P align=justify>BYTE m_bSystem;</P>
<P align=justify>BYTE m_bReserved[2]; // padding</P>
<P align=justify></P>
<P align=justify>//Runtime class data:</P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>CRuntimeClass* m_pClassInit;</P>
<P align=justify>#endif</P>
<P align=justify>CTypedSimpleList&lt;CRuntimeClass*&gt; m_classList;</P>
<P align=justify></P>
<P align=justify>// OLE object factories</P>
<P align=justify>#ifndef _AFX_NO_OLE_SUPPORT</P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>COleObjectFactory* m_pFactoryInit;</P>
<P align=justify>#endif</P>
<P align=justify>CTypedSimpleList&lt;COleObjectFactory*&gt; m_factoryList;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>// number of locked OLE objects</P>
<P align=justify>long m_nObjectCount;</P>
<P align=justify>BOOL m_bUserCtrl;</P>
<P align=justify></P>
<P align=justify>// AfxRegisterClass and AfxRegisterWndClass data</P>
<P align=justify>TCHAR m_szUnregisterList[4096];</P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>WNDPROC m_pfnAfxWndProc;</P>
<P align=justify>DWORD m_dwVersion; // version that module linked against</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>// variables related to a given process in a module</P>
<P align=justify>// (used to be AFX_MODULE_PROCESS_STATE)</P>
<P align=justify>#ifdef _AFX_OLD_EXCEPTIONS</P>
<P align=justify>// exceptions</P>
<P align=justify>AFX_TERM_PROC m_pfnTerminate;</P>
<P align=justify>#endif</P>
<P align=justify>void (PASCAL *m_pfnFilterToolTipMessage)(MSG*, CWnd*);</P>
<P align=justify></P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>// CDynLinkLibrary objects (for resource chain)</P>
<P align=justify>CTypedSimpleList&lt;CDynLinkLibrary*&gt; m_libraryList;</P>
<P align=justify></P>
<P align=justify>// special case for MFCxxLOC.DLL (localized MFC resources)</P>
<P align=justify>HINSTANCE m_appLangDLL;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>#ifndef _AFX_NO_OCC_SUPPORT</P>
<P align=justify>// OLE control container manager</P>
<P align=justify>COccManager* m_pOccManager;</P>
<P align=justify>// locked OLE controls</P>
<P align=justify>CTypedSimpleList&lt;COleControlLock*&gt; m_lockList;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>#ifndef _AFX_NO_DAO_SUPPORT</P>
<P align=justify>_AFX_DAO_STATE* m_pDaoState;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>#ifndef _AFX_NO_OLE_SUPPORT</P>
<P align=justify>// Type library caches</P>
<P align=justify>CTypeLibCache m_typeLibCache;</P>
<P align=justify>CMapPtrToPtr* m_pTypeLibCacheMap;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>// define thread local portions of module state</P>
<P align=justify>THREAD_LOCAL(AFX_MODULE_THREAD_STATE, m_thread)</P>
<P align=justify>};</P>
<P align=justify>从上面的定义可以看出,模块状态信息分为如下几类:</P>
<P align=justify>模块信息,资源信息,对动态链接到MFC DLL的支持信息,对扩展DLL的支持信息,对DAO的支持信息,对OLE的支持信息,模块-线程状态信息。</P>
<P align=justify>模块信息包括实例句柄、资源句柄、应用程序名称、指向应用程序的指针、是否为DLL模块、模块注册的窗口类,等等。其中,成员变量m_fRegisteredClasses、m_szUnregisterList曾经在讨论MFC的窗口注册时提到过它们的用处。</P>
<P align=justify>在“#ifdef _AFXDLL…#endif”条件编译范围内的是支持MFC DLL的数据;</P>
<P align=justify>在“#ifndef _AFX_NO_OLE_SUPPOR…#endif”条件编译范围内的是支持OLE的数据;</P>
<P align=justify>在“#ifndef _AFX_NO_OCC_SUPPOR…#endif”条件编译范围内的是支持OLE控件的数据;</P>
<P align=justify>在“#ifndef _AFX_NO_DAO_SUPPORT”条件编译范围内的是支持DAO的数据。</P>
<P align=justify>THREAD_LOCAL宏定义了线程私有的模块-线程类型的变量m_thread。</P>
<P align=justify></P>
<LI><B>_AFX_BASE_MODULE_STATE</B>
<p>
<P align=justify>该类定义如下:</P>
<P align=justify>class _AFX_BASE_MODULE_STATE : public AFX_MODULE_STATE</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>#ifdef _AFXDLL</P>
<P align=justify>_AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE,</P>
<P align=justify>AfxWndProcBase, _MFC_VER)</P>
<P align=justify>#else</P>
<P align=justify>_AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE)</P>
<P align=justify>#endif</P>
<P align=justify>{ }</P>
<P align=justify>};</P>
<P align=justify>由定义可见,该类没有在_AFX_MODULE_STATE类的基础上增加数据。它类用来实现一个MFC应用程序模块的状态信息。</P>
<P align=justify></P>
<LI><B>_AFX_THREAD_STATE</B>
<p>
<P align=justify>该类定义如下:</P>
<P align=justify>class _AFX_THREAD_STATE : public CNoTrackObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>_AFX_THREAD_STATE();</P>
<P align=justify>virtual ~_AFX_THREAD_STATE();</P>
<P align=justify></P>
<P align=justify>// override for m_pModuleState in _AFX_APP_STATE</P>
<P align=justify>AFX_MODULE_STATE* m_pModuleState;</P>
<P align=justify>AFX_MODULE_STATE* m_pPrevModuleState;</P>
<P align=justify></P>
<P align=justify>// memory safety pool for temp maps</P>
<P align=justify>void* m_pSafetyPoolBuffer; // current buffer</P>
<P align=justify></P>
<P align=justify>// thread local exception context</P>
<P align=justify>AFX_EXCEPTION_CONTEXT m_exceptionContext;</P>
<P align=justify></P>
<P align=justify>// CWnd create, gray dialog hook, and other hook data</P>
<P align=justify>CWnd* m_pWndInit;</P>
<P align=justify>CWnd* m_pAlternateWndInit; // special case commdlg hooking</P>
<P align=justify>DWORD m_dwPropStyle;</P>
<P align=justify>DWORD m_dwPropExStyle;</P>
<P align=justify>HWND m_hWndInit;</P>
<P align=justify>BOOL m_bDlgCreate;</P>
<P align=justify>HHOOK m_hHookOldCbtFilter;</P>
<P align=justify>HHOOK m_hHookOldMsgFilter;</P>
<P align=justify></P>
<P align=justify>// other CWnd modal data</P>
<P align=justify>MSG m_lastSentMsg; // see CWnd::WindowProc</P>
<P align=justify>HWND m_hTrackingWindow; // see CWnd::TrackPopupMenu</P>
<P align=justify>HMENU m_hTrackingMenu;</P>
<P align=justify>TCHAR m_szTempClassName[96]; // see AfxRegisterWndClass</P>
<P align=justify>HWND m_hLockoutNotifyWindow; // see CWnd::OnCommand</P>
<P align=justify>BOOL m_bInMsgFilter;</P>
<P align=justify></P>
<P align=justify>// other framework modal data</P>
<P align=justify>CView* m_pRoutingView; // see CCmdTarget::GetRoutingView</P>
<P align=justify>CFrameWnd*m_pRoutingFrame;//see CmdTarget::GetRoutingFrame</P>
<P align=justify></P>
<P align=justify>// MFC/DB thread-local data</P>
<P align=justify>BOOL m_bWaitForDataSource;</P>
<P align=justify></P>
<P align=justify>// common controls thread state</P>
<P align=justify>CToolTipCtrl* m_pToolTip;</P>
<P align=justify>CWnd* m_pLastHit; // last window to own tooltip</P>
<P align=justify>int m_nLastHit; // last hittest code</P>
<P align=justify>TOOLINFO m_lastInfo; // last TOOLINFO structure</P>
<P align=justify>int m_nLastStatus; // last flyby status message</P>
<P align=justify>CControlBar* m_pLastStatus; // last flyby status control bar</P>
<P align=justify></P>
<P align=justify>// OLE control thread-local data</P>
<P align=justify>CWnd* m_pWndPark; // "parking space" window</P>
<P align=justify>long m_nCtrlRef; // reference count on parking window</P>
<P align=justify>BOOL m_bNeedTerm; // TRUE if OleUninitialize needs to be called</P>
<P align=justify>};</P>
<P align=justify>从定义可以看出,线程状态的成员数据分如下几类:</P>
<P align=justify>指向模块状态信息的指针,支持本线程的窗口创建的变量,MFC命令和消息处理用到的信息,处理工具条提示信息(tooltip)的结构,和处理OLE相关的变量,等等。</P>
<P align=justify></P></LI></OL></LI></OL>
 楼主| 发表于 2006-12-8 10:23:39 | 显示全部楼层
<OL>
<LI>
<><B>AFX_MODULE_THREAD_STATE</B> </P>
<p></LI>
<LI>
<DIV align=justify>该类定义如下:</DIV></LI>
<LI>
<DIV align=justify>// AFX_MODULE_THREAD_STATE (local to thread *and* module)</DIV></LI>
<LI>
<DIV align=justify>class AFX_MODULE_THREAD_STATE : public CNoTrackObject</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>public:</DIV></LI>
<LI>
<DIV align=justify>AFX_MODULE_THREAD_STATE();</DIV></LI>
<LI>
<DIV align=justify>virtual ~AFX_MODULE_THREAD_STATE();</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>// current CWinThread pointer</DIV></LI>
<LI>
<DIV align=justify>CWinThread* m_pCurrentWinThread;</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>// list of CFrameWnd objects for thread</DIV></LI>
<LI>
<DIV align=justify>CTypedSimpleList&lt;CFrameWnd*&gt; m_frameList;</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>// temporary/permanent map state</DIV></LI>
<LI>
<DIV align=justify>DWORD m_nTempMapLock; // if not 0, temp maps locked</DIV></LI>
<LI>
<DIV align=justify>CHandleMap* m_pmapHWND;</DIV></LI>
<LI>
<DIV align=justify>CHandleMap* m_pmapHMENU;</DIV></LI>
<LI>
<DIV align=justify>CHandleMap* m_pmapHDC;</DIV></LI>
<LI>
<DIV align=justify>CHandleMap* m_pmapHGDIOBJ;</DIV></LI>
<LI>
<DIV align=justify>CHandleMap* m_pmapHimageLIST;</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>// thread-local MFC new handler (separate from C-runtime)</DIV></LI>
<LI>
<DIV align=justify>_PNH m_pfnNewHandler;</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>#ifndef _AFX_NO_SOCKET_SUPPORT</DIV></LI>
<LI>
<DIV align=justify>// WinSock specific thread state</DIV></LI>
<LI>
<DIV align=justify>HWND m_hSocketWindow;</DIV></LI>
<LI>
<DIV align=justify>CMapPtrToPtr m_mapSocketHandle;</DIV></LI>
<LI>
<DIV align=justify>CMapPtrToPtr m_mapDeadSockets;</DIV></LI>
<LI>
<DIV align=justify>CPtrList m_listSocketNotifications;</DIV></LI>
<LI>
<DIV align=justify>#endif</DIV></LI>
<LI>
<DIV align=justify>};</DIV></LI>
<LI>
<DIV align=justify>模块-线程状态的数据成员主要有:</DIV></LI>
<LI>
<DIV align=justify>指向当前线程对象(CWinThread对象)的指针m_pCurrentWinThread;</DIV></LI>
<LI>
<DIV align=justify>当前线程的框架窗口对象(CFrameWnd对象)列表m_frameList(边框窗口在创建时(见图5-8)把自身添加到m-frameList中,销毁时则删除掉,通过列表m_frameList可以遍历模块所有的边框窗口);</DIV></LI>
<LI>
<DIV align=justify>new操作的例外处理函数m_pfnNewHandler;</DIV></LI>
<LI>
<DIV align=justify>临时映射锁定标识m_nTempMapLock,防止并发修改临时映射。</DIV></LI>
<LI>
<DIV align=justify>系列Windows对象-MFC对象的映射,如m_pmapHWND等。</DIV></LI>
<LI>
<DIV align=justify>这些数据成员都是线程和模块私有的。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>下一节讨论MFC如何通过上述这些类来实现其状态的管理。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>线程局部存储机制和状态的实现</B> </LI>
<LI></LI>
<LI>
<DIV align=justify>MFC实现线程、模块或者线程-模块私有状态的基础是MFC的线程局部存储机制。MFC定义了CThreadSlotData类型的全局变量_afxThreadData来为进程的线程分配线程局部存储空间:</DIV></LI>
<LI>
<DIV align=justify>CThreadSlotData* _afxThreadData;</DIV></LI>
<LI>
<DIV align=justify>在此基础上,MFC定义了变量_afxThreadState来管理线程状态,定义了变量_afxBaseModuleState来管理进程状态。</DIV></LI>
<LI>
<DIV align=justify>THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)</DIV></LI>
<LI>
<DIV align=justify>ROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)</DIV></LI>
<LI>
<DIV align=justify>对于每个THREAD_LOCAL宏定义的变量,进程的每个线程都有自己独立的拷贝,这个变量在不同的线程里头可以有不同的取值。</DIV></LI>
<LI>
<DIV align=justify>对于每个PROCESS_LOCAL宏定义的变量,每个进程都有自己独立的拷贝,这个变量在不同的进程里头可以有不同的取值。</DIV></LI>
<LI>
<DIV align=justify>分别解释这三个变量。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>CThreadSlotData和_afxThreadData</B>
<p>
<OL>
< align=justify>
<LI><B>CThreadSlotData的定义</B>
<p>
< align=justify>以Win32线程局部存储机制为基础,MFC设计了类CThreadSlotData来提供管理线程局部存储的功能,MFC应用程序使用该类的对象──全局变量_afxThreadData来管理本进程的线程局部存储。CThreadSlotData类的定义如下:</P>
< align=justify>class CThreadSlotData</P>
< align=justify>{</P>
< align=justify>public:</P>
< align=justify>CThreadSlotData();</P>
< align=justify></P>
< align=justify>//Operations</P>
<P align=justify>int AllocSlot();</P>
<P align=justify>void FreeSlot(int nSlot);</P>
<P align=justify>void* GetValue(int nSlot);</P>
<P align=justify>void SetValue(int nSlot, void* pValue);</P>
<P align=justify>// delete all values in process/thread</P>
<P align=justify>void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);</P>
<P align=justify>// assign instance handle to just constructed slots</P>
<P align=justify>void AssignInstance(HINSTANCE hInst);</P>
<P align=justify></P>
<P align=justify>// Implementation</P>
<P align=justify>DWORD m_tlsIndex;// used to access system thread-local storage</P>
<P align=justify>int m_nAlloc; // number of slots allocated (in UINTs)</P>
<P align=justify>int m_nRover; // (optimization) for quick finding of free slots</P>
<P align=justify>int m_nMax; // size of slot table below (in bits)</P>
<P align=justify>CSlotData* m_pSlotData; // state of each slot (allocated or not)</P>
<P align=justify>//list of CThreadData structures</P>
<P align=justify>CTypedSimpleList&lt;CThreadData*&gt; m_list; </P>
<P align=justify>CRITICAL_SECTION m_sect;</P>
<P align=justify>// special version for threads only!</P>
<P align=justify>void* GetThreadValue(int nSlot);</P>
<P align=justify>void* PASCAL operator new(size_t, void* p){ return p; }</P>
<P align=justify>void DeleteValues(CThreadData* pData, HINSTANCE hInst);</P>
<P align=justify>~CThreadSlotData();</P>
<P align=justify>};</P>
<P align=justify>通过TLS索引m_tlsIndex,CThreadSlotData对象(_afxThreadData)为每一个线程分配一个线程私有的存储空间并管理该空间。它把这个空间划分为若干个槽,每个槽放一个线程私有的数据指针,这样每个线程就可以存放任意个线程私有的数据指针。</P>
<P align=justify></P>
<LI><B>CThreadSlotData的一些数据成员</B>
<p>
<P align=justify>在CThreadSlotData类的定义中所涉及的类或者结构定义如下:</P>
<P align=justify>(1)m_sect</P>
<P align=justify>m_sect是一个关键段变量,在_afxThreadData创建时初始化。因为_afxThreadData是一个全局变量,所以必须通过m_sect来同步多个线程对该变量的并发访问。</P>
<P align=justify>(2)m_nAlloc和m_pSlotData</P>
<P align=justify>m_nAlloc表示已经分配槽的数目,它代表了线程局部变量的个数。每一个线程局部变量都对应一个槽,每个槽对应一个线程局部变量。槽使用CSlotData类来管理。</P>
<P align=justify>CSlotData的定义如下:</P>
<P align=justify>struct CSlotData{</P>
<P align=justify>DWORD dwFlags; // slot flags (allocated/not allocated)</P>
<P align=justify>HINSTANCE hInst; // module which owns this slot</P>
<P align=justify>};</P>
<P align=justify>该结构用来描述槽的使用:</P>
<P align=justify>域dwFlags表示槽的状态,即被占用或者没有;</P>
<P align=justify>域hInst表示使用该槽的模块的句柄。</P>
<P align=justify>m_pSlotData表示一个CSlotData类型的数组,用来描述各个槽。该数组通过成员函数AllocSlot和FreeSlot来动态地管理,见图9-6。</P>
<P align=justify>(3)m_list</P>
<P align=justify>先讨论CThreadData 类。CThreadData定义如下:</P>
<P align=justify>struct CThreadData : public CNoTrackObject{</P>
<P align=justify>CThreadData* pNext; // required to be member of CSimpleList</P>
<P align=justify>int nCount; // current size of pData</P>
<P align=justify>LPVOID* pData; // actual thread local data (indexed by nSlot)</P>
<P align=justify>};</P>
<P align=justify>该结构用来描述CThreadSlotData为每个线程管理的线程局部空间:</P>
<P align=justify>域pNext把各个线程的CThreadData项目链接成一个表,即把各个线程的线程私有空间链接起来;</P>
<P align=justify>域nCount表示域pData的尺寸,即存储了多少个线程私有数据;</P>
<P align=justify>pData表示一个LPVOID类型的数组,数组中的每一个元素保存一个指针,即线程私有数据指针,该指针指向一个在堆中分配的真正存储线程私有数据的地址。数组元素的个数和槽的个数相同,每个线程局部变量(THREAD_LOCAL定义的变量)都有一个对应的槽号,用该槽号作为下标来引用pData。</P>
<P align=justify>m_list表示一个CThreadData类型的指针数组,数组中的各项指向各个线程的线程私有空间,每个线程在数组中都有一个对应项。该数组通过GetValue、SetValue、DeleteValues等成员函数来管理,见图9-6。</P>
<P align=justify></P>
<LI><B>_afxThreadData</B>
<p></LI></OL></LI></OL><B><IMG src="http://www.vczx.com/tutorial/mfc/image147.gif" align=left> </B>
<P align=justify>_afxThreadData仅仅定义为一个CThreadSlotData类型的指针,所指对象在第一次被引用时创建,在此之前该指针为空。下文_afxThreadData含义是它所指的对象。图9-5、9-6图解了MFC的线程局部存储机制的实现。</P>
<P align=justify>图9-5表示_afxTheadData使用TLS技术负责给进程分配一个TLS索引,然后使用TLS索引为进程的每一个线程分配线程局部存储空间。</P>
<P align=justify>图9-6表示每个线程的的局部存储空间可以分多个槽,每个槽可以放一个线程私有的数据指针。_afxThreadData负责给线程局部变量分配槽号并根据槽号存取数据。图的左半部分描述了管理槽的m_pSlotData及类CSlotData的结构,右半部分描述了管理MFC线程私有空间的m_list及类CThreadData的结构。</P>
<P align=justify>结合图9-6,对MFC线程局部存储机制总结如下:</P>
<UL>
<P align=justify>
<LI>每个线程局部变量(宏THREAD_LOCAL定义)占用一个槽,并有一个槽号。。
<p>
<P align=justify></P>
<LI>每个线程都有自己的MFC局部存储空间(下文多次使用“线程的MFC局部存储空间”,表示和此处相同的概念)。
<p>
<P align=justify></P>
<LI>通过TLS索引得到的是一个指针P1,它指向线程的MFC局部存储空间。
<p>
<P align=justify></P>
<LI>通过指针P1和线程局部变量在空间所占用的槽号,得到该槽所存储的线程私有的数据指针,即真正的线程私有数据的地址P2;
<p>
<P align=justify></P>
<LI>从地址P2得到数据D。
<p></LI></UL>
<P align=justify>这个过程相当于几重间接寻址:先得到TLS线程私有数据指针,从TLS线程私有数据指针得到线程的MFC线程局部存储空间,再从MFC局部存储空间的对应槽得到一个线程私有的数据指针,从该指针得到最终的线程私有数据。如果没有这种机制,使用Win32 TLS只要一次间接寻址:得到TLS线程私有数据指针,从该指针得到最终的线程私有数据。</P><IMG src="http://www.vczx.com/tutorial/mfc/image148.gif" align=left>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>线程状态_afxThreadState</B>
<p>
<P align=justify>从上一节知道了MFC的线程局部存储机制。但有一点还不清楚,即某个线程局部变量所占用的槽号是怎么保存的呢?关于这点可从线程局部的线程状态变量_afxThreadState的实现来分析MFC的作法。变量_afxThreadState的定义如下:</P>
<P align=justify>THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)</P>
<P align=justify></P>
<P align=justify>THREAD_LOCAL 是一个宏,THREAD_LOCAL(class_name, ident_name)宏展开后如下:</P>
<P align=justify>AFX_DATADEF CThreadLocal&lt;class_name&gt; ident_name;</P>
<P align=justify>这里,CThreadLocal是一个类模板,从CThreadLocalObject类继承。</P>
<P align=justify>CThreadLocalObject和CThreadLocal的定义如下:</P>
<P align=justify>class CThreadLocalObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>// Attributes</P>
<P align=justify>CNoTrackObject* GetData(CNoTrackObject* (AFXAPI* </P>
<P align=justify>pfnCreateObject)());</P>
<P align=justify>CNoTrackObject* GetDataNA();</P>
<P align=justify></P>
<P align=justify>// Implementation</P>
<P align=justify>int m_nSlot;</P>
<P align=justify>~CThreadLocalObject();</P>
<P align=justify>};</P>
<P align=justify>CThreadLocalObject用来帮助实现一个线程局部的变量。成员变量m_nSlot表示线程局部变量在MFC线程局部存储空间中占据的槽号。GetDataNA用来返回变量的值。GetData也可以返回变量的值,但是如果发现还没有给该变量分配槽号(m_slot=0),则给它分配槽号并在线程的MFC局部空间为之分配一个槽;如果在槽m_nSlot还没有数据(为空),则调用参数pfnCreateObject传递的函数创建一个数据项,并保存到槽m_nSlot中。</P>
<P align=justify></P>
<P align=justify>template&lt;class TYPE&gt;</P>
<P align=justify>class CThreadLocal : public CThreadLocalObject</P>
<P align=justify>{</P>
<P align=justify>// Attributes</P>
<P align=justify>public:</P>
<P align=justify>inline TYPE* GetData()</P>
<P align=justify>{</P>
<P align=justify>TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&amp;CreateObject);</P>
<P align=justify>ASSERT(pData != NULL);</P>
<P align=justify>return pData;</P>
<P align=justify>}</P>
<P align=justify>inline TYPE* GetDataNA()</P>
<P align=justify>{</P>
<P align=justify>TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA();</P>
<P align=justify>return pData;</P>
<P align=justify>}</P>
<P align=justify>inline operator TYPE*()</P>
<P align=justify>{ return GetData(); }</P>
<P align=justify>inline TYPE* operator-&gt;()</P>
<P align=justify>{ return GetData(); }</P>
<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>CThreadLocal模板用来声明任意类型的线程私有的变量,因为通过模板可以自动的正确的转化(cast)指针类型。程序员可以使用它来实现自己的线程局部变量,正如MFC实现线程局部的线程状态变量和模块-线程变量一样。</P>
<P align=justify>CThrealLocal的成员函数CreateObject用来创建动态的指定类型的对象。成员函数GetData调用了基类CThreadLocalObject的同名函数,并且把CreateObject函数的地址作为参数传递给它。</P>
<P align=justify>另外,CThreadLocal模板重载了操作符号“*”、“-&gt;”,这样编译器将自动地进行有关类型转换,例如:</P>
<P align=justify>_AFX_THREAD_STATE *pStata = _afxThreadState</P>
<P align=justify>是可以被编译器接收的。</P>
<P align=justify></P>
<P align=justify>现在回头来看_afxThreadState的定义:</P>
<P align=justify>从以上分析可以知道,THREAD_LOCAL(class_name, ident_name)定义的结果并没有产生一个名为ident_name的class_name类的实例,而是产生一个CThreadLocal模板类(确切地说,是其派生类)的实例,m_nSlot初始化为0。所以,_afxThreadState实质上是一个CThreadLocal模板类的全局变量。每一个线程局部变量都对应了一个全局的CThreadLoacl模板类对象,模板对象的m_nSlot记录了线程局部变量对象的槽号。</P></LI></OL></OL></OL>
 楼主| 发表于 2006-12-8 10:24:42 | 显示全部楼层
<OL>
<LI>
<><B>进程模块状态afxBaseModuleState</B> </P>
<>
< align=justify>进程模块状态定义如下:</P>
< align=justify>ROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)</P>
< align=justify>表示它是一个_AFX_BASE_MODULE_STATE类型的进程局部(process local)的变量。</P>
< align=justify>进程局部变量的实现方法主要是为了用于Win32s下。在Win32s下,一个DLL模块如果被多个应用程序调用,它将让这些程序共享它的全局数据。为了DLL的全局数据一个进程有一份独立的拷贝,MFC设计了进程私有的实现方法,实际上就是在进程的堆(Heap)中分配全局数据的内存空间。</P>
< align=justify>在Win32下,DLL模块的数据和代码被映射到调用进程的虚拟空间,也就是说,DLL定义的全局变量是进程私有的;所以进程局部变量的实现并不为Win32所关心。但是,不是说afxBaseModuleState不重要,仅仅是采用PROCESS_LOCAL技术声明它是进程局部变量不是很必要了。PROCESS_LOCAL(class_name, ident_name)宏展开后如下:</P>
< align=justify>AFX_DATADEF CProcessLocal&lt;class_name&gt; ident_name;</P>
< align=justify>这里,CProcessLocal是一个类模板,从CProcessLocalObject类继承。</P>
<P align=justify>CProcessLocalObject和CProcessLocal的定义如下:</P>
<P align=justify>class CProcessLocalObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>// Attributes</P>
<P align=justify>CNoTrackObject* GetData(CNoTrackObject* (AFXAPI* </P>
<P align=justify>pfnCreateObject)());</P>
<P align=justify></P>
<P align=justify>// Implementation</P>
<P align=justify>CNoTrackObject* volatile m_pObject;</P>
<P align=justify>~CProcessLocalObject();</P>
<P align=justify>};</P>
<P align=justify></P>
<P align=justify>template&lt;class TYPE&gt;</P>
<P align=justify>class CProcessLocal : public CProcessLocalObject</P>
<P align=justify>{</P>
<P align=justify>// Attributes</P>
<P align=justify>public:</P>
<P align=justify>inline TYPE* GetData()</P>
<P align=justify>{</P>
<P align=justify>TYPE* pData =(TYPE*)CProcessLocalObject::GetData(&amp;CreateObject);</P>
<P align=justify>ASSERT(pData != NULL);</P>
<P align=justify>return pData;</P>
<P align=justify>}</P>
<P align=justify>inline TYPE* GetDataNA()</P>
<P align=justify>{ return (TYPE*)m_pObject; }</P>
<P align=justify>inline operator TYPE*()</P>
<P align=justify>{ return GetData(); }</P>
<P align=justify>inline TYPE* operator-&gt;()</P>
<P align=justify>{ return GetData(); }</P>
<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><B>状态对象的创建</B>
<LI>
<LI>
<DIV align=justify></DIV>
<LI><B>状态对象的创建过程</B>
<p></LI></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 src="http://www.vczx.com/tutorial/mfc/image149.gif" 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></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></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></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><B>创建过程所涉及的几个重要函数的算法</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>创建过程所涉及的几个重要函数的算法描述如下:</P>
<OL>
<P align=justify>
<LI>AllocSlot
<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 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></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><B>管理状态</B>
<P>
<P align=justify>在描述了MFC状态的实现机制之后,现在来讨论MFC的状态管理和相关状态的作用。</P>
<OL>
<P align=justify>
<LI><B>模块状态切换</B>
<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></LI></UL>
<P align=justify>AFX_MANAGE_SATATE(AfxGetStaticModuleState());</P>
<P align=justify>其中,AfxGetStaticModuleState返回DLL的模块状态afxModuleState。</P>
<UL>
<P align=justify>
<LI>窗口函数使用
<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>
<P align=justify>
<LI>一个进程(使用MFC的应用程序)的主线程创建线程模块状态和进程模块状态,前者是_AFX_THREAD_STATE类的实例,后者是_AFX_BASE_MODULE_STATE类的实例。
<P>
<P align=justify></P>
<LI>当进程的新的线程被创建时,它创建自己的线程状态,继承父线程的模块状态。在线程的入口函数_AfxThreadEntry完成这样的处理,该函数的描述见8.5.3节。 </LI></UL>
 楼主| 发表于 2006-12-8 10:25:20 | 显示全部楼层
<OL>
<LI><B>扩展DLL的模块状态</B> </LI>
<LI></LI>
<LI>
<DIV align=justify>7.3.1节指出扩展DLL的实现必须遵循五条规则,为此,首先在扩展DLL实现文件里头,定义AFX_EXTENSION_MODULE类型的静态扩展模块变量,然后在DllMain入口函数里头使用AfxInitExtension初始化扩展模块变量,并且实现和输出一个初始化函数供扩展DLL的使用者调用。</DIV></LI>
<LI>
<DIV align=justify>使用者必须具备一个CWinApp对象,通常在它的InitInstance函数中调用扩展DLL提供的初始化函数。</DIV></LI>
<LI>
<DIV align=justify>一般用以下的几段代码完成上述任务。首先是扩展模块变量的定义和初始化:</DIV></LI>
<LI>
<DIV align=justify>static AFX_EXTENSION_MODULE extensionDLL;</DIV></LI>
<LI>
<DIV align=justify>DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>if (dwReason == DLL_PROCESS_ATTACH)</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>// Extension DLL one-time initialization</DIV></LI>
<LI>
<DIV align=justify>if (!AfxInitExtensionModule(extensionDLL,hInstance))</DIV></LI>
<LI>
<DIV align=justify>return 0;</DIV></LI>
<LI>
<DIV align=justify>……</DIV></LI>
<LI>
<DIV align=justify>}</DIV></LI>
<LI>
<DIV align=justify>}</DIV></LI>
<LI>
<DIV align=justify>然后是扩展DLL的初始化函数,假定初始化函数命名为InitMyDll,InitMyDll被定义为“C”链接的全局函数,并且被输出。</DIV></LI>
<LI>
<DIV align=justify>// wire up this DLL into the resource chain</DIV></LI>
<LI>
<DIV align=justify>extern “C” void WINAPI InitMyDll()</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>CDynLinkLibrary* pDLL = new</DIV></LI>
<LI>
<DIV align=justify>CDynLinkLibrary(extensionDLL, TRUE);</DIV></LI>
<LI>
<DIV align=justify>ASSERT(pDLL != NULL);</DIV></LI>
<LI>
<DIV align=justify>...</DIV></LI>
<LI>
<DIV align=justify>}</DIV></LI>
<LI>
<DIV align=justify>最后是调用者的处理,假定在应用程序对象的InitInstance函数中调用初始化函数:</DIV></LI>
<LI>
<DIV align=justify>BOOL CMyApp::InitInstance()</DIV></LI>
<LI>
<DIV align=justify>{</DIV></LI>
<LI>
<DIV align=justify>InitMyMyDll();</DIV></LI>
<LI>
<DIV align=justify>…</DIV></LI>
<LI>
<DIV align=justify>}</DIV></LI>
<LI>
<DIV align=justify>上述这些代码只有在动态链接到MFC DLL时才有用。下面,对这些代码进行分析和解释</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI></OL>
<><B>_AFX_EXTENSION_MODULE</B> </P>
<>
< align=justify>在分析代码之前,先讨论描述扩展模块状态的_AFX_EXTENSION_MODULE类。_AFX_EXTENSION_MODULE没有基类,其定义如下:</P>
< align=justify>struct AFX_EXTENSION_MODULE</P>
< align=justify>{</P>
< align=justify>BOOL bInitialized;</P>
< align=justify>HMODULE hModule;</P>
< align=justify>HMODULE hResource;</P>
< align=justify>CRuntimeClass* pFirstSharedClass;</P>
< align=justify>COleObjectFactory* pFirstSharedFactory;</P>
<P align=justify>};</P>
<P align=justify>其中:</P>
<P align=justify>第一个域表示该结构变量是否已经被初始化了;</P>
<P align=justify>第二个域用来保存扩展DLL的模块句柄;</P>
<P align=justify>第三个域用来保存扩展DLL的资源句柄;</P>
<P align=justify>第四个域用来保存扩展DLL要输出的CRuntimeClass类;</P>
<P align=justify>第五个域用来保存扩展DLL的OLE Factory。</P>
<P align=justify>该结构用来描述一个扩展DLL的模块状态信息,每一个扩展DLL都要定义一个该类型的静态变量,例如extensionDLL。</P>
<P align=justify></P>
<P align=justify>在DllMain中,调用AfxInitExtensionModule函数来初始化本DLL的静态变量该变量(扩展模块状态),如extensionDLL。函数AfxInitExtensionModule原型如下:</P>
<P align=justify>BOOL AFXAPI AfxInitExtensionModule(</P>
<P align=justify>AFX_EXTENSION_MODULE&amp; state, HMODULE hModule)</P>
<P align=justify>其中:</P>
<P align=justify>参数1是DllMain传递给它的扩展DLL的模块状态,如extensionDLL;</P>
<P align=justify>参数2是DllMain传递给它的模块句柄。</P>
<P align=justify>AfxInitExtensionModule函数主要作以下事情:</P>
<P align=justify>(1)把扩展DLL模块的模块句柄hModule、资源句柄hModule分别保存到参数state的成员变量hModule、hResource中;</P>
<P align=justify>(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改变前的状态。</P>
<P align=justify>关于CRuntimeclass信息和AFX_CLASSINIT对象的构造,在3.3.1节曾经讨论过。一个扩展DLL在初始化时,如果需要输出它的CRuntimeClass对象,就可以使用相应的CRuntimeClass对象定义一个静态的AFX_CLASSINIT对象,而不一定要使用IMPLEMENT_SERIAL宏。当然,可以序列化的类必定导致可以输出的CRuntimeClass对象。</P>
<P align=justify>(3)若支持OLE的话,把当前模块状态的m_factoryList的头保存到state的成员变量pFirstSharedFactory中。m_factoryList的头设置为模块状态的m_m_pFactoryInit。</P>
<P align=justify>(4)这样,经过初始化之后,扩展DLL模块包含了扩展DLL的模块句柄、资源句柄、本模块初始化的CRuntimeClass类等等。</P>
<P align=justify>扩展DLL的初始化函数将使用扩展模块状态信息。下面,讨论初始化函数的作用。</P>
<LI><B>扩展DLL的初始化函数</B>
<p></LI>
<P align=justify>在初始化函数InitMyDll中,创建了一个动态的CDynLinkLibrary对象,并把对象指针保存在pDLL中。CDynLinkLibrary类从CCmdTarget派生,定义如下:</P>
<P align=justify>class CDynLinkLibrary : public CCmdTarget</P>
<P align=justify>{</P>
<P align=justify>DECLARE_DYNAMIC(CDynLinkLibrary)</P>
<P align=justify>public:</P>
<P align=justify>// Constructor</P>
<P align=justify>CDynLinkLibrary(AFX_EXTENSION_MODULE&amp; state,</P>
<P align=justify>BOOL bSystem = FALSE);</P>
<P align=justify></P>
<P align=justify>// Attributes</P>
<P align=justify>HMODULE m_hModule;</P>
<P align=justify>HMODULE m_hResource; // for shared resources</P>
<P align=justify>CTypedSimpleList&lt;CRuntimeClass*&gt; m_classList;</P>
<P align=justify>#ifndef _AFX_NO_OLE_SUPPORT</P>
<P align=justify>CTypedSimpleList&lt;COleObjectFactory*&gt; m_factoryList;</P>
<P align=justify>#endif</P>
<P align=justify>BOOL m_bSystem; // TRUE only for MFC DLLs</P>
<P align=justify></P>
<P align=justify>// Implementation</P>
<P align=justify>public:</P>
<P align=justify>CDynLinkLibrary* m_pNextDLL; // simple singly linked list</P>
<P align=justify>virtual ~CDynLinkLibrary();</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>virtual void AssertValid() const;</P>
<P align=justify>virtual void Dump(CDumpContext&amp; dc) const;</P>
<P align=justify>#endif //_DEBUG</P>
<P align=justify>};</P>
<P align=justify>CDynLinkLibrary的结构和AFX_EXTENSION_MODULE有一定的相似性,存在对应关系。</P>
<P align=justify>CDynLinkLibrary构造函数的第一个参数就是经过AfxInitExtensionModule初始化后的扩展DLL的模块状态,如extensionDLL,第二个参数表示该DLL模块是否是系统模块。</P>
<P align=justify>创建CDynLinkLibrary对象导致CCmdTarget和CDynLinkLibrary类的构造函数被调用。CCmdTarget的构造函数将获取模块状态并且保存在成员变量m_pModuleState中。CDynLinkLibrary的构造函数完成以下动作:</P>
<P align=justify>构造列表m_classList和m_factoryList;</P>
<P align=justify>把参数state的域hModule、hResource复制到对应的成员变量m_hModule、m_hResource中;</P>
<P align=justify>把state的pFirstSharedClass、pFirstSharedFactory分别插入到m_classList列表、m_factoryList列表的表头;</P>
<P align=justify>把参数2的值赋值给成员变量m_bSystem中;</P>
<P align=justify>至此,CDynLinkLibrary对象已经构造完毕。之后,CDynLinkLibrary构造函数把CDynLinkLibrary对象自身添加到当前模块状态(调用扩展DLL的应用程序模块或者规则DLL模块)的CDynLinkLibrary列表m_libraryList的表头。为了防止多个线程修改模块状态的m_libraryList,访问m_libraryList时使用了同步机制。</P>
<P align=justify>这样,调用模块执行完扩展模块的初始化函数之后,就把该扩展DLL的资源、CRuntimeClass类、OLE Factory等链接到调用者的模块状态中,形成一个链表。图9-8表明了这种关系链。</P>
<P align=justify></P>
<P align=justify>综合以上分析,可以知道:</P>
<P align=justify>扩展DLL的模块仅仅在该DLL调用DllMain期间和调用初始化函数期间被使用,在这些初始化完毕之后,扩展DLL模块被链接到当前调用模块的模块状态中,因此它所包含的资源信息等也就被链接到调用扩展DLL的应用程序或者规则DLL的模块状态中了。扩展DLL扩展了调用者的资源等,这是“扩展DLL”得名的原因之一。</P>
<P align=justify>也正因为扩展DLL没有自己的模块状态(指AFX_MODULE_STATE对象,扩展DLL模块状态不是),而且必须由有模块状态的模块来使用,所以只有动态链接到MFC的应用程序或者规则DLL才可以使用扩展DLL模块的输出函数或者输出类。</P>
<P align=justify></P>
<LI><B>核心MFC DLL</B>
<p>
<P align=justify>所谓核心MFC DLL,就是MFC核心类库形成的DLL,通常说动态链接到MFC,就是指核心MFC DLL。</P>
<P align=justify>核心MFC DLL实际上也是一种扩展DLL,因为它定义了自己的扩展模块状态coreDLL,实现了自己的DllMain函数,使用AfxInitExtensionModule初始化核心DLL的扩展模块状态coreDLL,并且DllMain还创建了CDynLinkLibrary,把核心DLL的扩展模块状态coreDLL链接到当前应用程序的模块状态中。所有这些,都符合扩展DLL的处理标准。</P>
<P align=justify>但是,核心MFC DLL是一种特殊的扩展DLL,因为它定义和实现了MFC类库,模块状态、线程状态、进程状态、状态管理和使用的机制就是核心MFC DLL定义和实现的。例如核心MFC DLL定义和输出的模块状态变量,即_afxBaseModuleState,就是动态链接到MFC的DLL的应用程序的模块状态。</P>
<P align=justify>但是MFC DLL不作为独立的模块表现出来,而是把自己作为一个扩展模块来处理。当应用程序动态链接到MFC DLL时,MFC DLL把自己的扩展模块状态coreDLL链接到模块状态afxBaseModuleState,模块状态的成员变量m_hCurrentInstanceHandle指定为应用程序的句柄。当规则DLL动态链接到MFC DLL时,由规则DLL的DllMain把核心MFC DLL的扩展模块状态coreDLL链接到规则DLL的模块状态afxModuleState中,模块状态afxModuleState的m_hCurrentInstanceHandle指定为规则DLL的句柄。</P>
<P align=justify>关于afxModuleState和规则DLL的模块状态,见下一节的讨论。</P>
<P align=justify></P>
<LI><B>动态链接的规则DLL的模块状态的实现</B>
<p></LI>
<P align=justify>在本节中,动态链接到MFC DLL(定义了_AFXDLL)的规则DLL在下文简称为规则DLL。</P>
<P align=justify>(1)规则DLL的模块状态的定义</P>
<P align=justify>规则DLL有自己的模块状态_afxModuleState,它是一个静态变量,定义如下:</P>
<P align=justify>static _AFX_DLL_MODULE_STATE afxModuleState;</P>
<P align=justify>_AFX_DLL_MODULE_STATE的基类是AFX_MODULE_STATE。</P>
<P align=justify>在前面的模块状态切换中提到的AfxGetStaticModuleState函数,其定义和实现如下:</P>
<P align=justify>_AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState()</P>
<P align=justify>{</P>
<P align=justify>AFX_MODULE_STATE* pModuleState = &amp;afxModuleState;</P>
<P align=justify>return pModuleState;</P>
<P align=justify>}</P>
<P align=justify>它返回规则DLL的模块状态afxModuleState。</P>
<P align=justify>规则DLL的内部函数使用afxModuleState作为模块状态;输出函数在被调用的时候首先切换到该模块状态,然后进一步处理。</P>
<P align=justify>(2)规则DLL的模块状态的初始化</P>
<P align=justify>从用户角度来看,动态链接到MFC DLL的规则DLL不需要DllMain函数,只要提供CWinApp对象即可。其实,MFC内部是在实现扩展DLL的方法基础上来实现规则DLL的,它不仅为规则DLL提供了DllMain函数,而且规则DLL也有扩展DLL模块状态controlDLL。</P>
<P align=justify>顺便指出,和扩展DLL相比,规则DLL有一个CWinApp(或其派生类)应用程序对象和一个模块状态afxModuleState。应用程序对象是全局对象,所以在进入规则DLL的DllMain之前已经被创建,DllMain可以调用它的初始化函数InitInstance。模块状态afxModuleState是静态全局变量,也在进入DllMain之前被创建,DllMain访问模块状态时得到的就是该变量。扩展DLL是没有CWinApp对象和模块状态的,它只能使用应用程序或者规则DLL的CWinApp对象和模块状态。</P>
<P align=justify>由于核心MFC DLL的DllMain被调用的时候,访问的必定是应用程序的模块状态,要把核心DLL的扩展模块状态链接到规则DLL的模块状态中,必须通过规则DLL的DllMain来实现。</P>
<P align=justify>规则DLL的DllMain(MFC内部实现)把参数1表示的模块和资源句柄通过AfxWinInit函数保存到规则DLL的模块状态中。顺便指出,WinMain也通过AfxWinInit函数把资源和模块句柄保存到应用程序的模块状态中。</P>
<P align=justify>然后,该DllMain还创建了一个CDynLinkLibrary对象,把核心MFC DLL的扩展模块 coreDLL链接到本DLL的模块状态afxModuleState。</P>
<P align=justify>接着,DllMain得到自己的应用程序对象并调用InitInstance初始化。</P>
<P align=justify>之后,DllMain创建另一个CDynLinkLibrary对象,把本DLL的扩展模块controlDLL链接到本DLL的模块状态afxModuleState。</P>
<P align=justify>(3)使用规则DLL的应用程序可不需要CwinApp对象</P>
<P align=justify>规则DLL的资源等是由DLL内部使用的,不存在资源或者CRuntimeClass类输出的问题,这样调用规则DLL的程序不必具有模块状态,不必关心规则DLL的内部实现,不一定需要CwinApp对象,所以可以是任意Win32应用程序,</P>
<P align=justify>还有一点需要指出,DllMain也是规则DLL的入口点,在它之前,调用DllMain的RawDllMain已经切换了模块状态,RawDllMain是静态链接的,所以不必考虑状态切换。</P>
<OL>
<OL>
<P align=justify>
<LI><B>状态信息的作用</B>
<p>
<P align=justify>在分析了MFC模块状态的实现基础和管理机制之后,现在对状态信息的作用进行专门的讨论。</P>
<OL>
<OL>
<P align=justify>
<LI><B>模块信息的保存和管理</B>
<p></LI></OL>
<P align=justify>传统上,线程状态、模块状态等包含的信息是全局变量,但是为了支持Win32s、多线程、DLL等,这些变量必须是限于进程或者线程范围内有效,或者限于某个模块内有效。也就是,不再可能把它们作为全局变量处理。因此,MFC引入模块、线程、模块-线程状态等来保存和管理一些重要的信息。</P>
<P align=justify>例如:一个模块注册了一个“窗口类”之后,应用程序要保存“窗口类”的名字,以便在模块退出时取消注册的“窗口类”。因此,模块状态使用成员变量m_szUnregisterList在注册成功之后保存的“窗口类”名字。窗口注册见2.2.1节。</P>
<P align=justify>又如:Tooltip窗口是线程相关的,每个线程一个,所以线程状态用成员变量m_pToolTip来保存本线程的MFC Tooltip窗口对象。Tooltip窗口见13.2.4.4节。</P>
<P align=justify>还有,MFC对象是线程和模块相关的,所以模块线程中有一组变量用来管理本线程的MFC对象到Windows对象的映射关系。关于MFC对象和Windows对象的映射,见稍后的讨论。</P>
<P align=justify></P>
<P align=justify>模块状态、线程状态、模块线程状态的每个成员变量都有自己存在的必要和作用,这里就不一一论述了,在此,只是强调模块状态自动地实现对模块句柄和资源句柄等信息的保存和管理,这对MFC应用程序是非常重要的。</P>
<P align=justify>SDK 下的应用程序或者DLL,通常使用一个全局变量来保存模块/资源句柄。有了模块状态之后,程序员就不必这么作了。规则DLL或者应用程序的模块和资源句柄在调用DllMain或WinMain时被保存到了当前模块的模块状态中。如果是扩展DLL,则其句柄被保存到扩展模块状态中,并通过CDynLinkLibrary对象链接到主模块的模块状态。</P><IMG src="http://www.vczx.com/tutorial/mfc/image150.gif" align=left>
<P align=justify>图9-8示意了MFC模块状态对资源、CRuntimeClass对象、OLE工厂等模块信息的管理。</P>
<P align=justify>图9-8的说明:</P>
<P align=justify>左边的主模块状态表示动态链接到MFC DLL的应用程序或者规则DLL的模块状态,其资源句柄和模块句柄用来查找和获取资源,资源句柄一般是应用程序的模块句柄;CRuntimeClass对象列表和COleObjectFactory对象列表分别表示该模块初始化了的CRuntimeClass对象和该模块的OLE工厂对象;CDynLinkLibrary列表包含了它引用的系列扩展DLL的扩展模块状态(包括核心MFC DLL的状态),链表中的每一个CDynLinkLibrary对象对应一个扩展模块状态,代表了创建该对象的扩展DLL的有关资源、信息。</P>
<P align=justify>MFC查找资源、CRuntimeClass类、OLE工厂时,首先查找模块状态,然后,遍历CDynLinkLibrary表搜索相应的对象。下面两节举例说明。</P>
<P align=justify>
<LI><B>MFC资源、运行类信息的查找</B>
<p></LI></OL></LI></OL>
<LI>MFC内部使用的资源查找函数是:</LI></OL>
<P align=justify></P>
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 10:13

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表