3.单文档总结
(1)MFC的winMain:
在vs的安装路径下面的文件winmain.cpp 中的唯一个函数
int AFXAPI AfxWinMain(…)
{
…
if (pApp != NULL && !pApp->InitApplication())
if (!pThread->InitInstance())
nReturnCode = pThread->Run();//从run里面就进入了消息循环
…
}
通过这一段代码可以非常清楚的知道,MFC程序是怎样通过比较大的三个步骤进入消息循环,然后再进入MFC的消息机制的。
注意的是pApp和pThread实际上都是同一个指针。
InitApplication()和InitInstance()都是虚函数,不过在单文档中只重载了InitInstance()函数,所以我们可以忽略掉InitApplication()函数。
1)InitApplication()
在这个函数中我们只需要注意下面的一句,表示文档是动态创建的。(动态创建和RUNTIME_CLASS有关)
CDocManager::bStaticInit = FALSE;
2)分析InitInstance()
首先我们需要知道,管理文档的是CSingleDocTemplate(继承于CDocManager)。
下面贴出在InitInstance()中最主要的一段代码
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CsingleDocDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CsingleDocView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line. Will return FALSE if
// app was launched with /RegServer, /Register, /Unregserver or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
在分析这段代码之前,我们已经知道了消息循环存在于pThread->Run()中。但是我们的窗体创建和窗口注册又在哪里呢?我们的窗体创建和注册都在ProcessShellCommand中。
RUNTIME_CLASS是一个宏,作用是得到类的CRuntimeClass类型的对象的指针:
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
补充说明:#的作用是转换成字符串,##的作用就是连接
CRuntimeClass是一个结构体,里面存有这个类的名字,和用于动态创建的函数:CreateObject。
至于CRuntimeClass是怎样引入到一个类里面和怎样填完的,我们就必须知道动态创建:
DECLARE_DYNCREATE给一个类引入了三个非常重要的东西来支持动态创建:
#define _DECLARE_DYNCREATE(class_name) \
_DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define DECLARE_DYNAMIC(class_name) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
static const CRuntimeClass class##class_name; \
static CRuntimeClass* PASCAL GetThisClass(); \
virtual CRuntimeClass* GetRuntimeClass() const; \
在 IMPLEMENT_DYNCREATE 给出了这些实现:
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
CRuntimeClass* PASCAL class_name::_GetBaseClass() \
{ return RUNTIME_CLASS(base_class_name); } \
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL, class_init }; \
CRuntimeClass* PASCAL class_name::GetThisClass() \
{ return _RUNTIME_CLASS(class_name); } \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return _RUNTIME_CLASS(class_name); }
所以 new CSingleDocTemplate(…)的作用就很简单了:
CDocTemplate 里面有成员变量分别存放 doc,frame,view的 runtime_class类型的指针。其中IDR_MAINFRAME 是用来表示资源的,也用一个变量存放着。
存放着干什么呢? 存放着给ProcessShellCommand 里面用来调用它们的 CreateObject() 来创建各自的对象。
AddDocTemplate(pDocTemplate) ; 的作用:
在 CWinApp 里面有一个变量m_pDocManager, 用它来存放 pDocTemplate。
m_pDocManager->AddDocTemplate(pTemplate);
至此,如果你记住了 m_pDocManager, pDocTemplate, DOC, Frame, view 它们三者之间的关系, 并且知道doc,frame,view是在ProcessShellCommand里面创建的话,那么你就基本上搞清楚 singledoc 的结构了。
Doc,frame的创建比较容易找到,提醒一下view的创建在 CFrameWnd::CreateView
注意的是:我们可以重载一些函数来达到我们的目的。
MDI总结:
在MDI中下面的对象需要关注:theApp,DocManager, docTemplate, mainFrame,childFrame, view, doc。
一个app可以有多个docTemplate.一个mainFrame可以有多个childFrame;一个childFrame可以有多个view;但是doc和view是多对一的关系。
(1).theApp有一个CDocManager类的对象成员,用来管理docTemplate。
CDocManager 类则拥有一个指针链表 CPtrList m_templateList来维护这些文档模版。这些文档模版都是在 CAApp:: InitInstance()中通过 AddDocTemplate(pDocTemplate)。
CDocTemplate拥有 3个成员变量,分别保存着 Document、 View、 Frame的 CRuntimeClass指针,另外持有成员变量 m_nIDResource,用来指定这个 Document显示时候采用的菜单资源。这 4份数据都在 CAApp:: InitInstance()中 CDocTemplate的构造函数中指定。在 Document中拥有一个回指 CDocTemplate的指针( m_pDocTemplate)。
DocTemplate还有一个成员变量 m_docList 保存着所有doc的指针。
具体的可以看下面的代码:
(2) 一个文档可以有多个视图,由 Document中成员变量 CPtrList m_ViewList维护。
CView 中拥有一个成员变量 CDocument* m_pDocument指向该视图相关的 Document。
(3).我们在程序中找不到 childFrame的list和所有view的list,这些list只是在系统内部维护,看不到源代码。
但是我们可以确定的是:在任何时刻,程序中只有一个活动的文档、框架和视图,即当前的文档、框架、视图。
需要注意:childFrame有自己的菜单,和mainframe的菜单不一样,看以参考word文档。
1. RTTI(Runtime Type Information)
在CRuntimeClass这个结构体里面存储有类名、类的基类的CRuntimeClass对象,CreateObject等信息,所以就能组成一个链表一样的数据结构。 我们利用这些信息就能实现对象的动态创建和对象的类型识别了。
CRuntimeClass对象链表的头:
我们可以看CObject里面有一个定义,它就是链表的头。
链表的建立:
在IMPLEMENT_DYNCREATE或者其他IMPLEMENT宏里面(不同版本的vs可能有差别)有下面的定义:
实际上链表的物理建立是由 对象AFX_CLASSINIT 这个对象实现的,我们看看它的构造函数就明白了:
需要注意的是,即使没有这个物理的链表,我们也有一个逻辑的链表,因为我们可以得到基类的CRuntimeClass对象,但是这条链表的功能相对于AFX_CLASSINIT就弱了许多。
进行对象动态识别的方法:
CObject提供了两个函数 GetRuntimeClass 和IsKindOf 两个成员函数来支持对象动态识别。
GetRuntimeClass 根据对象的类返回 CRuntimeClass的指针。
IsKindOf 用于测试对象和类的关系。
5.序列化
序列化的作用:
通过序列化可以把类的成员变量的值(也可以说是类的当前状态)保存到一个文件里,文件通常是二进制或者xml文件。然后再通过反序列化可以把这些值从文件中读取出来给一个类,来初始化一个类。
序列化也叫串行化,反序列化也叫反串行化。
MFC中序列化依赖的对象: CArchive类和CFile类。 CFile类的作用是以二进制方式存取文件。
序列化必须做的四件事:定义的类必须继承于CObject; 必须引入两个宏DECLARE_SERIAL() 和IMPLEMENT_SERIAL(); 必须实现自己的 Serialize() 虚函数;最后一点,必须有默认构造函数,因为会动态创建(CreateObject函数里面的new是调用默认构造函数的)。
Ex:
//cpp 文件
IMPLEMENT_SERIAL(CArwen, CObject,1)//1是版本号,可以随便指定一个数值
void CArwen::Serialize(CArchive &ar)
{
if(ar.IsStoring()){//序列化,保存信息。CArchive::store 与此对应
ar << age;
ar >> name;
}else{//反序列化,读取信息
{
ar >> age;
ar >> name;
}
}
.测试程序
CString filePath = _T("D:\\...");//这里是文件的路径
void TestStore()
{
CArwen arwen;
arwen.age = 24;
arwen.name = _T("weiwen");
CFile fi;
fi.open(filePath, CFile::modeCreate|CFile::modeWrite);
CArchive ar(&fi,CArchive::store);
awen.Serialize(ar);//序列化
}
void testRead()
{
CArwen arwen;
CFile fi;
fi.open(filePath, CFile::Read);
CArchive ar(&fi,CArchive::load);
awen.Serialize(ar);//反序列化
ar.Close();
fi.Close();
}