MFC六大关键技术之初始化过程

MFC六大关键技术之初始化过程
我并不认为 MFC 减轻了程序员们的负担, MFC 出现的目的虽然似乎是为了让程序员不用懂得太多就可以进行视窗编程,但本人在 MFC 里徘徊了很久很久(因为那时没有书本详细介绍 MFC 的原理),毫无收获。可能朋友们会说,怎么一定要了解 MFC 的具体呢, 黑箱 作业不行吗?这不是微软的初衷吗?

  不行!!!如果这样,我宁愿永远不选择 MFC !在学电脑之前,本人学习的东西大都与艺术不无关系,小学时参加过全国书画比赛获银奖。儿时的爱好就是在一张纸上随心所欲地画画! MFC“ 黑箱 就象一幅硕大的抽象画(抽象到你不能理解),它用铅笔勾画好线条,然后请你填颜色。  

  我们怎么能忍受 黑箱 作业?我们选择 C++ ,就是因为它够自由,够艺术,我们可以在此放飞幻想。所以,我们要攻克 MFC

  伟大孙老师在剖析 MFC 的时候虽然尽心尽力,但可能由于篇幅所限,说得并不大清楚(我相信许多学员都有这方面的感受)。在此,我突发奇想,想与大家一同分享一下著名的 MFC 六大关键技术。

  从什么地方开始讲起好呢?我觉得回到最初摸索 MFC 的时候,从基本谈起最好。

  因为我知道,一个走过来程序员,总是忘记了当初自己是怎么走过来的,忘记了一个学员最想知道的是什么。一个小小的问题(一两句话就可以解释的),足学以令手无寸铁的学员头大半个月,所以,我努力回忆当初是怎么让自己豁然开朗的。

  转入正题, MFC 的六大关键技术包括:

   ·MFC 程序的初始化过程
   · 运行时类型识别( RTTI
   · 动态创建
   · 永久保存
   · 消息映射
   · 消息传递
  
   MFC 程序的初始化过程

   1 、设计一个简单完整 MFC 程序,产生一个窗口。当然这不能让 AppWizard 自动为我们生成。我们可以在 Win32 Application 工程下面那样写:
#include <afxwin.h>
class MyApp : public CWinApp
{
 public:
  BOOL InitInstance() //②程序入点
  {
   CFrameWnd *Frame=new CFrameWnd();//构造框架
   m_pMainWnd=Frame; //将m_pMainWnd设定为Frame;
   Frame->Create(NULL,"最简单的窗口");//建立框架
   Frame->ShowWindow(SW_SHOW); //显示框架
   return true; //返回
  }
};
MyApp theApp; //①建立应用程序。

  设定链接 MFC 库,运行,即可看见一个窗口。

  从上面,大家可以看到建立一个 MFC 窗口很容易,只用两步:一是从 CWinApp 派生一个应用程序类(这里是 MyApp ), , 然后建立应用程序对象( theApp ),就可以产生一个自己需要的窗口 ( 即需要什么样就在 InitInstance() 里创建就行了 )

  整个程序,就改写一个 InitInstance() 函数,创建那么一个对象( theApp , 就是一个完整的窗口程序。这就是 黑箱 作业的魅力!!!!

  在我们正想为微软鼓掌的时候,我们突然觉得心里空荡荡的,我们想知道微软帮我们做了什么事情,而我们想编自己的程序时又需要做什么事情,那怕在上面几行的程序里面,我们还有不清楚的地方,比如,干嘛有一个 m_pMainWnd 指针变量,它从哪里来,又要到哪里去呢?想一想在 DOS 下编程是多么美妙的一件事呵,我们需要什么变量,就声明什么变量,需要什么样的函数,就编写什么样的函数,或者引用函数库 …… 但是现在我们怎么办!!!

  我们可以逆向思维一下, MFC 要达到这种效果,它是怎么做的呢?首先我们要弄明白, VC 不是一种语言,它就象我们学 c 语言的时候的一个类似记事本的编辑器(请原谅我的不贴切的比喻),所以,在 VC 里面我们用的是 C++ 语言编程, C++ 才是根本(初学者总是以为 VC 是一门什么新的什么语言,一门比 C++ 先进很多的复杂语言,汗)。说了那么多,我想用一句简单的话概括 “MFC‘ 黑箱 就是帮助我们插入了 ‘C++ 代码 的东西

  既然 MFC 黑箱帮我们插入了代码,那么大家想想它会帮我们插入什么样的代码呢?他会帮我们插入求解一元二次方程的代码吗?当然不会,所以它插入的实际上是每次编写窗口程序必须的,通用的代码。

  再往下想,什么才是通用的呢?我们每次视窗编程都要写 WinMain() 函数,都要有注册窗口,产生窗口,消息循环,回调函数 …… 即然每次都要的东西,就让它们从我们眼前消失,让 MFC 帮忙写入!

  要知道 MFC 初始化过程,大家当然可以跟踪执行程序。孙老师的第三课跟踪了很长一段时间,我相信大家都有点晕头转向。本人觉得那怕你理解了 MFC 代码,也很容易让人找不着北,我们完全不懂的时候,在成千上万行程序的迷宫中如何能找到出口?

  我们要换一种方法,不如就来重新编写个 MFC 库吧,哗!大家不要笑,小心你的大牙,我不是疯子(虽然疯子也说自己不疯)。我们要写的就是最简单的 MFC 类库,就是把 MFC 宏观上的,理论上的东西写出来。我们要用最简化的代码,简化到刚好能运行。

  既然,我们这一节写的是 MFC 程序的初始化过程,上面我们还有了一个可执行的 MFC 程序。程序中只是用了两个 MFC 类,一个是 CWinApp, 另一个是 CFrameWnd 。当然,还有很多同样重要 MFC 类如视图类,文档类等等。但在上面的程序可以不用到,所以暂时省去了它(总之是为了简单)。

  好,现在开始写 MFC 类库吧 …… 唉,面前又有一个大难题,就是让大家背一下 MFC 层次结构图。天,那张鱼网怎么记得住,但既然我们要理解他,总得知道它是从那里派生出来的吧。

  考虑到大家都很辛苦,那我们看一下上面两个类的父子关系 ( 箭头代表派生 )
CObject->CCmdTarget->CWinThread->CWinApp->自己的重写了InitInstance()的应用程序类。
CObject(同上)->CCmdTarget(同上)->CWnd->CFrameWnd

  看到层次关系图之后,终于可以开始写 MFC 类库了。按照上面层次结构,我们可以写以下六个类(为了直观,省去了构造函数和析构函数)。
/
class CObiect{};//MFC类的基类。
class CCmdTarget : public CObject{};
------------------------------------------------
class CWinThread : public CCmdTarget{};
class CWinApp : public CWinThread{};
------------------------------------------------
class CWnd : public CCmdTarget{};
class CFrameWnd : public CWnd{};
/

  大家再想一下,在上面的类里面,应该有什么?大家马上会想到, CWinApp 类或者它的基类 CCmdTarget 里面应该有一个虚函数 virtual BOOL InitInstance(), 是的,因为那里是程序的入口点,初始化程序的地方,那自然少不了的。可能有些朋友会说,反正 InitInstance() 在派生类中一定要重载,我不在 CCmdTarget CWinApp 类里定义,留待 CWinApp 的派生类去增加这个函数可不可以。扯到这个问题可能有点越说越远,但我想信 C++ 的朋友对虚函数应该是没有太多的问题的。总的来说,作为程序员如果清楚知道基类的某个函数要被派生类用到,那定义为虚函数要方便很多。也有很多朋友问, C++ 为什么不自动把基类的所有函数定义为虚函数呢,这样可以省了很多麻烦,这样所有函数都遵照派生类有定义的函数就调用派生类的,没定义的就调用基类的,不用写 virtual 的麻烦多好!其实,很多面向对象的语言都这样做了。但定义一个虚函数要生成一个虚函数表,要占用系统空间,虚函数越多,表就越大,有时得不偿失!这里哆嗦几句,是因为往后要说明的消息映射中大家更加会体验到这一点,好了,就此打往。

  上面我们自己解决了一个问题,就是在 CCmdTarge 写一个 virtual BOOL InitInstance()
大家再下想,我们还要我们 MFC“ 隐藏 更多的东西: WinMain() 函数,设计窗口类,窗口注册,消息循环,回调函数 …… 我们马上想到封装想封装他们。大家似乎隐约地感觉到封装 WinMain() 不容易 觉得 WinMain() 是一个特殊的函数,许多时候它代表了一个程序的起始和终结。所以在以前写程序的时候,我们写程序习惯从 WinMain() 的左大括写起,到右大括弧返回、结束程序。  

  我们换一个角度去想,有什么东西可以拿到 WinMain() 外面去做,许多初学者们,总觉得 WinMain() 函数天大的函数,什么函数都好象要在它里面才能真正运行。其实这样了解很片面,甚至错误。我们可以写一个这样的 C++ 程序:

#include <iostream.h>
class test{
 public:
  test(){cout<<"请改变你对main()函数的看法!"<<endl;}
};
test test1;
/**************************/
void main(){}

  在上面的程序里,入口的 main() 函数表面上什么也不做,但程序执行了(注:实际入口函数做了一些我们可以不了解的事情),并输出了一句话(注:全局对象比 main() 首先运行)。现在大家可以知道我们的 WinMain() 函数可以什么都不做,程序依然可以运行,但没有这个入口函数程序会报错。

  那么 WinMain() 函数会放哪个类上面呢,请看下面程序:
#include <afxwin.h>
class MyApp : public CWinApp
{
 public:
  BOOL InitInstance() //②程序入点
  {
   AfxMessageBox("程序依然可以运行!");
   return true;
  }
};

MyApp theApp; //①建立应用程序。

  大家可以看到,我并没有构造框架,而程序却可以运行了 —— 弹出一个对话框(如果没有 WinMain() 函数程序会报错)。上面我这样写还是为了直观起见,其实我们只要写两行程序:
#include <afxwin.h>
CWinApp theApp; 
//整个程序只构造一个CWinApp类对象,任可事情,程序就可以运行!

  所以说,只要我们构造了 CWinApp 对象,就可以执行 WinMain() 函数。我们马上相信 WinMain() 函数是在 CWinApp 类或它的基类中,而不是在其他类中。其实这种看法是错误的,我们知道编写 C++ 程序的时候,不可能让你在一个类中包含入口函数, WinMain() 是由系统调用,跟我们的平时程序自身调用的函数有着本质的区别。我们可以暂时简单想象成,当 CWinApp 对象构造完的时候, WinMain() 跟着执行。

  现在大家明白了,大部分的 通用代码(我们想封装隐藏的东西) 都可以放到 CWinApp 类中,那么它又是怎样运行起来的呢?为什么构造了 CWinApp 类对象就 自动 执行那么多东西。

  大家再仔细想一下, CWinApp 类对象构造之后,它会 自动 执行自己的构造函数。那么我们可以把想要 自动 执行的代码放到 CWinApp 类的构造函数中。

  那么 CWinApp 类可能打算这样设计(先不计较正确与否):
class CWinApp : public CWinThead{
 public:
  virtual BOOL InitInstance(); //解释过的程序的入点
  CWinApp ::CWinApp(){ //构造函数
   
   WinMain(); //这个是大家一眼看出的错误
   Create(); //设计、创建、更新显示窗口
   Run(); //消息循环
   //
  }
};

  写完后,大家又马上感觉到似乎不对, WinMain() 函数在这里好象真的一点用处都没有,并且能这样被调用吗(请允许我把手按在圣经上声明一下: WinMain() 不是普通的函数,它要肩负着初始化应用程序,包括全局变量的初始化,是由系统而不是程序本身调用的, WinMain() 返回之后,程序就结束了,进程撤消)。再看 Create() 函数,它能确定设计什么样的窗口,创建什么样的窗口吗?如果能在 CWinApp 的构造函数里确定的话,我们以后设计 MFC 程序时窗口就一个样,变得写程序变有必要。再看 Run() 函数,它能在 WinMain() 函数外面运行吗?

  回过头来,我们可以让 WinMain() 函数一条语句都不包含吗?不可以,我们看一下 WinMain()  函数的四个参数:
WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

  其中第一个参数指向一个实例句柄,我们在设计 WNDCLASS 的时候一定要指定实例句柄。我们窗口编程,肯定要设计窗口类。所以, WinMain() 再简单也要这样写:
int WinMain(HINSTANCE hinst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{ hInstance=hinst }

  既然实例句柄要等到程序开始执行才能知道,那么我们用于创建窗口的 Create() 函数也要在 WinMain ()内部才能执行 [ 因为如果等到 WinMain ()执行完毕后,程序结束,进程撤消,当然 Create() 也不可能创建窗口 ]

  那么 Run()( 消息循环 ) 放在那里执行好呢?众所周知,消息循环就是相同的那么几句代码,但我们也不要企图把它放在 WinMain ()函数之外执行。

  所以我们在 WinMain ()函数里面,我们程序要象以下这样写
WinMain(……)
{
 ……窗口类对象执行创建窗口函数……
 ……程序类对象执行消息循环函数……
}

  对于 WinMain ()的问题,得总结一下,我们封装的时候是不可以把它封装到 CWinApp 类里面,但由于 WinMain ()的不变性(或者说有规律可循), MFC 完全有能力在我们构造 CWinApp 类对象的时候,帮我们完成那几行代码。
转了一个大圈,我们仿佛又回到了 SDK 编程的开始。但现在我们现在能清楚地知道,表面上 MFC SDK 编程截然不同,但实质上 MFC 只是用类的形式封装了 SDK 函数,封装之后,我们在 WinMain ()函数中只需要几行代码,就可以完成一个窗口程序。我们也由此知道了应如何去封装应用程序类( CWinApp )和主框架窗口类( CFrameWnd )。下面把上开始设计这两个类。

  为了简单起见,我们忽略这两个类的基类和派生类的编写,可能大家会认为这是一种很不负责任的做法,但本人觉得这既可减轻负担,又免了大家在各类之间穿来穿去,更好理解一些(我们在关键的地方作注明)。还有,我把全部代码写在同一个文件中,让大家看起来不用那么吃力,但这是最不提倡的写代码方法,大家不要学哦!
#include <windows.h>
HINSTANCE hInstance;

class CFrameWnd 
{
 HWND hwnd;
 public:
  CFrameWnd(); //也可以在这里调用Create()
  virtual ~CFrameWnd();
  int Create(); //类就留意这一个函数就行了!
  BOOL ShowWnd();
};
class CWinApp1 
{
 public:
  CFrameWnd* m_pMainWnd;//在真正的MFC里面
  //它是CWnd指针,但这里由于不写CWnd类
  //只要把它写成CFrameWnd指针
  CWinApp1* m_pCurrentWinApp;//指向应用程序对象本身
  CWinApp1();
  virtual ~CWinApp1();
  virtual BOOL InitInstance();//MFC原本是必须重载的函数,最重要的函数!!!!
  virtual BOOL Run();//消息循环
};
CFrameWnd::CFrameWnd(){}
CFrameWnd::~CFrameWnd(){}

int CFrameWnd::Create() //封装创建窗口代码 
{
 WNDCLASS wndcls;
 wndcls.style=0;
 wndcls.cbClsExtra=0;
 wndcls.cbWndExtra=0;
 wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
 wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
 wndcls.hIcon=LoadIcon(NULL,IDC_ARROW);
 wndcls.hInstance=hInstance;
 wndcls.lpfnWndProc=DefWindowProc;//默认窗口过程函数。
 //大家可以想象成MFC通用的窗口过程。
 wndcls.lpszClassName="窗口类名";
 wndcls.lpszMenuName=NULL;
 RegisterClass(&wndcls);

 hwnd=CreateWindow("窗口类名","窗口实例标题名",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);
 return 0;
}

BOOL CFrameWnd::ShowWnd()//显示更新窗口
{
 ShowWindow(hwnd,SW_SHOWNORMAL);
 UpdateWindow(hwnd);
 return 0;
}

/
CWinApp1::CWinApp1()
{
 m_pCurrentWinApp=this;
}
CWinApp1::~CWinApp1(){}
//以下为InitInstance()函数,MFC中要为CWinApp的派生类改写,
//这里为了方便理解,把它放在CWinApp类里面完成!
//你只要记住真正的MFC在派生类改写此函数就行了。
BOOL CWinApp1::InitInstance()
{
 m_pMainWnd=new CFrameWnd;
 m_pMainWnd->Create();
 m_pMainWnd->ShowWnd();
 return 0;
}

BOOL CWinApp1::Run()//封装消息循环
{
 MSG msg;
 while(GetMessage(&msg,NULL,0,0))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 return 0;
} //封装消息循环

CWinApp1 theApp; //应用程序对象(全局)

int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
 hInstance=hinst;
 CWinApp1* pApp=theApp.m_pCurrentWinApp;
 //真正的MFC要写一个全局函数AfxGetApp,以获取CWinApp指针。
 pApp->InitInstance();
 pApp->Run();
 return 0;
}

  代码那么长,实际上只是写了三个函数,一是 CFrameWnd 类的 Create(), 第二个是 CWinApp 类的 InitInstance() Run() 。在此特别要说明的是 InitInstance() ,真正的 MFC 中,那是我们跟据自己构造窗口的需要,自己改写这个函数。

  大家可以看到,封装了上面两个类以后,在入口函数 WinMain 中就写几行代码,就可以产生一个窗口程序。在 MFC 中,因为 WinMain 函数就是固定的那么几行代码,所以 MFC 绝对可以帮我们自动完成( MFC 的特长就是帮我们完成有规律的代码),所以我们创造 MFC 应用程序的时候,看不到 WinMain 函数。

  写到这里, MFC 六大关键技术之一: MFC 程序的初始化过程(模拟),就差不多写完了。回头看一下,居然写了八千多字,原本以为写完六大关键技术也不用写那么多字,现在还觉得庆幸的是不把文档、视类牵连进去,否则更不知写到何时。

  还有五大关键技术没有写,我还应该写下去吗?上面写了八千多字,都是我一个字一个字地敲进去,每个例子都是自己生硬地想出来。用了十多个小时,换来的可能更多是论坛中朋友们的漫骂,讥讽!

  但我觉得还是值得的,我一向认为 VC 没有敌人,只有朋友,放眼周围,发觉学 VC 的朋友越来越少,也没有发现多少招收 VC 程序员的地方。记得读大学的时候,我遇到一位搞美术的师兄,本来同行如敌国(我曾经搞过美术)。师兄美术功底很好,但他从来没有在学校获过美术一等奖,原因评奖的不懂得欣赏他的作品。我的出现,他深刻地体会到了:多一个朋友,会少一分孤独!有时觉得学习 VC 的朋友是英雄(但我不是英雄,因为我学 VC 多年来无甚突破),是值得尊敬的人物,大家交流一下,纠正一下自己的错误,真是一种福份 ……
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值