一个极小的MFC程序(16行) (附MFC入门心得)

刚开始从C++向MFC过渡的时候,发现一个HELLO就要上百行语句,面对Wizard一头N个大,一打开电脑就问自己,这东西是从哪开始的啊,WinMain哪去了,我靠了,上百行写个HELLO还找不到WinMain,找不到WinMain我从哪开始学啊我?
    再后来发现不光是WinMain,注册窗口类,消息循环,窗口函数,CreateWindow通通没有,想改个窗口标题都没地找,在一大堆书和光盘中爬行了几个月后突然发现一个手工写的纯MFC程序就16行代码,可以正常编译运行,就拿出C++编程的那点底子,不就16行吗,一天一行半个月我也通了.果然经过一翻..啊..再结合以前看过的书和光盘,今天终于大功告成,就把这个小程序给大家贴出来,另附心得,我想对新人应该有点帮助,发现错误也请行家们帮忙指出,深表谢意。如果你也是刚开始学MFC的,想互相交流的加我QQ41184836

注释:   本程序书写方法相当不准确,可以说十分恶劣,只是为初学理解方便
    第一步:VC6.0启动->菜单栏[文件]->新建->在工程项选择Win32 Application,工程名称: text 点击确定进入下一页面 -> 选择一个空工程 ->点击确定完成.
    第二步:进入VC++环境界面后->菜单栏[工程]->设置(Alt+F7)->选择 常规 项 ->右边第一个下拉列表框    原来默认值是"不使用MFC",把它改为"使用MFC作为静态链接库"->点确定完成
    第三步:菜单栏[文件]->新建->在文件项选择C++ source; 文件名:text ->点确定
    第四步:把下面的代码粘到新建的C++源文件中,就可以编译执行了.

****************代码如下***********************************
//text.cpp

#include <afxwin.h>

class CMyWinApp : public CWinApp
{
public:
    BOOL InitInstance();
};

class CMyFrameWnd : public CFrameWnd
{
public:
    CMyFrameWnd();   
};

CMyWinApp theApp;

BOOL CMyWinApp::InitInstance()
{
    m_pMainWnd = new CMyFrameWnd();
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();

    return TRUE;
}

CMyFrameWnd::CMyFrameWnd()
{
    Create(NULL, "演示窗口");
}
******************************代码结束*****************************

***************************心得如下********************************

以下是对上面那个16行代码小程序的逐行解释,也是一个MFC程序启动,运行,结束的全过程.

#include <afxwin.h>
    //调用MFC类库的头文件,所有MFC程序都要用到这个头文件。

两个派生类的声明就不说了,下面是正文:

CMyWinApp theApp;
    MSDN对基于MFC类库的编程规定:每个程序必要有一个而且仅有一个全局的
         从CWinApp类的派生的类产生的对象。
       

    ①CWinApp::CWinApp
        既然从类中产生了一个对象,自然其构造函数马上就执行了,
                  应该是CMyWinApp::CMyWinApp才对但因为我们在定义CMyWinApp的时
                  候没有重写构造函数,所以执行其父类的构造函数WinApp::CWinApp,
        这个函数在MFC类库早已经写好了;因为CWinApp本身就是MFC库中
                  的一个类;这个函数运行的结果是CWinApp中的成员变量被初始化,
                  所有变量在内存中有了地址,并有初值。
        因为CMyWinApp是CWinApp的子类,这一动作也就使theApp对象的成
                  员变量有了初值,一切也因此开始了;
        theApp这个对象在面对对象的程序的设计中表示:整个应用程序,也
                  就是说整个程序也是一个对象。
回复人:黯月精灵 回复时间:2005-10-15 22:07:00
WinMain(…)
    在程序代码中看不到这一行,在我开始学MFC的时候,曾无数次问过自己这个
         问题,WinMain哪去了?它怎么知道什么时候运行啊?
    郁闷了好一阵子,最后看MSND的回答非常干脆,我靠了,答案如下:就一句话
    在基于MFC类库的编程中:在用户自定义的全局对象(变量)和theApp这个全
         局对象产生后MFC将自动提供并运行一个WinMain函数
    哪来的?是MFC事先写好了放在那的,什么时候运行?全局对象定义完了就运
         行,…
    这个WinMain函数是MFC已经写好了的,为便于理解内容简写如下
   
    ********MFC自动提供的WinMain程序代码简略如下***************
    int CALLBACK WinMain(…)
    {
        CWinApp* pApp = AfxGetApp();

        AfxWinInit(…);

        pApp->InitApplication();

        pApp->InitInstance();

        pApp->Run();

        return …
    }   
    *************************代码结束*************************

    ***********WinMain程序代码说明如下***********************

int CALLBACK WinMain(…) //实际MFC原文中不是这么写的,是用宏写的,
                           //把宏打开还是这个德行;
{
    CWinApp* pApp = AfxGetApp(); //MFC类库中的一个全局函数,功能是取得theApp
                                  //指针,只要有了指针我们就可以利用theApp中
                                  //大量的现成的成员函数为所欲为了;

    ②AfxWinInit(…);            //MFC的全局函数,其功能是初始化MFC内部操
                                  //作,这里的初始化似乎和AFX的关系很大;
                  //其实到底这个函数初始了什么东西我也搞不
                                  //懂,也不想搞懂,太难了但有意思的是这个函
                                  //数是每个MFC程序必须要走的一步;
                  //注释:MFC中不仅有类,还有大量的全局函数,
                                  //以Afx开头的都是这也就是说用MFC编程并不是
                                 //纯粹的面向对象的程序设计,
                                 //书上又说这种混血的设计更灵活

    ③pApp->InitApplication();   //看看得到指针后调用的第一个成员函数是什么,
                                 //因在theApp的类中没有重写这个函数所以最后执
                                 //行的是CWinApp.InitApplication()
                 //在基于MFC的编程中这个函数是不用重写的,直
                                //接用现成的就行了它的作用也是初始化MFC内部操
                                //作,这里的初始化和DOC模板关系很近说深了我也
                                //不知道它初始了什么东西,但也是必走的一步;

    ④pApp->InitInstance();          //这是取得theApp指针后调用的第二个成员函
                                 //数,因为这个函数在代码中重写了所以最后执
                                //行的是自己写的代码CMyWinApp.InitInstance()
                 //这个函数必须是要重写的,因为它的父类WinApp
                                //中的这个函数什么也没写,是空的MFC提供的
                                //WinMain函数调用这个函数目的很单纯,就是让用
                                //户自己创建一个程序主窗口                          //我们的编程也是从这个函数开始写第一行代码
//本程序写的代码是 m_pMainWnd = new CMyFrameWnd();
//有人问m_pMainWnd是哪来的,它是CWinAPP类的成员变量
//在这个程序的第①步,它被初始化了,当然初值为NULL,
//这里我们给它赋新值了.前缀 m_ 表示类的成员变量 p 表示指针
//就是指向本应用程序主窗口的指针
//这里我们 new 的是 CFrameWnd, 表示建立一个简单的窗口
//也可以   new 一个 CDialog,    表示建立一个简单的对话框
//也可以new 一个 CSingleDoctemplate 表示建立一个单文档应用程序(AppWizard)
//也可以new 一个 CMultiDoctemplate 表示建立一个多文档应用程序(AppWizard)
//回到本程序,既然new了CMyFrameWnd(),老规矩执行构造函数
//CMyFrameWnd::CMyFrameWnd()这个函数我们也重写了,代码在上面
//实际上我们自己一共就重写了二个函数,这是最后一个,内容见下一步;
//注意: 从这时已经从WinMain跳出来了,
//转而执行CFrameWnd类的成员函数Create

    ⑤Create(NULL, "演示窗口");     //为什么要偏偏要写这行代码呢,
                                    //就不能写别的?答案还是在MSDN里找,
                     //MSDN上是这样写的:要产生一个CFrameWnd类
                                      的实体分二步走
//第一步从类中定义一个对象,这步我们做了:m_pMainWnd = new CMyFrameWnd();
//第二步利用Create函数创建一个与这个对象关联的窗口,就这么简单,
//创建一个与这个对象关联的窗口有三种做法:
//㈠Create(NULL, "演示窗口");
//㈡CreateEx(NULL,NULL,"演示窗口",WS_OVERLAPPEDWINDOW,rectDefault,NULL,NULL);
//㈢::CreateWindowEx(……);
//第三种又回归SDK了,我们这里不讨论,这里只讨论前二种
//其实和大家想的一样,前二种方法最后调用的一定还是::CreateWindowEx(……)
//继续,这行代码实际上调用的是CFrameWnd::Create
//这个函数也是MFC早已写好了的,我们只是调用了它,并给它传递了参数       
//这一行是本程序最复杂的一行代码,
//传说中的注册窗口类和窗口函数都是在这一行完成的
//因为这个类成员函数又调用了(也可以说封装)了好多API函数,
//打开MFC的关于这个成员函数的定义,发现它最后又调用了另一个成员CWnd::CreateEx
//你是不是要问你在哪打开这个函数定义部分的,我没那本事,书上写的,呵呵
//另外千万不要试着重写Create函数,后果很严重啊,用现成的就行
//你是不是又要问不改写我怎么体现自己想要的东西啊,别急,Create函数有8个参数
//后6个参数有默认值,本程序省略了,你可以给它传递各式的参数满足你的要求
//我们继续,通过这个函数的调用我们的第⑥步也就出来了

回复人:黯月精灵 回复时间:2005-10-15 22:08:00
⑥CreateEx(NULL,NULL,"演示窗口",
               WS_OVERLAPPEDWINDOW,rectDefault,NULL,NULL,NULL);
                         

//恩,这是CWnd类的一个成员函数
//大家可以试一下,把上面那个16行小程序的最后一行Create(NULL,"演示窗口")删掉
//用这行得代替,这个程序一样能编译成功,而且结果一样
//CreateEx 和 Create这两个函数参数个数一样,功能也一样,其实这里MFC想的很周到
//如果你不想传递窗口扩展样式这个参数你就用Create,省事,因为写2个参数就可以了
//如果你想传递窗口扩展样式这个参数直接用这个函数也行,
//当然用Create也是可以的,只不过参数就不能用默认值了
//这个差别和CreateWindow与CreateWindowEx的差别有点相像
//其实关于CreateEx 和 Create这两个函数你只要查一下MSDN就全明白了
//下面把CreateEx函数在MFC定义部分打开,天也就亮了
                         

**************CreateEx函数在MFC中的定义部分简写********************

    BOOL CWnd::CreateEx( DWORD dwExStyle, LPCTSTR lpszClassName,
                         LPCTSTR lpszWindowName,DWORD dwStyle, int x,
                         int y, int nWidth, int nHeight,HWND hWndParent,
                         HMENU nIDorHMenu, LPVOID lpParam)

    //从参数个数上来说就比CreateWindowEx少一个,
    //少的那个参数下面有一行语句会补上,这样就可以调用CreateWindowEx修成正果了
{
CREATESTRUCT cs;              //对初学者来说这个结构体还有和WNDCLASS
                              //这两个结构体太重要了,入门必修

cs.dwExStyle = dwExStyle;
cs.lpszClassName = lpszClassName;
cs.lpszWindowName = lpszWindowName;
cs.dwStyle = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hWndParent = hWndParent;
cs.nIDorHMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle(); //少的那个参数就是这个:程序实例的句柄,
                                       //为什么少,MFC类封装了这个句柄
                  //现在要取回这个句柄就得依靠全局函数来完成,
cs.lpParam = lpParam;                 //到此为止,我们熟悉的API函数
                                      //CreateWindowEx的12个参数一个也不少了

PreCreateWindow(cs);                   //这一行最复杂,一会说

HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClassName,
                             cs.lpszWindowName, cs.dwStyle, cs.x,
                             cs.y, cs.cx, cs.cy, cs.hWndParent,
                             cs.nIDorHMenu,cs.hInstance,cs.lpParam)
                 
                                       //最后一步,窗口终于产生了
}

*************代码结束,以下为这个函数的说明部分*********************

//说句废话,我们仍然在第⑥步讨论
//这个函数展开后主要做了五件事
//㈠定义了一个CREATESTRUCT型的结构体并把CreateEx函数接收的参数放到这个结构体中
//㈡额外地取得了实例句柄这个数据也放到这个结构体中
//㈢把这个结构体用别名传参的方式传给函数PreCreateWindow(cs)
//㈣这个结构体被PreCreateWindow函数处理后成为API函数CreateWindowEx的12个参数
//㈤调用API函数CreateWindowEx产生窗口
//多说一句,如果你还不太清楚别名传参,请不要急于接触MFC,很危险,
//不把(虚函数)和(传参)这两个知识点搞透就学习MFC的结果只有一个
//那就是你会最终会选择放弃MFC,实际上MFC一点也没有扩展C++,看不懂的都是宏
//下面进行第⑦步,    PreCreateWindow(cs)

回复人:黯月精灵 回复时间:2005-10-15 22:08:00
⑦PreCreateWindow(cs)        

//看看这个函数的声明:BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
//从这可以看出它接收的是一个别名参数,别名传参太霸道了
//首先说PreCreateWindow这个成员函数是CWnd类的一个虚函数
//包括CWnd类自己,每一个CWnd的派生类对这个函数都重新做了定义
//实际上我们自己从CWnd类子类中派出自己的类时也要重写这个函数,当然也可以不重写
//当这个函数PreCreateWindow(cs)接收到结构体cs以后,会自动检测cs.lpszClassName
//cs.lpszClassName这个数据表示窗口类的名称,本程序传递的为空,就是没有窗口类
//当这个函数检测到cs.lpszClassName为空时,也就是当它发现程序没有窗口类时
//它会自动真充一个WNDCLASS结构,并注册之,那它根据什么提供什么样的窗口类呢
//前面说了每个CWnd类及其子类都有自己定义的PreCreateWindow函数
//那就是说CWnd是一种,CFrameWnd又是一种,还有:CMDIFrameWnd,CMDIChildWnd,CView
//这5种类都有自己默认的窗口类,如果你未手动提供窗口类,它就用默认的
//另外说一下这个窗口类,很多书上都是这么写,这对初学者是一种考验
//窗口类不是MFC类库中的类,是一个数据结构WNDCLASS的名称,WNDCLASS就是窗口类
//个人以为WNDCLASS应该写成(窗口"类")而不是(窗口类)       
//继续: 那么本程序调用的PreCreateWindow是CWnd的,还是CFrameWnd的成员函数呢?
//要知道PreCreateWindow在CWnd和CFrameWnd这两个类中都有定义,
//而且是CWnd::CreateEx这个函数启动的PreCreateWindow(cs),那是CWnd类????
//不是,正确答案是调用CFrameWnd类PreCreateWindow(cs),为什么?
//对象决定this指针,只有已经存在的对象才有this指针存在
//本程序已经存在的对象是m_pMainWnd = new CMyFrameWnd(),
//this指针指向CMyFrameWnd,但本程序CMyFrameWnd没有重新定义PreCreateWindow
//所以最后执行CMyFrameWnd的父类CFrameWnd的成员函数
//那么这个默认的窗口类也就是CFrameWnd提供的               
//到这步了,你可能还问,窗口类是找到了,那窗口函数在哪,在哪处理消息啊?
//恩,是个问题,在SDK编程中,窗口函数的指针是在窗口类指定的,
//然后再编写窗口函数处理消息,当然这个窗口函数的名字要和窗口类中指定的一致.
//在本程序那就看看CFrameWnd提供的默认的窗口类是怎么写的?
//WNDCLASS wc.lpfnWndProc = DefWindowProc;
//DefWindowProc 熟悉吧,SDK编程窗口函数最后一行都是这个
//就是说它提供的窗口函数就把是全部消息都送去系统默认处理
//恩,不错,窗口函数也有了,不过你可能又要问
//我编程不能全指望默认处理啊,那菜单消息,按钮鼠标键盘自定义消息怎么办?
//用MFC提供的功能强大的消息图 MESSAGE_MAP               
//消息图这东西的原理十分复杂,在我看来能编出这东西的是神一级的人物
//看过N遍,至今没搞懂但有一点,原理复杂,用起来却十分简单
//想让哪个窗口能处理消息,就在这个窗口声明部分加一句DECLARE_MESSAGE_MAP()
//在这个窗口的定义部分加下面二句
//BEGIN_MESSAGE_MAP(本身类名, 其父类名)
//END_MESSAGE_MAP()   
//再在这两句中间把所有要处理的消息声明一下,就可以编写声明完的消息处理函数了//你可能又要问了,处理自定义消息是有了,但有时要改变一些系统消息的处理
//没有窗口函数那还不是什么也做不了?
//可以这样,有一个强大的API函数GetWindowLong,它可以改变系统原来指定的窗口函数
//之后你自己再自定义一个窗口函数(呵,又回归SDK了),之后你想做什么就随你了
//到目前为止WinMain,注册窗口类,窗口函数,CreateWindow全找到了
//就差一个消息循环最后说.                       
//还有一问题就是这个小程序没写几句代码窗口自己就出来了,那么
//在不重写本程序的前提下如何修改窗口样式,窗口标题,窗口大小和添加菜单
//很简单,重写CMyFrameWnd这个类的虚函数PreCreateWindow
//重写归重写,重写时还是先调用其父类CFrameWnd的PreCreateWindow,
//不然的话就啥都没了,然后重新给cs.dwStyle cs.lpszWindowName等赋值
//想改哪个就给哪个变量赋新值,至于为什么这样能行,(别名传参)太霸道了
//稍复杂一点的是改变窗口图标,指针形状和窗口背景
//因为这三个变量是在窗口类中指定的,办法如下:
//还是要重写CMyFrameWnd这个类的虚函数PreCreateWindow
//不过这回要改变系统默认指定的窗口类,就是这个cs.lpszClassName
//要先自己声明并填充一个WNDCLASS,要填满,一个不能少
//特别注意窗口函数项wndcls.lpfnWndProc=::DefWindowProc(要加上::)
//再给自己定义好的WNDCLASS起一个新名字,并把它赋给cs.lpszClassName
//因为是你自己定义的WNDCLASS,想改成啥样就能改成啥样(又是回到SDK了)
//之所以说这个笨方法是因为好理解,在实际要改的时候有一个API函数,一行就搞定了
扯的太远了,第⑧步是什么?

    ⑧::CreateWindowEx(…)          //那个MFC提供默认窗口类的窗口诞生了
                 //窗口创建以后这个API函数并没有再调用其它函
                                //数,就回到CMyWinApp::InitInstance()

    ⑨m_pMainWnd->ShowWindow(m_nCmdShow);
      m_pMainWnd->UpdateWindow();

                   //到此CMyWinApp::InitInstance()函数也执行完毕,
                             //回到WinMain函数

    ⑩pApp->Run();              //这就是系统提供的消息循环,不停的等待消息,
                                 //翻译消息,再把消息发送到窗口函数
//直到收到WM_QUIT这个消息时,循环结束,整个程序也结束了
//另外说一句,这个Run()同时还提供了时间片函数

win32編程基础

尽管Windows应用程序千变万化,令人眼花缭乱,但,消息机制和窗口过程却始终它们的基础,掌握了这两项技术,也就相当于把握住了问题的关键。

  如果你以前是C程序员或是MFC的忠实用户,只要你学习过C语言的语法,自己亲手编过一些简短的C程序,理解以下的Win32编程基础也不是一件困难的事。

  一个最简单的Win32程序

  在以前的C语言编程中,一个最简单的程序可以只有两行。

void main(void)

{ printf "Hello World!"; }

  而要实现同样功能的Windows程序却最少也要写几十行,这并不是说明Windows应用程序效率低下,难于掌握,只是说明程序在Windows环境下有更丰富的内涵。Windows程序的效率其实不低,在所有的Windows应用程序中,都有一个程序初始化的过程,这得用上几十条语句,这段初始化的代码对于任何Windows应用程序而言,都是大同小异的。下面以一个实现最简单功能的程序EasyWin为例,说明Windows程序的基本框架。

  打开Visual C++ 6.0。

  选择File菜单的New,在出现的对话框中,选择Projects栏目(新建工程),并点取其下的Win32 Application项,表示使用Win32环境创建应用程序。先在Locatin(路径)中填入“c:\”,然后在Project Name(项目名称)中填入“EasyWin”,其它按照缺省设置)。单击OK按钮。

  再次选择File菜单的New,在出现的对话框中,选择Files栏目(新建文件),并点取其下的C++ Source File项,表示新建一个C++源文件。在右边的File栏中输入“EasyWin”,最后确定让Add to project检查框打上勾 )。单击OK按钮。

  在EasyWin.cpp文件中输入以下源程序代码。  
 
//*******************************************************************
// 工程:easywin
// 文件:easywin.cpp
// 内容:一个基本的Win32程序//*******************************************************************

#include <windows.h>

#include <windowsx.h>

//函数声明

BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

//*******************************************************************

//函数:WinMain()

//功能:Win32应用程序入口函数。创建主窗口,处理消息循环

//*******************************************************************

int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄

HINSTANCE hPrevInstance, //前一个实例句柄

LPSTR lpCmdLine, //命令行字符

int nCmdShow) //窗口显示方式

{

MSG msg;

//创建主窗口

if ( !InitWindow( hInstance, nCmdShow ) )

return FALSE;

//进入消息循环:

//从该应用程序的消息队列中检取消息,送到消息处理过程,

//当检取到WM_QUIT消息时,退出消息循环。

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

//程序结束

return msg.wParam;

}

//******************************************************************

//函数:InitWindow()

//功能:创建窗口。

//******************************************************************

static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )

{

HWND hwnd; //窗口句柄

WNDCLASS wc; //窗口类结构

//填充窗口类结构

wc.style = CS_VREDRAW | CS_HREDRAW;

wc.lpfnWndProc = (WNDPROC)WinProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );

wc.hCursor = LoadCursor( NULL, IDC_ARROW );

wc.hbrBackground = GetStockObject(WHITE_BRUSH);

wc.lpszMenuName = NULL;

wc.lpszClassName = "EasyWin";

//注册窗口类

RegisterClass( &wc );

 

//创建主窗口

hwnd = CreateWindow(

"EasyWin", //窗口类名称

"一个基本的Win32程序", //窗口标题

WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型

100, //窗口位置的x坐标

100, //窗口位置的y坐标

400, //窗口的宽度

300, //窗口的高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //应用程序实例句柄

NULL ); //窗口创建数据指针

if( !hwnd ) return FALSE;

//显示并更新窗口

ShowWindow( hwnd, nCmdShow );

UpdateWindow( hwnd );

return TRUE;

}

//******************************************************************

//函数:WinProc()

//功能:处理主窗口消息

//******************************************************************

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )

{

switch( message )

{

case WM_KEYDOWN://击键消息

switch( wParam )

{

case VK_ESCAPE:

MessageBox(hWnd,"ESC键按下了!","Keyboard",MB_OK);

break;

}

break;

case WM_RBUTTONDOWN://鼠标消息

{

MessageBox(hWnd,"鼠标右键按下了!","Mouse",MB_OK);

break;

}

case WM_PAINT://窗口重画消息

{

char hello[]="你好,我是EasyWin !";

HDC hdc;

PAINTSTRUCT ps;

hdc=BeginPaint( hWnd,&ps ); //取得设备环境句柄

SetTextColor(hdc, RGB(0,0,255)); //设置文字颜色

TextOut( hdc, 20, 10, hello, strlen(hello) );//输出文字

EndPaint( hWnd, &ps ); //释放资源

break;

}

case WM_DESTROY://退出消息

PostQuitMessage( 0 );//调用退出函数

break;

}

//调用缺省消息处理过程

return DefWindowProc(hWnd, message, wParam, lParam);

}

  程序输入完毕,即可编译执行。在窗口中击鼠标键或按ESC键时,会弹出一个对话框以表示你的操作。

  其实,这个程序可以看成是所有Win32应用程序的框架,在以后所有的程序中,你会发现它们都是在这个程序的基础之上再添加代码。

  WinMain()函数
 
  WinMain()函数是应用程序开始执行时的入口点,通常也是应用程序结束任务退出时的出口点。它与DOS程序的main()函数起同样的作用,有一点不同的是,WinMain()函数必须带有四个参数,它们是系统传递给它的。WinMain()函数的原型如下:

int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄

HINSTANCE hPrevInstance, //前一个实例句柄

LPSTR lpCmdLine, //命令行字符

int nCmdShow) //窗口显示方式

  第一个参数hInstance,是标识该应用程序当前的实例的句柄。它是HINSTANCE类型,HINSTANCE是Handle of Instance的缩写,表示实例的句柄。hInstance是一个很关键的数据,它唯一的代表该应用程序,在后面初始化程序主窗口的过程中需要用到这个参数。

  这里有两个概念,一个是实例,一个是句柄。实例代表的是应用程序执行的整个过程和方法,一个应用程序如果没有被执行,只是存在于磁盘上,那么就说它是没有被实例化的;只要一执行,则说该程序的一个实例在运行。句柄,顾名思义,指的是一个对象的把柄。在Windows中,有各种各样的句柄,它们都是32位的指针变量,用来指向该对象所占据的内存区。句柄的使用,可以极大的方便Windows管理其内存中的各种对象。

  第二个参数是hPrevInstance,它是用来标识该应用程序的前一个实例句柄。对于基于Win32的应用程序来说,这个参数总是NULL。这是因为在Win95操作系统中,应用程序的每个实例都有各自独立的地址空间,即使同一个应用程序被执行了两次,在内存中也会为它们的每一个实例分配新的内存空间,所以一个应用程序被执行后,不会有前一个实例存在的可能。也就是说,hPrevInstance这个参数是完全没有必要的,只是为了提供与16位Windows的应用程序形式上的兼容性,才保留了这个参数。在以前的16位Windows环境下(如Windows3.2),hPrevInstance用来标识与hInstance相关的应用程序的前一个句柄。

  第三个参数是lpCmdLine,是指向应用程序命令行参数字符串的指针。如在Win95的“开始”菜单中单击“运行”,输入“easywin hello”,则此参数指向的字符串为“hello”。

  最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。这个整数值可以是SW_SHOW、SW_HIDE、SW_SHOWMAXIMIZED、SW_SHOWMINIMIZED等,关于这些值的含义,将在下一节说明。

  注册窗口类

  一个应用程序可以有许多窗口,但只有一个是主窗口,它是与该应用程序的实例句柄唯一关联的。上面的例程中,创建主窗口的函数是InitWindow()。

  通常要对填充一个窗口类结构WNDCLASS,然后调用RegisterClass()对该窗口类进行注册。每个窗口都有一些基本的属性,如窗口边框、窗口标题栏文字、窗口大小和位置、鼠标、背景色、处理窗口消息函数的名称等等。注册的过程也就是将这些属性告诉系统,然后再调用CreateWindow()函数创建出窗口。这也就象你去裁缝店订做一件衣服,先要告诉店老板你的身材尺寸、布料颜色、以及你想要的款式,然后他才能为你做出一件让你满意的衣服。

  在VC的帮助中,可以看到WNDCLASS结构是这样定义的:

typedef struct _WNDCLASS {

UINT style; //窗口的风格*

WNDPROC lpfnWndProc; //指定窗口的消息处理函数的远指针*

int cbClsExtra; //指定分配给窗口类结构之后的额外字节数*

int cbWndExtra; //指定分配给窗口实例之后的额外字节数

HANDLE hInstance; //指定窗口过程所对应的实例句柄*

HICON hIcon; //指定窗口的图标

HCURSOR hCursor; //指定窗口的鼠标

HBRUSH hbrBackground; //指定窗口的背景画刷

LPCTSTR lpszMenuName; //窗口的菜单资源名称

LPCTSTR lpszClassName; //该窗口类的名称*

} WNDCLASS;

  在Win95和WinNT的具有新界面特性的系统中,为了支持新的窗口界面特性,还有一种扩展的窗口类型WNDCLASSEX,它的定义如下:

typedef struct _WNDCLASSEX {

UINT cbSize; //指定WNDCLASSEX结构的大小

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HANDLE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCTSTR lpszMenuName;

LPCTSTR lpszClassName;

HICON hIconSm; //窗口的小图标

} WNDCLASSEX;

  WNDCLASS和WNDCLASSEX这两个结构基本上是一致的,只是WNDCLASSEX结构中多了cbSize和hIconSm这两个成员。WNDCLASS结构的各成员中,其注释后打了星号的表示该项应特别注意。

  WNDCLASS结构的第一个成员style表示窗口类的风格,它往往是由一些基本的风格通过位的“或”操作(操作符位“|”)组合而成。下表列出了一些常用的基本窗口风格:

风格 含义
CS_HREDRAW 如果窗口客户区宽度发生改变,重绘整个窗口
CS_VREDRAW 如果窗口客户区高度发生改变,重绘整个窗口
CS_DBLCLKS 能感受用户在窗口中的双击消息
CS_NOCLOSE 禁用系统菜单中的“关闭”命令
CS_OWNDC 为该窗口类的各窗口分配各自独立的设备环境
CS_CLASSDC 为该窗口类的各窗口分配一个共享的设备环境
CS_PARENTDC 指定子窗口继承其父窗口的设备环境
CS_SAVEBITS 把被窗口遮掩的屏幕图象部分作为位图保存起来。当该窗口被移动时,Windows使用被保存的位图来重建屏幕图象

  在EasyWin应用程序��,是按如下方式对WNDCLASS结构进行填充和注册的:

wc.style = CS_VREDRAW | CS_HREDRAW;

wc.lpfnWndProc = (WNDPROC)WinProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );

wc.hCursor = LoadCursor( NULL, IDC_ARROW );

wc.hbrBackground = GetStockObject(WHITE_BRUSH);

wc.lpszMenuName = NULL;

wc.lpszClassName = "EasyWin";

  可以看到,wc.style被设为CS_VREDRAW | CS_HREDRAW,表示只要窗口的高度或宽度发生变化,都会重画整个窗口。

  第二个成员lpfnWndProc的值为(WNDPROC)WinProc。表明该窗口类的消息处理函数是WinProc()函数。这里,要指定窗口的消息处理函数的远指针,输入消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成WNDPROC型。

  接下来的cbClsExtra和wc.cbWndExtra在大多数情况下都会设为0。

  然后的hInstance成员,给它的值是由WinMain()传来的应用程序的实例句柄,表明该窗口与该实例是相关联的。事实上,只要是注册窗口类,该成员的值始终是该程序的实例句柄,你应该象背书一样记住它。

  下面的hIcon,是让你给这个窗口指定一个图标,调用 LoadIcon( hInstance, IDI_APPLICATION ),可以调用系统内部预先定义好的标志符为IDC_APPLICATION的图标作为该窗口的图标。

  同样,调用LoadCursor( NULL, IDC_ARROW )为该窗口调用系统内部预先定义好的箭头型鼠标。

  hbrBackground成员用来定义窗口的背景画刷颜色,也就是该窗口的背景色。调用GetStockObject(WHITE_BRUSH)可以获得系统内部预先定义好的白色画刷作为窗口的背景色。

  上面的LoadIcon()、LoadCursor()、GetStockObject()都是Windows的API函数,它们的用法可以参看VC的帮助,这里就不多介绍了。

  lpszMenuName成员的值我们给它NULL,表示该窗口将没有菜单。如果你想让你的窗口拥有菜单,就把lpszMenuName成员赋值为标志菜单资源的字符串。

  WNDCLASS结构的最后一个成员lpszClassName是让你给这个窗口类起一个唯一的名称,因为Windows操作系统中有许许多多的窗口类,必须用一个独一无二的名称来代表它们。通常,你可以用你的程序名来命名这个窗口类的名称。这个名称将在创建窗口的CreateWindow()函数中用到。

  填充完毕后,对于WNDCLASS结构,调用RegisterClass()函数进行注册;对于WNDCLASSEX结构,调用RegisterClassEx()函数进行注册,它们的原型分别如下:

ATOM RegisterClass( CONST WNDCLASS *lpWndClass );

ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx );

  该函数如调用成功,则返回一个非0值,表明系统中已经注册了一个名为EasyWin的窗口类。如果失败,则返回0。

  创建窗口

  当窗口类注册完毕之后,并不会有窗口显示出来,因为注册的过程仅仅是为创建窗口所做的准备工作。实际创建一个窗口的是通过调用CreateWindow()函数完成的。窗口类中已经预先定义了窗口的一般属性,而CreateWindow()中的参数可以进一步指定一个窗口的更具体的属性,在EasyWin程序中,是如下调用CreateWindow()函数来创建窗口的:

hwnd = CreateWindow(

"EasyWin", //创建窗口所用的窗口类的名称*

"一个基本的Win32程序", //窗口标题

WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型*

100, //窗口位置的x坐标

100, //窗口位置的y坐标

400, //窗口的宽度

300, //窗口的高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //应用程序实例句柄*

NULL ); //一般都为NULL

  CreateWindow()函数的参数的含义在上面的注释中已有介绍,注释后打了星号标记的参数应该着重注意,其它的参数都很简单,不多做介绍,可参看VC的帮助。

  第一个参数是创建该窗口所使用的窗口类的名称,注意这个名称应与前面所注册的窗口类的名称一致。

  第三个参数为创建的窗口的风格,下表列出了常用的窗口风格:

风格 含义
WS_OVERLAPPEDWINDOW 创建一个层叠式窗口,有边框、标题栏、系统菜单、最大最小化按钮,是以下几种风格的集合:WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX
WS_POPUPWINDOW 创建一个弹出式窗口,是以下几种风格的集合: WS_BORDER,WS_POPUP,WS_SYSMENU。WS_CAPTION与WS_POPUPWINDOW风格必须一起使用才能使窗口菜单可见
WS_OVERLAPPED 创建一个层叠式窗口,它有标题栏和边框,与WS_TILED风格一样
WS_POPUP 该窗口为弹出式窗口,不能与WS_CHILD同时使用
WS_BORDER 窗口有单线边框
WS_CAPTION 窗口有标题栏
WS_CHILD 该窗口为子窗口,不能与WS_POPUP同时使用
WS_DISABLED 该窗口为无效,即对用户操作不产生任何反应
WS_HSCROLL 窗口有水平滚动条
WS_ICONIC 窗口初始化为最小化
WS_MAXIMIZE 窗口初始化为最大化
WS_MAXIMIZEBOX 窗口有最大化按钮
WS_MINIMIZE 与WS_MAXIMIZE一样
WS_MINIMIZEBOX 窗口有最小化按钮
WS_SIZEBOX 边框可进行大小控制的窗口
WS_SYSMENU 创建一个有系统菜单的窗口,必须与WS_CAPTION风格同时使用
WS_THICKFRAME 创建一个大小可控制的窗口,与WS_SIZEBOX 风格一样.
WS_TILED 创建一个层叠式窗口,有标题栏
WS_VISIBLE 窗口为可见
WS_VSCROLL 窗口有垂直滚动条

  程序中使用了WS_OVERLAPPEDWINDOW标志,它是创建一个普通窗口常用的标志。而在DirectX编程中,我们常用的是WS_POPUP,用这个标志创建的窗口没有标题栏和系统菜单,如果设定窗口为最大化,客户区可以占满整个屏幕,以满足DirectX编程的需要。

  CreateWindow()函数后面的参数中,仍用到了该应用程序的实例句柄hInstance。

  如果窗口创建成功,返回值是新窗口的句柄,否则返回NULL。

  显示和更新窗口

  窗口创建后,并不会在屏幕上显示出来,要真正把窗口显示在屏幕上,还得使用ShowWindow()函数,其原型如下:

BOOL ShowWindow( HWND hWnd, int nCmdShow );

  参数hWnd指定要显示得窗口的句柄,nCmdShow表示窗口的显示方式,这里指定为从WinMain()函数的nCmdShow所传递而来的值。

  由于ShowWindow()函数的执行优先级不高,所以当系统正忙着执行其它的任务时,窗口不会立即显示出来,此时,调用UpdateWindow()函数以可以立即显示窗口。其函数原型如下:

BOOL UpdateWindow( HWND hWnd );

  消息循环

  在Win32编程中,消息循环是相当重要的一个概念,看似很难,但是使用起来却是非常简单。在WinMain()函数中,调用InitWindow()函数成功的创建了应用程序主窗口之后,就要启动消息循环,其代码如下:

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

  Windows应用程序可以接收以各种形式输入的信息,这包括键盘、鼠标动作 、记时器产生的消息,也可以是其它应用程序发来的消息等等。Windows系统自动监控所有的输入设备,并将其消息放入该应用程序的消息队列中。

  GetMessage()函数则是用来从应用程序的消息队列中按照先进先出的原则将这些消息一个个的取出来,放进一个MSG结构中去。GetMessage()函数原型如下:

BOOL GetMessage(

LPMSG lpMsg, //指向一个MSG结构的指针,用来保存消息

HWND hWnd, //指定哪个窗口的消息将被获取

UINT wMsgFilterMin, //指定获取的主消息值的最小值

UINT wMsgFilterMax //指定获取的主消息值的最大值

);

  GetMessage()将获取的消息复制到一个MSG结构中。如果队列中没有任何消息,GetMessage()函数将一直空闲直到队列中又有消息时再返回。如果队列中已有消息,它将取出一个后返回。MSG结构包含了一条Windows消息的完整信息,其定义如下:

typedef struct tagMSG {

HWND hwnd; //接收消息的窗口句柄

UINT message; //主消息值

WPARAM wParam; //副消息值,其具体含义依赖于主消息值

LPARAM lParam; //副消息值,其具体含义依赖于主消息值

DWORD time; //消息被投递的时间

POINT pt; //鼠标的位置

} MSG;

  该结构中的主消息表明了消息的类型,例如是键盘消息还是鼠标消息等,副消息的含义则依赖于主消息值,例如:如果主消息是键盘消息,那么副消息中则存储了是键盘的哪个具体键的信息。

  GetMessage()函数还可以过滤消息,它的第二个参数是用来指定从哪个窗口的消息队列中获取消息,其它窗口的消息将被过滤掉。如果该参数为NULL,则GetMessage()从该应用程序线程的所有窗口的消息队列中获取消息。

  第三个和第四个参数是用来过滤MSG结构中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息将被过滤掉。如果这两个参数为0,则表示接收所有消息。

  当且仅当GetMessage()函数在获取到WM_QUIT消息后,将返回0值,于是程序退出消息循环。

  TranslateMessage()函数的作用是把虚拟键消息转换到字符消息,以满足键盘输入的需要。DispatchMessage()函数所完成的工作是把当前的消息发送到对应的窗口过程中去。

  开启消息循环其实是很简单的一个步骤,几乎所有的程序都是按照EasyWin的这个方法。你完全不必去深究这些函数的作用,只是简单的照抄就可以了。
 

  消息处理函数

  消息处理函数又叫窗口过程,在这个函数中,不同的消息将用switch语句分配到不同的处理程序中去。Windows的消息处理函数都有一个确定的样式,即这种函数的参数个数和类型以及其返回值的类型都有明确的规定。在VC的说明书中,消息处理函数的原型是这样定义的:

LRESULT CALLBACK WindowProc(

HWND hwnd, //接收消息窗口的句柄

UINT uMsg, //主消息值

WPARAM wParam, //副消息值

LPARAM lParam //副消息值

);

  如果你的程序中还有其它的消息处理函数,也都必须按照上面的这个样式来定义,但函数名称可以随便取。EasyWin中的WinProc()函数就是这样一个典型的消息处理函数。

  消息处理函数的四个参数是由GetMessage()函数从消息队列中获得MSG结构,然后分解后得到的。第二个参数uMsg和MSG结构中的message值是一致的,代表了主消息值。程序中用switch语句来将不同类型的消息分配到不同的处理程序中去。

  WinProc()函数明确的处理了4个消息,分别是WM_KEYDOWN(击键消息)、WM_RBUTTONDOWN(鼠标右键按下消息)、WM_PAINT(窗口重画消息)、WM_DESTROY(销毁窗口消息)。

  值得注意的是,应用程序发送到窗口的消息远远不止以上这几条,象WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等这样频频使用的消息就有几十条。为了减轻编程的负担,Windows的API提供了DefWindowProc()函数来处理这些最常用的消息,调用了这个函数后,这些消息将按照系统默认的方式得到处理。

  因此,在switch_case语句中,只须明确的处理那些有必要进行特别响应的消息,把其余的消息交给DefWindowProc()函数来处理,是一种明智的选择,也是你必须做的一件事。
 
  结束消息循环

  当用户按Alt+F4或单击窗口右上角的退出按钮,系统就向应用程序发送一条WM_DESTROY的消息。在处理此消息时,调用了PostQuitMessage()函数,该函数会给窗口的消息队列中发送一条WM_QUIT的消息。在消息循环中,GetMessage()函数一旦检索到这条消息,就会返回FALSE,从而结束消息循环,随后,程序也结束。

  小结

  本章介绍的是Win32编程的基础知识,在进行DirectX编程之前,掌握它们是十分必要的。

  通过本文的学习,你应该学到以下知识:

   如何创建一个Win32应用程序工程

   用RegisterClass()函数注册一个窗口类,再立即调用CreateWindow()函数创建一个窗口的实例

   设置窗口的类型以及将一个消息处理函数与窗口联系上

   用一固定的模式开启消息循环

   了解消息处理函数的定义规则,如何自己定义一个窗口消息处理函数

   在消息处理函数中,最后必须调用DefWindowProc()函数以处理那些缺省的消息

   调用PostQuitMessage()函数以结束消息循环

转自:http://3576178.blog.163.com/blog/static/121612114200962831231644/

寄存器

不来复制了。直接给百度百科链接吧。

http://baike.baidu.com/view/6159.htm