`

读书笔记 来自网络

阅读更多

2010年3月15日 #

《深入解析MFC》笔记 12. 进程与线程

2009-10-7

=======================
《深入解析MFC》笔记 12. 进程与线程
=======================

核心对象
核心对象 产生方法
event CreateEvent
mutex CreateMutex
semaphore CreateSemaphore
file CreateFile
file-mapping CreateFileMapping
process CreateProcess
thread CreateThread

进程的生命周期: 《深入浅出MFC》P39
1、shell调用 CreateProcess 激活 App.exe
2、系统产生一个进程核心对象,计数值为1.
3、系统在此进程建立一个4GB地址空间。
4、加载器将必要的代码加载到上述地址空间中,包括App.exe的程序、数据,以及所需的动态链接函数库(DLLs)。
5、系统为此进程建立一个线程,成为主线程,现成才是CPU时间的分配对象。
6、系统调用 C runtime 函数库的 Startup code。
7、startup code 调用 App程序的 WinMain函数。
8、App开始运行。
9、使用者关闭App主窗口,是WinMain消息循环结束,WinMain结束
10、回到Startup code。
11、回到系统,系统调用 ExitProcess结束进程

产生子进程:
CreateProcess(
LPCSTR lpApplicationName, //指定可执行文件名
LPSTR lpCommandLine, //指定欲传给新进程的命令行参数
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, //指定3、4的安全属性是否需要被继承
DWORD dwCreationFlags, //标识组合
LPVOID lpEnvironment, //指定进程所使用的环境变量区,Null或父进程环境变量
LPCSTR lpCurrentDirectory, //设定子进程的工作目录与工作驱动器,若为NULL,继承父进程
LPSTARTUPINFO lpStartupInfo, //指向一个 STARTUPINFO结构的指针,
LPPROCESS_INFORMATION lpProcessInformation
);

线程的生命周期: 《深入浅出MFC》P41
调用CreateThread产生额外的线程时,系统完成以下工作
1、配置“线程对象”,其handle将成为 CreateThread 的返回值
2、设定计数器为 1.
3、配置线程 context。
4、保留线程的堆栈。
5、将 context 中的堆栈指针缓存器(SS)和指令指针缓存器(IP)设定妥当。

CreateThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性的设定及继承
DWORD dwStackSize, //堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, //设定“线程函数”的名称
LPVOID lpParameter,
DWORD dwCreationFlags, //为0,则线程立即开始执行。
LPDWORD lpThreadId //纺织线程的ID
);

unsigned long _beginthreadex ( 《深入浅出MFC》P42
void* security,
unsigned stack_size,
unsigned (__stdcall *start_address) (void *),
void *arglist,
unsigned initflag,
unsigned* thrdaddr
);

posted @ 2010-03-15 23:29 Euan 阅读(108) | 评论 (0) | 编辑 收藏

《深入解析MFC》笔记 11. MFC 实现 COM

2009-10-2

======================
《深入解析MFC》笔记 11. MFC 实现 COM
======================

COM ( Component Object Model,组件对象模型)
OLE 是微软的对象技术的名字,是一种整合软件的统一方法,使得软件可以随时间而演变,是一种整合技术。
从时间的角度讲,OLE是最早出现的,然后是COM和 ActiveX;
从体系结构角度讲,OLE和ActiveX是建立在COM之上的,所以COM是基础;
OLE的主要目标是使得应用程序被外界所认识和理解。
使用COM编程的目的是使得软件从源代码以及的重用上升到二进制一级的重用。

组件是一个可重用的模块,由一组处理过程、数据封装和用户接口组成的业务对象(Rules Object)。组件看起来像对象。它们的主要区别是:
  1)组件可以在另一个称为容器(有时也称为承载者或宿主)的应用程序中使用,也可以作为独立过程使用;
  2)组件可以由一个类构成,也可以由多个类组成,或者是一个完整的应用程序;
  3)组件为模块重用,而对象为代码重用。
  组件模型有COM(Component Object Model,对象组件模型)/DCOM(Distributed COM,分布式对象组件模型)和CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)。
  
  
=================
COM

------------------------
COM 类
COM 所做的全部工作就是在二进制一级共享软件。

C++ 类 和 COM 类
1、C++使用操作符new来实例化一个对象,而COM对象通过API的函数来创建。
2、C++使用delete来删除一个对象,COM使用引用计数来控制对象的生存期,引用计数为0,自动删除。
3、C++使用特殊的机制来控制运行时类型信息(run-time type info)和类型转化(typecasting),而COM类通过成员函数来支持
4、C++类提供成员函数和数据以供外部访问,COM中,类只提供成员函数。
5、COM类没有具体规定的布局。
6、COM引入了一套接口的规范来形式化对象指针的概念。
使用COM对象:
①、给操作系统发送实例化一个COM的请求;
②、得到对象的一个接口指针
③、使用接口; ④、释放接口

------------------------
COM 接口
一个接口就是一组等待被实现的纯虚函数

GUID
Globally Unique Identifier 128位数字

COM 类的ID用 "CLSID"作为前缀,
接口 ID 用"IID"作为前缀

=================
IUnknown接口
struct IUnknown{
virtual HRESULT QueryInterface ( IID& iid, void** ppvObj ) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
}
-------
对象生存期管理
COM对象自己控制生存期。
COM 对象的引用计数由 IUnknown 接口的 AddRef() 和Release() 这两个函数来管理。
--------
接口协商(negotiation,运行时发现功能)
当一个客户拥有了一个COM对象的一个接口指针,就能够通过 QueryInterface() 发现这个对象所支持的别的接口
HRESULT QueryInterface ( REFIID riid, LPVOID far* ppvObj );
riid用来标识所要得到的接口的 GUID。
ppvObj 用来存放接口指针
实现 IUnknown::QueryInterface的一个方法:
HRESULT CoSomeObject::QueryInterface ( IID& riid, void FAR** ppvObj ){
HRESULT hr = ResultFromSCode ( E_NOINTERFACE );
*ppvObj = NULL;
if ( riid == IID_IUnknown ){
*ppvObj = ( IPersist * ) this;
hr = NOERROR;
} else if (riid == IID_IPersist ) {
*ppvObj = ( IPersist * ) this;
hr = NOERROR;
}
return hr;
}
-------------
调用 、 使用 、释放。
1、调用某个方法得到一个接口(通过初始化一个对象或者通过一个存在的接口调用 QueryInterface())。
2、使用接口内的函数。
3、调用Release() 对对象引用计数减1.

========================
COM 对象服务器

COM类存在于服务器之中。COM服务器分为两种: 进程内服务器(in-proc)和进程外服务器(out-proc)。
in-proc server 是 DLL形式的,他们与客户在同一个进程空间内。
out-proc server 是作为 EXE文件形式存在,和客户在不同的进程空间内(甚至在不同机器).

进程内服务器(DLL)
进程外服务器(EXE)
有更强的鲁棒性,进程外服务器需要付出额外的代价,列集(marshaling)。
一个进程空间内的地址(数据地址、函数指针)在另一个进程空间内不起作用,需要用远程过程调用(RPC)和列集(marshaling)。
marshaling:
每一个接口,在客户代码中有一个代理(proxy),一个代理是一个接口的进程内实现。
服务端中,每一个特定的接口都建立了一份存根(stub)代码(在服务器进程空间内)。存根是RPC传输的接收端,
它把代理传过来的函数调用和参数传递映射为映射为服务器进程内的函数调用和参数传递。
客户通过接口进行函数调用时,代理将参数打包为一个32位可移植的数据结构。打包完后,对服务器进程产生一个远程过程调用。
服务器端,存根保存真正的接口指针、函数和相关的数据结构。存根将参数从包中解开以在本进程空间内调用。
一个函数调用返回时,存根将返回值和其他返回信息打包,发送回代理。代理解包,取出返回值供客户使用。

类厂(class factory)
COM通过类厂来创建对象,进程空间内的每一个COM类都有一个类厂与之相对应。类厂产生的是对象,不是类。
类厂是一个特殊COM类。
一个重要的接口是 IClassFactory。它包含两个函数:CreateInstance()和LockServer()。
客户通过调用CreateInstance来创建OLE类的实例,LockServer来增加整个服务器的引用计数。
每一个COM类在源代码一级都有自己的类厂。
进程内服务器通过一个预先定义的函数将类厂提供给客户,
进程外服务器将类厂注册到 Windows的注册表中。

在进程内服务器提供类厂
通过导出函数(exported function)来提供类厂,函数名为:DLLGetClassObject()。
客户可以通过 CoGetClassObject() 或 CoCreateInstance() 来创建一个 COM 对象。它们都调用了DLLGetClassObject来得到相应COM类的类厂接口指针。
然后可以调用类厂的CreateInstance() 来创建一个类的实例并且得到一个接口指针。
STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, LPVOID FAR* ppv)
要得到的类厂的COM类的GUID值,第三个参数 是一个指向接口指针的指针,用来存放请求的接口指针的地方。

在进程外服务器中提供类厂:
在运行时在注册表里注册类厂,CoRegisterClassObject():
HRESULT CoRegisterClassObject ( REFCLSID clsid, // 要注册的类的GUID
IUnknown *pUnk, //对象的 IUnknown 指针
DWORD grfContext, //参数用来标识服务器的环境——可执行的服务器代码是一个DLL、一个本地服务器还是一个远程服务器
DWORD grfFlags, //用来表示客户使用何种方式跟服务器建立连接
LPDWORD pdwRegister ); //指向DWORD的指针,所指向的值在类厂注册的时候由COM来填写。该值标识了类厂的注册信息,可以通过
它然后调用 CoRevokeClassObject() 来取消类厂的注册。

卸载进程内服务器
调用COM中的 CoFreeUnusedLibraries() 来实现这个功能,该函数通过调用DLL的DLLCanUnloadNow()来询问是否能被卸载。
卸载进程外服务器
进程外服务器是自己卸载自己,
· 当客户减少服务器的引用计数(通过调用IClassFactory::LockServer ( FALSE ),而且外部没有对象了。
· 当客户释放最后一个对象的最后一个接口指针,并且服务器的锁计数是0.

posted @ 2010-03-15 23:28 Euan 阅读(107) | 评论 (0) | 编辑 收藏

《深入解析MFC》笔记 10. MFC的DLL与线程

2009-10-1

========================================================
《深入解析MFC》笔记 10. MFC的DLL与线程
========================================================

---------------------
概念:
模块:
一段可执行的程序(包括EXE和DLL),其程序代码、数据、资源被加载到内存中,由系统建置一个数据结构来管理它,这就是一个模块。
进程:
进程是一堆拥有权(ownership)的集合,进场那个拥有地址空间,动态配置而来的内存、文件、线程和一序列的模块。
概要:
· DLL与线程的实现依赖于内部 MFC 状态类,MFC的状态将数据分到不同的逻辑范围中,从而使得线程和 DLL 不会破会对方的数据。
· 扩展 DLL 仅仅用来扩展已存在的 MFC 应用程序。
· MFC中,扩展 DLL 被创建时要使用 _AFXDLL标志。
· 扩展 DLL 有一些资源和其他信息需要在运行时被检索。CDynLinkLibrary是它的辅助类。

· 辅助线程,UI线程 都是用 _beginthreadex()创建、以_endthread()来结束。
· CWinThread::CreateThread创建线程,并且使用_AfxThreadEntry()来为线程提供执行路径。
· 核心:CWinThread::Run()
--------------------
MFC状态

3中MFC状态信息类型:
模块状态、进程状态、线程状态
win32中,一个模块就是独立于应用程序其他部分而操作的可执行代码。

模块状态, 既可以包含真正的全局状态,也可以包含进程局部或者线程局部的状态,且它可以被快速地切换。
可把MFC状态理解成应用程序不同部分的局部数据。
进程状态包含局部于进程和某个模块的数据。模块的状态信息包含局部于该模块的数据,线程的状态信息包含局部于该线程的数据。

------------------------------
MFC进程状态

AFX_MODULE_PROCESS_STATE的定义, AFXSTAT_.H 《深入解析MFC》P301
· m_pfnFilterToolTipMessage —— 一个函数指针,指向用于过滤工具提示消息的函数。
· CTypedSimpleList<CDynLinkLibrary*> m_libraryList —— 附加的MFC扩展DLL链表
· HINSTANCE m_appLangDLL —— 当前被局部化的资源的实例句柄。
· COccManager* m_pOccManager —— 指向OLE控件管理对象的指针
· CTypedSimpleList<COleControlLock*> m_lockList —— 被锁定的 OLE 控件链表

------------------------------
MFC模块状态

AFX_MODULE_STATE 定义, AFXSTAT_.H 《深入解析MFC》 P302
· CWinApp* m_pCurrentWinApp —— 指向该模块的CWinApp对象的指针
· HINSTANCE m_hCurrentInstanceHandle —— 模块的实例句柄
· HINSTANCE m_hCurrentResourceHandle —— 资源的实例句柄
· LPCTSTR m_lpszCurrentAppName —— 当前应用程序的名称
· BYTE m_bDLL —— 指明该模块是否是 DLL 的一个标志。
· BYTE m_bSystem —— 指明该模块是否是系统模块的一个标志
· short m_fRegisteredClasses —— 用于模块的延迟注册类的位标识。
· CRuntimeClass* m_pClassInit —— 指向第一个类的 CRuntimeClass 信息的指针(通常是 m_classList的头)
· CTypedSimpleList<CRuntimeClass*> m_classList —— 模块里各个对象的 CRuntimeClass 信息链表
· COleObjectFactory* m_pFactoryInit —— 指向第一个 COleObjectFactory对象的指针(通常是 m_factoryList 的头)
· CTypedSimpleList<COleObjectFactory*> m_factoryList —— 模块里各个对象的 COleObjectFactory 对象的链表
· long m_nObjectCount —— 被锁住的OLE对象的数目。
· BOOl m_bUserCtrl —— 如果用户有控制权,则设置为 TRUE,否则为FALSE
· TCHAR m_szUnregisterList —— 未被注册的类链表
· WNDPROC m_pfnAfxWndProc —— 指向模块所有的 AfxWndProc 的指针
· DWORD m_dwVesion —— 模块连接所使用的 MFC 版本号
· m_process —— 进程状态信息 PROCESS_LOCAL( AFX_MODULE_PROCESS_STATE, m_process )
· m_thread —— 线程状态信息 THTEAD_LOCAL( AFX_MODULE_THREAD_STATE, m_thread )

-------------------------------
MFC 线程状态信息 《深入解析MFC》P305

_AFX_THREAD_STATE
· AFX_MODULE_STATE* m_pModuleState —— 指向当前模块状态的指针
· AFX_MODULE_STATE* m_pPrevModuleState —— 指向下一个模块状态的指针
· void* m_pSafetyPoolBuffer —— 指向安全缓冲区的指针,它支持强壮的临时对象内存分配
· AFX_EXCEPTION_CONTEXT m_exceptionContext —— 当前的异常环境。
· CWnd* m_pWndInit —— 一个窗口指针,指向最近hook的窗口
· CWnd* m_pAlternateWndInit —— 指向最近被hook的公用对话框窗口的指针
· DWORD m_dwPropStyle ——属性页的风格
· DWORD m_dwPropExStyle —— 属性页的扩展风格
· HWND m_hWndInit —— m_pWndInit 的匹配句柄
· BOOL m_bDlgCreate —— 表明某个对话框被创建了,MFC为对话框绘制与普通窗口不同的背景颜色
· HHOOK m_hHookOldSendMsg —— 由::SetWindowsHookEx ( WH_CALLWNDPROC ) 返回的前一个句柄的句柄
· HHOOK m_hHookOldCbtFilter —— 由::SetWindowsHookEx ( WH_CBT )返回的前一句并的句柄。
· HHOOK m_hHookOldMsgFilter —— 由::SetWindowsHookEx ( WH_MSGFILTER )返回的前一个句柄的句柄
· MSG m_lastSentMsg —— 发送的最后一个消息
· HWND m_hTrackingWindow —— 当前跟踪窗口的句柄
· HMENU m_hTrackingMenu —— 当前跟踪菜单的句柄
· TCHAR m_szTempClassName —— 在 AfxRegisterWndClass() 中使用的缓冲区
· HWND m_hLockoutNotifyWindow —— 在锁定(没有OLE控件)的窗口句柄,如果存在一个的话。
· BOOL m_bInMsgFilter —— 表明该线程在一个消息过滤器中的标志。
· CView* m_pRoutingView —— 在将消息发送给文档之前,视图所先将自己保存到该变量中。
· CFrameWnd* m_bWaitForDataSource —— 之名 ODBC 正在等待的数据
· CToolTipCtrl* m_pToolTip —— 指向当前CToolTipCtrl的指针
· CWnd* m_pLastHit —— 指向拥有工具提示控件的最后一个窗口的指针
· int m_nLastHit —— 最后的点击测试代码(用于工具提示点击测试)
· TOOLINFO m_lastInfo —— 最后的工具提示 TOOLINFO结构
· int m_nLastStatus —— 最后的浮动状态代码
· CControlBar* m_pLastStatus —— 指向最后的浮动状态控制条的指针

----------------------------------------------
MFC状态之间的联系 《深入解析MFC》P305

当MFC需要到达当前的 _AFX_THREAD_STATE时,调用 AfxGetThreadState()
THREAD_LOCAL( _AFX_THREAD_STATE, _afxThreadState )
这行代码为每个线程的 TLS 创建一个名为 _afxThreadState 的 _AFX_THREAD_STATE 类,可通过调用 AfxGetThreadState()来访问
_AFX_THREAD_STATE 记录了指向当前模块状态的指针,名为 m_pModuleState。可通过调用 AfxGetModuleState()得到。
· 多数情况会得到 _afxThreadState.m_pModuleState 的 AFX_MODULE_STATE
· 如果为NULL,将有一个全局的进程局部模块状态,PROCESS_LOCAL( _AFX_BASE_MODULE_STATE, _afxBaseModuleState )
当MFC是一个DLL时,会通过调用 AfxSetModuleState() 来改变模块的状态。


=================
MFC的DLL
---------------------------《深入解析MFC》P307
USRDLL 与 AFXDLL

USRDLL:
可被静态“粘贴”到DLL上,可以再DLL使用MFC,而不必要求使用该DLL的应用程序也是用MFC写的。
AFXDLL :
是使用了MFC的DLL,只能用于MFC程序。

---------------------------
DLL 的资源问题

DLL 里有 3 种类型的信息:
资源、 静态的CRuntimeClass指针、 OLE对象厂(object factory)
MFC 的 DLL 版本程序起点在 DllMain()(当DLL被装载时该函数被调用)

AfxInitExtensionModule( coreDLL, hInstance );
CDynLinkLibrary* pDLL = new CDynLinkLibrary ( coreDLL, TRUE );

装载资源时,通过调用 AfxFindResourceHandle() 来实现
AfxInitExtensionModule( AFX_EXTENSION_MODULE& state, HMODULE hModule )
{
//only initialize once
if ( state.bInitialized ){ //若该模块已经初始化,AfxInitLocalData()被调用来更新 TLS 使用的模块句柄。
AfxInitLocalData( hModule );
return TRUE;
}
state.bInitialized = TRUE;
// save the current HMODULE info for resource loading
state.hModule = hModule;
state.hResource = hModule;
// save the start of the runtime class list
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
state.pFirstSharedClass = pModuleState->m_classList.GetHead();
pModuleState->m_classList.m_pHead = pModuleState->m_pClassInit;
//save the start of the class factory list
state.pFirstSharedFactory = pModuleState->m_factoryList.GetHead();
pModuleState->m_factoryList.m_pHead = pModuleState->m_pFactoryInit;
return TRUE;
}

--------------------------------
剖析 CDynLinkLibrary 《深入解析MFC》P310
class CDynLinkLibrary : public CCmdTarget
{
HMODULE m_hModule; //
HMODULE m_hResource; //for shared resources
CTypedSimpleList<CRuntimeClass* > m_classList;
CTypedSimpleList<COleObjectFactory*> m_factoryList;
BOOL m_bSystem; // TRUE only for MFC DLLs

CDynLinkLibrary* m_pNextDLL; //simple singly linked list
//implementation
CDynLinkLibrary::CDynLinkLibrary ( AFX_EXTENSION_MODULE& state, BOOL bSystem ){
m_factoryList.Construct( offsetof ( COleObjectFactory, m_pNextFactory ) );
m_classList.Construct( offsetof ( CRuntimeClass, m_pNextClass ) );
//copy info from AFX_EXTENSION_MODULE
m_hResource = state.hResource;
m_classList.m_pHead = state.pFirstSharedClass;
m_factoryList.m_pHead = state.pFirstSharedFactory;
m_bSystem = bSystem;
//insert at the end of the list ( extensions will go in front of core DLL )
AFX_MODULE_PROCESS_STATE* pState = AfxGetModuleProcessState();
AfxLockGlobals ( CRIT_DYNLINKLIST );
pState->m_libraryList.AddHead ( this );
AfxUnlockGlobals ( CRIT_DYNLINKLIST );
}
}

--------------------------------
剖析 AfxFindResourceHandle() 《深入解析MFC》P311
AfxFindResourceHandle() "DLLINIT.CPP"

首先,如果当前模块不是系统模块,在当前模块的资源句柄(由AfxGetResourceHandle() 函数返回)里查找资源。
接着,在进程状态 m_libraryList 里遍历 CDynLinkLibrary 链表。若扩展DLL不是系统 DLL,FindResource() 被调用。
然后在被进程状态指向的与特定语言相关的DLL里寻找该信息。
接着,检查当前的模块是否是系统模块,调用FindResource() 搜索资源,最后再次遍历 CDynLinkLibrary链表,在系统扩展DLL里查找该资源
若没有发现任何东西,返回 AfxGetResourceHandle(),即返回了进程的当前资源句柄。

--------------------------------
扩展 DLL 初始化与清除

AFXDLL 与 宏
DECLARE_DYNAMIC( DLL版 和 非DLL版本) (AFX.H) 《深入解析MFC》P312
IMPLEMENT_DYNAMIC (AFX.H)

DLL 和 非DLL之间的差别:
MFC必须调用能返回静态 CRuntimeClass成员地址的函数,而不是直接存储、访问静态CRuntimeClass数据成员的地址。


=========================
MFC 线程
辅助线程 UI 线程
AfxBeginThread()
辅助线程:将一个 CWinThread对象和一个指针传递给控制函数(控制函数负责工作的完成)
UI 线程: 创建一个 CWinThread,将他的 CRuntimeClass 信息传递给 AfxBeginThread()。

--------------------------
MFC 的辅助线程
AfxBeginThread(){
CWinThread* pThread = new CWinThread ( pfnThreadProc, pParam );
if ( !pThread->CreateThread ( dwCreateFlags | CREATE_SUSPENDED, nStackSize, lpSecurityAttrs ) ){
pThread->Delete(); return NULL;
}
VERIFY ( pThread->SetThreadPriority ( nPriority ) );
if( !(dwCreateFlags & CREATE_SUSPENDED )
VERIFY ( pThread->ResumeThread() != (DWORD) - 1);
return pThread;
}


=========================
CWinThread 《深入解析MFC》P316

数据成员:
· CWnd* m_pMainWnd —— 指向应用程序主窗口的指针, CWinThread需要用该指针来正确地用程序的主窗口。
· CWnd* m_bActiveWnd —— 当OLE 服务器在某个地方处于活动状态时,该指针指向包含应用程序的主窗口。
· BOOL m_bAutoDelete —— 当该变量为 TRUE时, CWinThread会在线程结束时删除自己。
· HANDLE m_hThread —— 被 CWinThread封装的Win32线程的句柄
· DWORD m_hThreadID —— 被CWinThread 封装的线程的 Win32 线程 ID。
· MSG m_msgCur —— 缓冲当前正被 CWinThread 消息发送其所处理的消息。
· LPVOID m_pThreadParams —— 保存pParam参数,他会继续传递给辅助函数(worker function)。
· AFX_THREADPROC m_pfnThreadProc —— 指向辅助函数的指针
· CPoint m_ptCursorLast —— 最后鼠标移动消息中的 CPoint。被用来在 CWinThread 的空闲时刻过滤掉多与的鼠标移动信息。
· UINT m_nMsgLast —— 用来探测两个连续的消息。

· CommonConstruct() —— 供构造函数调用,仅仅将数据成员设置成正确的默认值
· Delete() —— 如果 m_bAutoDelete为TRUE,则调用删除自身。

----------------------. 《深入解析MFC》P317
线程的创建—— “THRDCORE.CPP”
CWinThread::CreateThread() 第一部分:
BOOL CWinThread::CreateThread ( DWORD dwCreateFlags, UINT nStackSize, LPSECURITY_ATTRIBUTES lpSecurityAttrs ){
//setup startup structure for thread initialization
_AFX_THREAD_STARTUP startup;
memset ( &startup, 0, sizeof( startup) );
startup.pThreadState = AfxGetThreadState(); //_AFX_THREAD_STATE
startup.pThread = this; //指向 CWinThread 的后向指针 (back pointer)
startup.hEvent = ::CreateEvent ( NULL, TRUE, FALSE, NULL); //线程被成功创建后,hEvent被触发
startup.hEvent2 = ::CreateEvent ( NULL, TRUE, FALSE, NULL); //线程被重新启动,hEvent2 被触发
startup.dwCreateFlags = dwCreateFlags; //指定线程是否应该被挂起以及其他信息的创建标志。
//**some event checking and cleanup omitted for brevity
//create the thread (it may or may not start to run)
m_hThread = (HANDLE) _beginthreadex ( lpSecurityAttrs, nStackSize, &_AfxThreadEntry, &startup,
dwCreateFlags | CREATE_SUSPENDED, (UINT* ) &m_nThreadID );
if( m_hThread ==NULL)
return FALSE;
}
线程创建之后:
//CWinThread::CreateThread() continued...
① ResumeThread();
::WaitForSingleObject ( startup.hEvent, INFINITE );
② ::CloseHandle ( startup.hEvent );
//if created suspended , suspend it until resum thread wakes it up
if( dwCreateFlags & CREATE_SUSPENDED )
::SuspendThread ( m_hThread );
if( startup.bError )
//**Error cleanup omitted - lots of frees and closes <g>
// allow thread to continue, once resumed (it may already be resumed)
::SetEvent ( startup.hEvent2 );
return TRUE;

一个最初调用CreateThread() 得到的线程(父线程),另一个处在幼稚期的线程。
到①时调用::ResumeThread( m_hThread ),这是父进程暂停,它等待_AFX_THREAD_START::hEvent。
幼稚期线程开始运行,开始的位置在 _beginthreadex()调用告诉的位置:_AfxThreadEntry()
⊙﹏⊙b _AfxThreadEntry 简称为 “_ATE” 《深入解析MFC》P319
1、_ATE将它的 _AFX_THREAD_STARTUP 参数的 pThread 域保存到真正的 CWinThread 指针里。
2、然后调用 AfxGetThreadState(),它会返回父线程的状态,复制大部分父线程的状态信息,用_AFX_THREAD_STARTUP的参数来修改模块状态。
新的子线程从父线程那里”继承“到了模块状态信息。
3、再调用 AfxInitThread(),该函数为该线程创建了一个消息队列以及一些消息过滤器。
4、在AfxInitThread()执行完成后,_ATE创建一个局部的 CWnd对象,并将它贴附到当前的主窗口里。
即将它贴附到 CWinApp::m_pMainWnd::m_hWnd,然后使得 CWinThread::m_pMainWnd指针指向它的地址。
5、hEvent2句柄被从 _AFX_STARTUP_THREAD指针里拷贝出来。
6、准备通知父线程子线程已准备好,信号被转换成时间,使得父线程里的 ::WaitForSingleObject() 被启动。真正的函数调用是 ::SetEvent(startup->hEvent)
7、_ATE调用 ::WaitForSingleObject( hEvent2, INFINITE ) 将新的 成人线程休眠,直到父线程发出最后的信号
这时,在调用完::WaitForSingleObject()后,回到父线程,即回到②。
父线程调用::CloseHandle() 为它的子女清除 hEventHandle,若子线程以暂停方式被调用,则在子线程调用::SuspendThread()。
8、WaitForSingleObject()调用完成后,子线程向它的父线程说再见,并关闭了 hEvent2 记录的句柄
9、如果CWinThread::m_pfnThreadProc非空,子线程会意识到自己是 辅助线程,并且通过控制传递给 m_pfnThreadProc来开始工作
10、若m_pfnthreadproc为空,则为一个UI 线程。

_AfxThreadEntry() 中与 UI 有关的部分 "THRDCORE.CPP"
//Initialization and synchronization with mom before here.
if ( pThread->m_pfnThreadProc != NULL )
nResult = pThread->ExitInstance();
else if ( ! pThread->InitInstance() ) // InitInstance() 被CWinThread的派生类重载
nResult = pThread->ExitInsance();
else
nResult = pThread->Run(); // 通常情况调用 CWinThread::Run()
//cleanup and shutdown the thread
threadWnd.Detach(); //将局部的 CWnd 主窗口对象分离出来
AfxEndThread( nResult ); //最终调用 _endthreadex().
return 0; //not reached
----------------
MFC UI 线程 《深入解析MFC》P320

辅助线程仅需向 CWinThread 提供一个指向某个函数的指针,
UI 线程 需要从CWinThread 派生。
· ExitInstance() —— 当线程结束时执行一些清理工作。
· InitInstance() —— 执行线程实例的初始化工作。
· OnIdle() —— 执行与线程有关的空闲时的处理工作。
· PreTranslateMessage() —— 消息过滤器
· Run() —— 消息泵(非MFC消息的循环处理)
CWinApp 是一个UI线程

-----------------------------
CWinThread::Run() “THRDCORE.CPP”

int CWinThread::Run(){
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
for( ; ; ){
//phase1: check to see if we can do idle work
while (bIdle && ! ::PeekMessage ( &m_msgCur, NULL, NULL, NULL, PM_NOREMOVE ) ){
if ( ! OnIdle ( lIdleCount ++ ) )
bIdle = FALSE; // assume "no idle " state
}
//phase 2: pump messages while available
do {
if ( !PumpMessage() )
return ExitInstance();
if ( IsIdleMessage ( &m_msgCur ) ){
bIdle = FALSE;
lIdleCount = 0;
}
}while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE ) );
}
}
通过bIdle标志 和 LONG变量 lIdleCount 连接两个阶段。
· 阶段一 —— while循环,当线程的消息队列里没有任何消息时,它处在闲置状态。
· 阶段二 —— do-while 循环,当有消息需要被分发时,它进行消息的分发。


● 阶段一: 空闲处理
通过调用OnIdle() 来告诉用户没有执行任何操作,线程可以做一些清理工作或其他。闲置操作完成后,返回 0 ;
OnIdle()的参数用来指示线程已经有多长时间处在非活动状态了,这样,可以对空闲处理做一些优先排序工作。

CWinThread::OnIdle() “THRDCORE.CPP”

BOOL CWinThread::OnIdle ( LONG lCount )
{
if ( lCount <= 0 ) {
//send WM_DILEUPDATECMDUI to the main window
//send WM_IDLEUPDATECMDUI to all frame windows
}
else if ( lCount >= 0 ){
//Call AfxLockTempMaps / AfxUnlockTempMaps to free maps, DLLS, etc...
}
return lCount < 0; //nothing more to do if lCount >= 0
}
若调用 OnIdle( -1 ),强制执行一次 UI 更新,

● 阶段二 : 消息的分发

do-while 循环中,调用 PumpMessage() 分发消息,然后调用 IsIdleMessage() 来确认当前正在被处理的消息是否是空闲消息。

posted @ 2010-03-15 23:27 Euan 阅读(400) | 评论 (0) | 编辑 收藏

《深入解析MFC》笔记 9.MFC的增强型用户界面类

2009-9-8

========================================================
《深入解析MFC》笔记 9.MFC的增强型用户界面类
========================================================

----------------------------
CSplitterWnd: MFC分割窗口 《深入解析MFC》P249

使用 CSplitterWnd
创建动态分割窗口
① 在子框架的派生类中加上一个 CSplitterWnd 数据成员。
② 在CMyChildFrame::OnCreateClient() 处理程序,添加对 CSplitterWnd::Create() 的调用。
Create() 的第一个参数指向父框架的指针,第二个和第三个分别指定了最大行数和最大列数。
第四个参数指定了所允许的最小窗格大小,第五个参数指向 CCreateContext 的指针。
③ 将框架窗口的 RUNTIME_CLASS 消息传递给 CDocTemplate 构造函数:
CMultiDocTemplate* pDocTemplate =
new CMultiDocTemplate( IDR_MYAPPTYPE, RUNTIME_CLASS(CMyDocClass ), RUNTIME_CLASS( CMyChildFrameClass ),
RUNTIME_CLASS(CMyViewClass));
AddDocTemplate(pDocTemplate);

创建静态分割窗口,用 CreateStatic() 代替 Create()。调用CSplitterWnd::CreateView() 来创建新的窗格。

*************************
CSplitterWnd 内部实现

封装的数据类型
· ESplitType —— 定义要画出的分割器的类型,属于枚举类型。类型有 分割框、分割条、分割焦点以及分割边界。
· CRowColInfo —— 记录行或列的最小尺寸、理想尺寸和当前尺寸。
创建/布局数据成员
· m_pDynamicViewClass —— 指向由 CSplitterWnd 动态创建的视图(窗格)的CRuntimeClass 信息的指针。Create() 或CreateView()中定义
· m_nMaxRows / m_nMaxCols —— 调用 Create() 和 CreateStatic() 时制定的最大行数和列数。
· m_nRows / m_nCols —— 当前在 CSplitterWnd 里显示的行数和列数。
· m_bHasHScroll / m_bHasVScroll —— 表明行滚动条或列滚动条 是否已经创建的标记。
· m_pColInfo —— 是CRowInfo的数组,每个元素对应 CSplitterWnd 的一列。静态分割中,这个值固定。
· m_pRowInfo —— 是 CRowColInfo 的数组,每个元素对应CSplitterWnd 的一行。
修饰的数据成员
在构造函数中初始化,
· m_cxSplitte / m_cySplitter —— 分割框和分割器的宽度和高度。
· m_cxBorderShare / m_cyBorderShare —— 如果分割窗口正在画分割窗口的边界,值为1.
· m_cxSplitterGap / m_cySplitterGap —— 分割框/分割条和滚动条/边界之间的距离。值为6.
· m_cxBorder / m_cyBorder —— 分割边界的边界宽度。值为0.
跟踪数据成员
用于点击测试和跟踪
· m_bTracking —— 如果为真,则用户正在拖动一个分割条。
· m_bTracking2 —— 如果为真,用户正在拖动两个分割条。
· m_ptTrackOffset —— 点击测试中的“选取”尺寸。允许用户有所偏差。
· m_rectLimit —— 跟踪时窗格的大小,用来确定被跟踪的分割条的高度。
· m_rectTracker —— 跟踪时用来画分割条的矩形。
· m_rectTracker2 —— 跟踪时用来画第二个分割条的矩形
· m_htTrack —— 被 CSplitterWnd 的点击跟踪机制设置成一个枚举值,用来描述分割窗口的哪个部分被点击了。
通用成员函数
· CreateCommon() —— 当Create() 和 CreateStatic() 初始化完 CSplitterWnd 的动态成员或静态成员时会调用这个函数。
· CreateScrollBarCtrl() —— 创建带有指定风格和标示符的滚动条。
· DoScroll() —— 对滚动条消息作出反应。 DoScroll() 能够同步适当的窗格。
· DoScrollBy() —— 以制定的数量滚动相应的窗格。
· DoKeyboardSplit() —— 在程序里调用该函数会使得窗口被分割。
· CanActivateNext() —— 用来确定下一个窗格是否能被激活。即是否能得到焦点,被CView类调用
· ActivateNext() —— 激活下一个窗格。通常在一个窗格被删除时调用
布局成员函数
· RecalcLayout() —— 维护所有分割窗口的位置,当一个窗格被创建/删除时,被调用。
· TrackRowSize() —— 更新指定行的 m_pRowInfo 数组信息。同时确定是否有足够的空间来存储该行。
· TrackColumnSize() ——
· GetSizingParent() —— 搜索大小可变的父窗口
绘画成员函数
· DrawAllSplitBars() —— “驱动”分割窗口的绘画进程,为每个需要绘画的组件调用 OnDrawSplitter.
· OnDrawSplitter() —— 为分割窗口的每个组件进行绘画,为虚函数。
· OnPaint() —— 对WM_PAINT消息作出响应
点击测试成员函数
· HitTest() —— 选取某个点,返回这个点的点击测试值。
· GetInsideRect() —— 类同GetClientRect(),考虑了共享的滚动条。
· GetHitRect() —— 为某一指定的分割窗口组件检索点击矩形。
· SetSplitCursor() —— 使用点击测试来确定要显示哪一种光标。
跟踪成员函数
· OnNcCreate() —— CSplitterWnd 处理WM_NCCREATE 消息,所以它可以移走 WS_EX_CLIENTEDGE 的扩展风格位。
· OnPaint() —— 画分割窗口的各个组件。
· OnDisplayChange() —— 当用户改变显示器的分辨率时背调用,调用RecalcLayout() 来更新分割窗口。
· OnSize() —— 当用户改变窗口大小时 调用 RecalcLayout()。
· OnMouseMove() —— 在分割窗口组件上执行点击测试。
**************************
CSplitteWnd 的初始化
CSPlitterWnd::CreateCommon()
① 调整好风格标记。
② 调用 AfxDeferRegisterClass(),CWnd::CreateEx()。
③ 为 m_pColInfo 和 m_pRowInfo 数组分配空间,并进行初始化,将 m_nMaxCols/Rows 作为数组的大小。
CreateCommon() 在循环里一次访问CRowColInfo数组,做如下操作
1. nMinSize 和 nIdealSize 都被设置成参数 sizeMin的值。
2. nCurSize 被初始化为 -1,说明当窗格的尺寸被初始化(RecalcLayout)时,该值应该被设置。
④ 初始化完CrowColInfo 的行列数组后,调用 SetScrollStyle() 将 m_bHasH/VScroll 进行初始化,然后返回TRUE

CSplitterWnd::CreateView()
① 将sizeInit参数存储在 CRowColInfo相应的数组下标里,设置一个局部标记 bSendInitialUpdate 值为FALSE。
② 创建一个局部的CCreateContext(),尽量将每个元素初始化为比较完整的值。调用 GetActivePane() 来确定 m_pLastViewCView指针
一旦CreateView() 有了m_pLastView,就可以通过调用 GetDocument() 来确定 CCreateContext 其他域的值,
然后调用 CDocument::GetDocTemplate()。在找到所有这些元素后,pContext指向她们,将bSendInitialUpadte设为 TRUE。
③ 调用 CreateObject() 为 CRuntimeClass 信息创建一个窗格对象。设置要传递到 Create() 的参数 风格和定位矩形。

posted @ 2010-03-15 23:26 Euan 阅读(271) | 评论 (0) | 编辑 收藏

《深入解析MFC》笔记 8.MFC的文档/视图结构 拓展

2009-9-7

========================================================
《深入解析MFC》笔记 8.MFC的文档/视图结构 拓展
========================================================

----------------------------
CMirrorFile 《深入解析MFC》 P221

CMirrorFile::Open() (DOCCORE.CPP)
第一部分:先检查 modeCreate,看调用者是想创建一个新文件还是想连接在已有文件后面。
然后调用CFile::GetStatus(),若文件非空,返回一个非零的数;
若文件存在,Open()接着会调用GetDiskFreeSpace(),确定驱动器有多少字节可以用。将结果和已有文件大小的两倍进行比较。
若大于后者,创建一个临时文件,文件名存储在 m_strMirrorName 中。
第二部分当 m_strMirrorName 非空时执行。
调用 CFile::Open() 打开镜像文件,然后将文件的时间和文件权限从源文件拷贝给镜像文件。
若执行了第二部分,Open() 返回TRUE,若没有被执行,会调用 CFile::Open(), 返回调用结果。
(若有写操作正在执行或者有文件被覆盖,CMirrorFile::Open() 就打开一个和指定文件不同的文件。

CMirrorFile::Close()
先在 m_strName 存储文件的名字,在调用CFile::Close() 之后,检查是否使用了镜像文件,若使用了,Open()会删除指定 文件,将文件拷贝为指定文件。

即CMirrorFile 通过保存原有文件和对临时文件进行写操作来保护你的文档。
这样,若写操作出现问题,源文件也是安全的 。
CMirrorFile 还会确保源文件的安全性以 及文件创建信息是否能被正确地拷贝


---------------------------
CView 打印 《深入解析MFC》 P223 - 235


---------------------------
CView的派生类: CScrollView

调用SetScrollSizes() 让 CScrollView 知道你的“逻辑视图”的大小,获得大小信息后,操纵传递给 OnDraw() 的DC,从而支持滚动

CScrollView 如何运作:
CScrollView ( VIEWSCRL.CPP )

· m_nMapMode —— 在 SetScrollSizes() 中,可以为应用程序指定一个映射模式。默认为MM_NONE,CScrollView定义为(MM_SCALETOFIT)。
· m_totalLog —— 逻辑坐标中视图的大小,这个值通过 SetScrollSizes() 成员函数传递给 CScrollView。
· m_totalDev —— 设备坐标中视图的大小。
· m_pageDev —— 设备坐标中一个页的大小。
· m_lineDev —— 设备中一条线的大小。
· m_bCenter —— CPreviewView 使用这个数据成员进入窗口中的视图。

· CenterOnPoint() —— 将视图集中于一点,由 CPreviewView 调用
· ScrollToDevicePosition() —— 负责滚动视图。通过调用::SetScrollPos() 和 ::CSrollWindow() 更新滚动条,来完成视图的滚动条。
· UpdateBars() —— CScrollView 在初始化的时候和窗口大小发生变化的时候调用。责任是根据 GetScrollBarState() 的返回信息隐藏、显示、初始化滚动条。
· GetTrueClientSize() —— 用来确定用户视图是否足够大(是否需要滚动条),只有 UpdateBars 才会调用这个函数。
· GetScrollBarSizes() —— 确定滚动条的宽度和高度,考虑了窗口的风格和边框的宽度。
· GetScrollBarState() —— 获取 CScrollView 所需要的关于视图的状态。
· CalcWindowRect() —— 计算窗口矩形的大小,考虑滚动条和其他窗口饰物的大小。
· OnPrepareDC() —— CScrollView 和 ONVScroll() 交互的关键。
· OnScroll() —— OnHScroll() 和 OnVScroll两个消息处理函数都调用了这个函数。根据页面大小和行大小确定要滚动的量。再调用 OnScrollBy()
· OnScrollBy() —— 检查需要滚动的量是否超出了滚动条的范围和视图的逻辑大小。若没有,调用 ::SetScrollPos() 移动滚动条,在调用 ::ScrollWindow().
· OnSize() —— 若 CScrollView 不是处于“scale-to-fit”模式下,OnSize() 调用 UpdateBars();否则,调用 SetScaleToFitSize()。
· OnHScroll() —— 调用 OnScroll() 的消息处理函数。
· OnVScroll() —— 调用 OnScroll() 的消息处理函数。

CScrollView::SetScrollSizes() (VIEWSCRL.cpp) 《深入解析MFC》P239
①. 将 m_nMapMode 初始化为提供的新映射模式, m_totalLog 的初始化值是sizeTotal 参数。
②. 在栈上创建一个 CWindowDC,并且在设置了映射模式之后,使用DC 来计算视图大小、页面大小和行大小的设备坐标。
③. 将逻辑坐标转化为设备坐标后,检查确保用户提供了一个非默认的值,若为默认值0,页面大小设为视图大小的1/10,行为页的1/10或视图的1/100。
④. 若需要,调用 UpdateBars() 和 Invalidate()。设置滚动条,若修改了映射模式,重画。

CScrollView::OnPrepareDC() 在调用 OnDraw()之前调用
①. 设置映射模式,若在“scale-to-fit”模式下,模式设置为 ANISOTROPIC,然后对窗口和视图端口操作,知道视图的大小和客户窗口匹配。
若不是,调用 SetMapMode(), 参数是用户通过 SetScrollSizes() 指定的映射模式。
②. 在栈上创建一个 CPoint ptVpOrg(assume no shift for printing),初始化为 0,0。 若视图不在打印, ptVpOrg = -GetDeviceScrollPosition()
③. 调用 CDC::SetViewportOrg( ptVpOrg )。然后调用 CView::OnPrepareDC()。

-------------------------------------
CFormView

先用资源编辑器创建一个对话框模板,然后创建一个CFormView 的派生类,然后将它和对话框模板绑定(通过将模板的资源ID 传递给 CFormView 构造函数)

(VIEWFORM.CPP) 《深入解析MFC》 P242
· m_lpszTemplateName —— 对话框模板资源的名字
· m_pCreateContext —— 指向 CCreateContext 的指针。CFormView不直接使用,会再 OnCreate() 函数中将它传送给框架。
· m_hWndFocus —— 在 OnSetFocus() 中,CFormView将焦点设置为窗口句柄, OnActivateView(),OnActivateFrame()。
· OnDraw() —— 什么都不做,Windows控件都会重画自己。
· PreTranslateMessage() —— 完成一些路由的任务,将控件、视图和窗口的消息送往正确的目的地
· SaveFocusControl() —— 将 m_hWndFocus 设置为 ::GetFocus() 的返回结果,即当前处于焦点的控件。
· OnActivateFrame() —— 当视图被禁用时,调用 SaveFocusControl() 确保当焦点回来是,焦点的控件仍有控制。
· OnActivateView —— 设置 m_hWndFocus 变量
· OnCreate() —— 将m_pCreateContext 传递给 LPCREATESTRUCT 结构指针的 lpCreateParams 域。
· OnSetFocus() —— 若 m_hWndFocus 指向一个合法窗口,OnSetFocus()会将焦点设为这个控件。否则将焦点设为源窗口。

CFormView::Create()
①. 在m_pCreateContext 中存储 CCreateContext 参数,这样就可以再调用 OnCreate() 时传入。然后确保 Windows通用控件都是注册过的。
②. 调用PreCreateWindow 来确定用户指定的扩展风格,在调用 CWnd::CreateDlg()。这个函数从应用程序的资源中装载对话框模板,
并调用CWnd::CreateDlgIndirect()。CreateDlg() 创建一个非模态的对话框。
③. 将m_pCreateContext 设置为 NULL,再修改窗口的标准风格和扩展风格。将窗口控件的标示符设置为 nID 参数。
再调用 SetScrollSizes() 来初始化 ScrollView,采用的映射模式为 MM_TEXT。就创建了一个和对话框模板一样大的逻辑视图。
④. 将视图的大小和 rect 参数的值匹配,再在 PreCreateWindow() 中调用指定了 WS_VISIBLE,就调用 ShowWindow()。
CFormView::OnInitialUpdate()调用了 UpdateData(FALSE) 来初始化窗体中的控件。 DDX/DDV就认为CFormView是一个对话框。

--------------------------------
CCtrlView 《深入解析MFC》 P244

CCtrlView 派生以下类: CEditView、CListView、CTreeView、CRichEditView

· m_strClass —— 包含了控件的窗口类的名字,通常是“viewized”。在构造函数中设置。
· m_dwDefaultStyle —— 视图类的默认风格。通过构造函数传入。
· OnDraw() —— 从不被调用,调用了 ASSERT(FALSE)来确保不被调用。
· PreCreateWindow() —— 设置CREATESTRUCT 的 lpszClass 域,设置成 m_strClass 成员数据,这样就创建了控件。
还对 CREATESTRUCT 的风格域操作,以反映存储在 m_dwDefaultStyle 中的值。
· OnPaint() —— 调用 Default()。Default() 会将前一个消息(WM_PAINT)发送给DefWindowProc()。

CTreeView (VIEWCMN.CPP,AFXCVIEW.INL)
· GetTreeCtrl() —— 返回一个 CTreeCtrl 引用,类的使用者可以用这个引用直接调用 CTreeCtrl,对树控件操作。
· RemoveImageList() —— 一个内部辅助函数,将控件的图像链表清空。这个辅助函数会再 OnDestroy() 中被调用
· OnDestroy() —— 清空 LSVIL_NORMAL 和 LVSIL_STATE 两个图像链表,调用 RemoveImageList()。

posted @ 2010-03-15 23:25 Euan 阅读(506) | 评论 (0) | 编辑 收藏

《深入解析MFC》笔记 7.MFC的文档/视图结构

2009-9-3

========================================================
《深入解析MFC》笔记 7.MFC的文档/视图结构
========================================================

文档/视图相互依赖关系
· CWinApp包含一个 CDocManager 指针。
· CDocManager 维护一个文档模板链表,须在 CWinApp::IniInstance()中 创建并加入这些模板。
· 文档模板将这 3 个类绑定在一起: CView / CDocument / CFrameWnd。这些类都根据传递给文档模板构造函数的CRuntimeClass信息创建
· MDI 专有的文档模板维护一个打开文档链表,而 SDI 专有的文档模板只有一个指向打开文档的指针。
· 文档维护一个打开视图的链表。使用这个链表与视图通信、更新视图。
· 文档有一个指针指向它们的文档模板。用这个指针设置标题,进行命令路由选择,在被删除时通知文档模板
· 框架有一个指针指向当前的活跃视图
· 被创建时,框架等到一个 CCreateContext 结构,包含了在文档模板中可以找到的CRuntimeClass信息和一个指向文档的指针。
· 视图有一个指针指向它的文档。当该视图被删除时,用该指针通知文档。
· 视图可以通过调用 CView::GetParentFrame() 获得它的框架窗口。
· 如果文档要访问它的所有视图的框架,可以遍历它的视图链表,并调用 CView::GetParentFrame().

· CDocTemplate —— 在 CWinApp 中创建,保存在 CWinApp::m_pDocManage中
· CDocManager —— 在CWinApp::InitInstance() 中创建
· CFrameWnd —— 在 CDocTemplate::CreateNewFrame() 中创建
· CDocument —— 在 CDocTemplate::CreateNewDocument() 中创建
· CView —— 通过 CFrameWnd::OnCreate() 创建

ID_FILE_OPEN → CWinApp::OnFileOpen() CWinApp::OnFileNew() ← ID_FILE_NEW
命令 ↓ ↓
获取文件名 一个文档模板? →否→ 从用户获取文档类型
↓ ↓ ↓
使用文件拓展名,选择文档模板 是 ↓
↓ ↓ ↓
------------------------------选择的模板-------------------------------


选择模板
↓ → →→打开? →→Yes→→ CMyDoc::OnOpenDocument() →
Construct document object: CMyDoc ↑ ↓ ↓ ↓
↓ ↑ ↓ ↓ ↓
Construct frame window object : CMainFrame ↑ →→→→→→→CMyDoc::OnNewDocument() ↓
↓ ↑ ↓ ↓
CframeWnd::Create() ↑ 文档准备好 ←←←←←←
CFrameWnd::OnCreateClient() ↑
↓ ↑
Create CMyView →→→→→→→→→→→→ 选择模板之后的控制流程


CWinApp::OnFileOpen() CWinApp::OnFileNew()
↓ ↓
CDocManager::OnFileOpen() CDocManager::OnFileNew()
↓ ↓
CDocTemplate::OpenDocumentFile()
CDocTemplate::CreateNewDocument()
CDocTemplate::CreateNewFrame()
WM_CREATE
CFrameWnd::OnCreate()
CFrameWnd::OnCreateHelper()
CFrameWnd::OnCreateClient()
CFrameWnd::CreateView()
CMyDocument::OnOpenDocument()
[if file was specified to OpenDocumentFile()]
CMyDocument::OnNewDocument()
创建文档/视图体系中所有对象过程中调用的函数


-----------
体系结构

文档与视图
1)文档; 2)视图; 3)文档/视图框架; 4)文档模版
文档
由CDocument类体现,派生自CCmdTarget,具有CObject提供的所有支持,且可以接受命令消息,支持组件对象模型(COM)接口和 OLE 自动化。
视图

文档/视图框架
文档模板 《深入解析MFC》 P 198
将整个机制绑定在一起。这3个组件由 CDocTemplate 的类管理。
CDocTemplate是一个抽象基类,定义了处理文档、框架和视图的基本操作。
CSingleDocTemplate: 4个参数①一个资源ID,②运行时的CDocument派生类;③ 运行时的试图框架; ④ 运行时的文档视图。
资源ID:①窗口标题;②文档名称;③当创建一个新文档时使用的文档类型描述符;④对标准的打开文件对话框中文件类型的描述符
⑤文档扩展名过滤器; ⑥ 文件管理器使用的文件类型描述符; ⑦ 在Windows注册表中注册的 ProgID。
SMultiDocTemplage
可以支持一组文档的链表,而CSingleDocTemplate只能支持一种文档

CWinApp的角色
文档模板由 CWinApp 对象管理。

---------------
文档/视图结构内幕 《深入解析MFC》P 199
------------------------------------
CWinApp 管理文档模板,文档模板管理 框架/视图/文档。

CWinApp / CDocTemplate 接口: CDocManager
· CPtrList m_templateList —— 维护一个文档模板列表的指针。 通过GetFirstDocTemplatePosition()和GetNextDocTemplate()来遍历模板列表。

CDocManager::OnFileNew()
负责创建一个新的文档模板
· ①OnFileNew() 检查 m_templateList 中是是否有多于一个文档模板,若是,则创建一个 CNewTypeDlg 对话框,列出一个文档链表以供用户选择。
· ② 调用CDocTemplate::OpenDocumentFile(),
tag:CNewTypeDlg的构造函数读入一个指向文档模板指针链表的指针,即 m_templateList。在DoModal() 之后,OnFileNew()从
CNewTypeDlg::m_pSelectdeTemplate 中得到选中的对话框模板。

CNewTypeDlg (DOCMGR.CPP)
CNewTypeDlg::OnInitDialog()
· ①, 调用GetDlgItem() 得到一个指向 CListBox 指针,并将之类型转换成 CListBox 指针。
· ②,得到指针后,遍历它的文档模板链表,遍历时,通过调用CDocTemplate::GetDocString()得到文档类型的字符串版本,
OnInitDialog获取每个文档的这个字符串并加入链表,再调用 CListBox::SetItemDataPtr() 将这个文档模板指针附加到这项上。
· ③,返回到CDialog::OnInitDialog()。
tag:在CNewType::OnOK()中,当用户选择从链表中选择了一项并按下OK按钮时, CNewType::OnOK() 从量表中得到被选中的项,并调用
CListBox::SetItemDataPtr() 获得指向该文档模板的一个指针。 OnOK() 将这个被选中的模板指针放在 pSelectedTemplate 中,这样,
DoModal() 返回后就可以获取这个指针
----------------------------------------
CDocTemplate: CDocument、CView 和 CFrameWnd 管理器

· m_nIDResource —— 存储传给 CDocTemplate 构造函数的第一个参数,是一个资源ID,
· m_pDocClass —— 指向该文档模板的CDocument(或派生类)的CRuntimeClass结构,在构造函数中设定
· m_pFrameClass —— 指向该文档模板的 CFrameWnd(或派生类)的CRuntimeClass 结构。在构造函数中设定
· m_pViewClass —— 指向该文档模板的CView (或派生类)的CRuntimeClass结构,在构造函数中设定。
· m_strDocStrings —— 一个CString,包含用来定义与每个文档模板联系的7个字符串的字符串表资源。在LoadTemplate()中初始化。

创建新的 文档/视图/框架
CDocTemplate::CreateNewDocument() (DOCTEMPL.CPP) 《深入解析MFC》P205
· 首先,通过CRuntimeClass 指针的数据成员 m_pDocClass 调用 CRuntimeClass::CreateObject() 创建一个新的文档
· 在pDocument 中保存了 新的 CDocument(或派生类 ) 指针之后,验证 CreateObject 是否已经工作,若失败,返回NULL。
若成功,调用 AddDocument() 将该文档加到打开文档链表中。 CreateNewDocument() 返回指向新文档的指针。

CDocTemplate::CreateNewFrame()
· 首先,填充一个CCreateContext(存放创建各种文档/视图结构元素所需的关键信息)。
1>. m_pNewViewClass —— 一个 CRuntimeClass 指针,用来创建视图
2>. m_pCurrentDoc —— 指向当前文档对象的指针
3>. m_pCurrentFrame —— 指向当前框架对象的指针
4>. m_pNewDocTemplate —— 如果有多个文档,指向最后一个;
5>. m_pLastView —— 如果有多个视图,指向最后一个视图。
· 通过m_pFrameClass 中的 CRuntimeClass 结构调用 CRuntimeClass::CreateObject() 创建一个框架,再调用 LoadFrame()装入
这个框架的资源,根据这些值创建该框架。 最后,CreateNewFrame()返回一个指向新框架的指针。

CSingleDocTemplate 《深入解析MFC》P 207
: CDocument* m_pOnlyDoc;
AddDocument() 将 m_pOnlyDoc 设置成参数;

CMultiDocTemplate
: AddDocument() 通过调用 m_docList.AddTail( pDocument ) 将新文档加入自己的链表中。
CPtrList m_docList;
int m_nUntitledCount; //记录没有标题的窗口数

CMultiDocTemplate::OpenDocumentFile()
· 调用CreateNewDocument() 创建一个新的空文档(一个CDocument派生对象),然后通过 CreateNewFrame() 创建一个新的框架,
· 若参数 lpszPathName 为NULL, 则OpenDocumentFile() 知道要创建的事一个空的新文档,调用SetDefaultTitle()完成这个功能。
这个函数为新文档确认名称[使用Untitled与m_nUntitledCount中未命名计数的组合],然后通过新文档指针调用CDocument::SetTitle()
设置新文档名字,组后,OpenDocumentFile()调用 OnOpenDocumentFile(),并将未命名计数的数据成员加1.
· 若lpszPathName 不等于NULL,则 OpenDocumentFile() 尝试打开指定的文档,首先,OpenDocumentFile() 显示一个等待光标,然后
调用CDocument::OnOpenDocumentFile(),将文档名传入作为参数。若调用失败,则销毁新创建的对话框,返回一个NULL。若调用成功,
则OpenDocumentFile() 调用 CDocument::SetPathName() 更新CDocument 的路径。
·不管lpszPathName的状态时说明,OpenDocumentFile() 最后调用 CDocTemplate::InitializeUpdateFrame(),再调用
CFrameWnd::InitialUpdateFrame(), 并传递一个指向新文档的指针。

****///---------------------------------------------
CFrameWnd (WINFRM.CPP)

CFrameWnd 和 CView 的创建 《深入解析MFC》P208
视图的创建:CFrameWnd::CreateView
在文档模板在CreateNewFrame() 中创建框架时,Windows发出一个 WM_CREATE,进行如下调用
OnCreate -> OnCreateHelper() -> OnCreateClient() -> CreateView()
CreateView() 使用 CCreateContext::m_pNewViewClass 中存储的 CRuntimeClass 创建一个视图,创建完成后,调用 CWnd::Create()完成创建工作。

CFrameWnd::InitialUpdateFrame()
使框架中的所有视图都被更新。
· 首先,InitialUpdateFrame() 获得一个活跃视图的指针,并存放在 pView中,
· 若 bMakeVisible 参数为TRUE, 则InitialUpdateFrame() 发出 WM_INITIALUPDATE 给他所有后代,这个消息被映射到 CView::OnInitialUpadte()。
发送消息后,通过CView::OnActiveView() 激活视图。


****///---------------------------------------------
CDocument (AFXWIN.H)

· m_strTitle —— 文档名,通过 SetTitle()设置,保存时用它来设定框架窗口的标题和文件的名字
· m_strPathName —— 当前文档的路径(包括文件名)。用 SetPathname() 设置,是对MRU菜单的补充,保存文件时用到。
· m_pDocTemplate —— 指向文档的文档模板的指针。 CDocTemplate::AddDocument()中设置,通过CDocTemplate::GetDocTemplate()访问
· m_viewList —— 记录所有在该文档下打开的所有视图的指针。 文档改变时,通过这个指针通知视图。
视图通过AddView()加入到m_viewList中,RemoveView()删除。
GetFirstViewPosition() 和 GetNextView() 来遍历
· m_bModified —— “修改”标志位,被修改就设为 1。为1是框架在用户关闭文档时提示。 IsModified() SetModifiedFlag()
· m_bAutoDelete —— 被框架设置为 TRUE时,所有视图关闭时该文档对象要被删除。可设为 FALSE,文档在没有任何打开视图时仍会保留
· DisconnectViews() —— 该成员函数遍历 m_viewlist 中保存的视图链表,并将 CView::m_pDocument 指针设置为 NULL,从而将所有视图从文档上断开。
· DoSave() —— CDocument成员 OnFileSaveAs() 和 OnFileSave() 在提示输入文件名或验证文件名后,最终都调用 DoSave(),DoSave调用了 OnSaveDocument()
· DoFileSave() —— 先检查文档要被存储到的文件的一些性质,在调用 DoSave()。
· UpdateFrameCounts() —— 计数为该文档的所有视图打开的框架,并根据新的技术通知更新它们的窗口标题。
· SendInitialUpdate() —— 遍历视图链表并调用

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics