您的位置首页百科知识

MFC程序入口函数WinMain封装原理分析

MFC程序入口函数WinMain封装原理分析

的有关信息介绍如下:

MFC程序入口函数WinMain封装原理分析

MFC是什么?

MFC是Microsoft Foundation Classes的简称。

MFC是微软公司提供的一个C++语言的类库(class libraries), 封装了Windows API,并且包含一个应用程序框架, 以减少应用程序开发人员的工作量。

但是刚接触MFC程序的时候总是不知道从何下手, 因为我们传统学习的C, C++, java等语言程序都有一个入口函数, 而MFC程序却找不到入口函数, 这是怎么回事呢?

本文就和大家一起通过一个代码例子来分析MFC封装win32应用程序入口函数WinMain的原理。

启动vs2017, 新建一个名为myMfcTest的空白工程(新建--->项目--->Visual C++--->空项目), 如下图所示:

可以看到myMfcTest工程没有源码文件, 接下来, 我们新建一个源文件和头文件tstMfcEntry.h和tstMfcEntry.cpp, 我们的目标是设计一个简单完整MFC程序, 产生一个窗口, 但是这里不能让AppWizard自动生成相关代码!

在文件tstMfcEntry.h中输入以下代码:

#pragma once

#include

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; // 返回

}

};

在文件tstMfcEntry.cpp中输入以下代码:

#include "tstMfcEntry.h"

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

添加了源代码的工程项目如下图所示:

点击"生成"菜单下面的"生成解决方案", 发现没有成功并且产生了一堆的警告和错误, 如下图所示:

分析错误信息, 得出可能是没有设定链接MFC库, 那么怎么在空白工程上来设定链接MFC库呢?

这里分享一个vs项目的配置解决通用方法: 项目配置比较法

按下面步骤来:

(1).启动vs2017, 新建立一个mfc对护框项目, 查看其配置信息;

(2).比较myMfcTest工程配置和mfc对护框工程配置(逐项检查, 尽量一致);

(3).再次生成解决方案直到生成成功.

大致需要配置myMfcTest工程的地方:

(1)."配置属性"--->"常规"--->"项目默认值", 改为"在静态库中使用 MFC"; (2)."配置属性"--->"链接器"--->"系统"--->"子系统", 改为"窗口 (/SUBSYSTEM:WINDOWS)";

再点击"应用"和"确定", 再次生成解决方案生成成功.

具体如下图所示:

从上面可以看到建立一个MFC窗口很容易, 只用两步:

一是从CWinApp派生一个应用程序类(这里是MyApp),

然后建立应用程序对象(theApp), 就可以产生一个自己需要的窗口(即需要什么样就在InitInstance()里创建就行了);

整个程序, 就改写一个InitInstance()函数, 创建一个对象(theApp), 就是一个完整的窗口程序。

MFC黑箱操作帮我们插入了代码,它插入的实际上是每次编写窗口程序必须的通用的代码。每次视窗编程都要写WinMain()函数,都要有注册窗口,产生窗口,消息循环,回调函数……等等.

看一下上面两个类的父子关系(箭头代表派生):

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(),

因为这是程序的入口点, 初始化程序的地方。

这里都是讲解了一些mfc框架的相关类及关系为后面做准备。

下面一起看一段程序代码:

#include class test

{

public:

test(){cout<<"请改变对main()函数的看法!"<

test test1;

void main()

{

}

入口的main()函数表面上什么也不做, 但程序执行了, 为什么?

实际入口函数做了一些我们可以不了解的事情, 最后程序输出了一句话!

这里要注意: 全局对象test1是比main()首先运行。

通过以上代码我们, 程序在入口函数main以前, 还有先要执行的代码。

win下面有两个入口程序: main和WinMain;

如果WinMain()函数也是什么都不做, 但是程序仍然可以运行, 不过, 没有这个入口函数程序会报错, 那么WinMain()函数会放哪个类上面呢, 请看下面程序:

#include class MyApp : public CWinApp{public: BOOL InitInstance() // ②程序入点 { AfxMessageBox("程序依然可以运行!"); return true; }};

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

上面程序代码并没有构造框架,

而程序却可以运行了------弹出了一个对话框;

我们理解如果没有WinMain()函数程序会报错, 这里并没有报错!

上面这样写还是为了直观起见, 其实可以更简洁, 只要写两行程序

#include

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(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类对象的时候, 完成那几行代码。

那么MFC到底是怎么做到的呢, 请看下面的分析?

表面上MFC与SDK编程截然不同,

但实质上MFC只是用类的形式封装了SDK函数, 封装之后,

在WinMain()函数中只需要几行代码, 就可以完成一个窗口程序。

我们也由此知道了应如何去封装应用程序类(CWinApp)和

主框架窗口类(CFrameWnd)。

下面开始设计这两个类:

为了简单起见,忽略这两个类的基类和派生类的编写!

代码如下:

#include 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函数。