MFCwin32程序的解析

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(.c plus plus的缩写)

// 内容:一个基本的Win32程序//*******************************************************************

 

#include

 

#include

 

//函数声明

 

BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );(可以看一看msdn

HINSTANCE 是进程句柄;

HANDLE    是对象句柄;

CWnd      MFC的一个类,所有窗口类从其派生;

HWND      是窗口的句柄。

其实句柄是一个32位的整数,WINDOWS操作系统用来标志一个对象。

我觉得,除了cwnd 是个类,其他都是些标记

微软喜欢将内核对象标识,称为句柄。如进程:HINSTANCE ,文件句柄:HANDLE

窗口句柄HWND,画笔句柄HPEN等等。CWnd是提供窗口处理的一个类,里面有HWND m_hWnd成员,CWnd对象一般和一个窗口句柄绑定,但提供了很多窗口操作,如SetWindowText,GetWindowText,...。)

 

int nCmdShow 一参数,表示显示类型,诸如最大化或最小化)

 

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

//(请看如下在头文件中的宏定义:

typedef long LONG; 

typedef LONG LRESULT;

现在你应该明白LRESULT实际上就是long了吧!

用在函数前仅表示函数返回值的类型而已!

请注意函数定义和函数原型的匹配:))

 

//UINT 就是unsinged int

 

//Win 3.x中,WPARAM16位的,而LPARAM32位的,两者有明显的区别。因为地址通常是32位的,所以LPARAM被用来传递地址,这个习惯在Win32 API中仍然能够看到。在Win32 API中,WPARAMLPARAM都是32位,所以没有什么本质的区别。Windows的消息必须参考帮助文件才能知道具体的含义。如果是你定义的消息,愿意怎么使这两个参数都行。但是习惯上,我们愿意使用LPARAM传递地址,而WPARAM传递其他参数。

 

//它们是用来在WINDOWS下消息之间(或在CallBack函数)传递数据,

简单的说就是32位的数据(也就是一小块的内存空间),我们可以在其中写入各种数值信息

wParam是一个word型的Parameter(参数),lParam是一个long型的参数,用于消息的传递,意义由各消息定义。

 

 

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

 

//函数:WinMain()

 

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

 

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

 

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

//

__cdecl__stdcall__fastcall的区别 

__cdecl

C/C++默认的调用规范,参数从右到左入栈,由调用者负责参数出栈,因此程序代码会比

__stdcall方式稍大

 

__stdcall

win32 API函数使用这种调用方式,参数从右到左入栈,由被调用者负责参数出栈

 

__fastcall

前两个参数传给ECXEDX寄存器,剩下的从右向左入栈,由调用者负责参数出栈

 

//VC中如何调用ASM汇编代码:

http://dwbclz.myetang.com/articles/masmhelp/

 

完全使用汇编语言编写程序是不切实际的,也是根本没有必要的。大概只有两种情况需要用到汇编:要求很高的执行效率或者需要执行系统底层的功能。因此,我们在多数情况下还是要用高级语言的,只是在关键的部分使用汇编语言,这就要求我们必须知道如何将高级语言和汇编语言结合起来。如果使用内联汇编那就简单的多了,只要加入asm关键字就可以了。不过,有些指令是不被内联汇编所支持的,这就使得我们不得不编写真正的汇编程序文件。汇编语言和高级语言的命名规范是不太一样的,必须正确命名汇编文件里的标识符,这样才能够在高级语言中正常调用。下面简要介绍一些常见的命名原则:

为程序段命名

C语言中,代码段都是以_TEXT作为段名的,数据段是用_DATA作为段名的。这条规则在Pascal中也是适用的。下面是一个例子:

    _TEXT segment public use32 'CODE'

 

    _TEXT ends

 

这段程序定义了一个代码段,use32告诉编译器生成32位代码。

为标识符命名

C语言在编译以后,为所有的标识符添加了一条下划线作为前缀。如果你想在汇编语言中引用在C程序中定义的变量或函数,就必须也加上一条下划线。如果你使用的是C++编译器,那么还要注意一点,你一定要使用C链接才能使标识符符合上述命名规范。下面就是一个例子:

//这是在C语言中的定义方法

 

extern "C"

{

    int hello;

}

 

 

;;这是在汇编语言中进行引用

 

    extrn _hello

    ;...其它的代码

    ;............

    mov     _hello,   0

 

函数调用

变量的命名还是比较简单的,为函数命名就要考虑更多的问题了。不仅仅是要添加下划线,还要考虑调用规范,有时甚至要弄清楚各个编译器的命名原则。下名列出一些常用的调用规范(这些规范只适用于C链接):

__cdecl

这种调用规范比较简单,只要在变量名前面加上下划线就可以了。而且,这种调用方式在任何编译器中都是相同的。详细规则如下:

参数从右向左入栈

 

由调用者管理参数出栈

 

添加下划线作为前缀

 

__stdcall

很多Windows API都是使用这种调用方式,它比较节省空间。

参数从右向左入栈

 

由被调用者负责参数出栈

 

添加下划线作为前缀,加@n作为后缀,其中n为参数的字节数。(如果使用C++Builder,则无需添加前后缀,直接使用)

 

__fastcall

这是利用寄存器传输参数的调用方式。

如果使用VC,那么前两个参数传给ECXEDX寄存器,剩下的从右向左入栈;如果使用C++Builder,那么前三个双字依次通过EAXEDXECX寄存器传送,其它的从右向左入栈。

 

由被调用者负责参数出栈

 

添加"@"作为前缀,加"@n"作为后缀,其中n为参数的字节数。(如果使用C++Builder,则无需添加后缀,只需@作为前缀)

 

__msfastcall

这是Inprise公司为了和微软的调用规范兼容而采用的一个特别的关键字。实际上,没有必要在VC中使用__fastcall,以我的个人经验,这种方式在VC中没有得到很好的优化,执行效率反而会降低。

 

在高级语言中使用MASM6.14

我们可以使用一些方便的办法来把汇编语言的编译器和高级语言的开发环境有效地结合起来,每次编译都使用命令行是不明智的。

VC中的使用方法

VC提供了一些支持,可以自动的编译汇编文件,你可以按照以下步骤进行:

在菜单中选择Project | Settings...

选中指定的汇编文件(单击即可)

选中Custom Build

Commands中输入:

如果是DEBUG模式,则输入:

e:/masm32/bin/ml /c /coff /Zi /FoDEBUG/$(InputName).obj $(InputPath)

 

如果是RELEASE模式,则输入:

e:/masm32/bin/ml /c /coff /FoRELEASE/$(InputName).obj $(InputPath)

 

Outputs中输入:

如果是DEBUG模式,则输入:

DEBUG/$(InputName).obj

 

如果是RELEASE模式,则输入:

RELEASE/$(InputName).obj

 

如果你的没有把masm安装在E盘,则要作相应的修改。

相应的参数还有必要再解释一下:

/c表示只编译不链接。/coff表示生成coff格式的目标文件,这是在 VC中使用的文件格式。/Zi表示需要产生符号信息,便于调试。

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

 

LPSTR lpCmdLine, //命令行字符

//我在QA000377 "TCHARCHARLPSTRLPCSTRchar这几个数据类型有何不同"中说过了,LPSTRLPCSTR相当于char *,所以这种类型变量的赋值等同于char *的赋值。如果你不了解char *的赋值,你需要去看看C语言入门的书,比如谭浩强的《C语言程序设计》第9章就介绍了这个问题。下面给出两个例子,一个是直接赋值,另一个是间接的。

    Ex1: LPSTR lpstrMsg = "I'm tired.";

    Ex2: char strMsg[]="I'm tired.";

     LPSTR lpstrMsg = (LPSTR) strMsg;

 

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;//Windows程序的实例句柄,这是由编译器提供的

 

 

wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );//为所有基于这个窗口类建立的窗口设置一个图标

 

wc.hCursor = LoadCursor( NULL, IDC_ARROW );//为所有基于这个窗口类建立的窗口设置一个鼠标

 

wc.hbrBackground = GetStockObject(WHITE_BRUSH);//指定了基于这个窗口类创建的窗口背景颜色,用取得一个白色刷子的句柄来实现

 

wc.lpszMenuName = NULL;

//指定窗口类的菜单,用NULL赋值表示这个程序中没有菜单

wc.lpszClassName = "EasyWin";

//指定窗口类的名称

 

//注册窗口类

 

RegisterClass( &wc );

 

 

 

//创建主窗口

hwnd = CreateWindow (szAppName,    // 窗口类的名称,基于这个窗口类创建窗口

 

                   TEXT ("The Hello Program"), //窗口标题栏的名称

 

                   WS_OVERLAPPEDWINDOW,  // 窗口的风格

 

                   CW_USEDEFAULT,         //窗口位置的X坐标

 

                   CW_USEDEFAULT,         //窗口位置的Y坐标

 

                   CW_USEDEFAULT,         //窗口大小宽

 

                   CW_USEDEFAULT,         //窗口大小高

 

                   NULL,                   // 父窗口的句柄

 

                   NULL,                   // 窗口菜单的句柄

 

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

 

                   NULL) ;        // 创建参数的指针,可以用它访问程序中的数据

 

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;//HDC是设备描述表句柄.a handle of the device context of an object

 

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类型,HINSTANCEHandle of Instance的缩写,表示实例的句柄。hInstance是一个很关键的数据,它唯一的代表该应用程序,在后面初始化程序主窗口的过程中需要用到这个参数。

 

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

 

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

 

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

 

  最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。这个整数值可以是SW_SHOWSW_HIDESW_SHOWMAXIMIZEDSW_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;

 

  在Win95WinNT的具有新界面特性的系统中,为了支持新的窗口界面特性,还有一种扩展的窗口类型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;

 

  WNDCLASSWNDCLASSEX这两个结构基本上是一致的,只是WNDCLASSEX结构中多了cbSizehIconSm这两个成员。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型。

 

  接下来的cbClsExtrawc.cbWndExtra在大多数情况下都会设为0

 

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

 

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

 

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

 

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

 

  上面的LoadIcon()LoadCursor()GetStockObject()都是WindowsAPI函数,它们的用法可以参看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_SYSMENUWS_CAPTIONWS_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结构中主消息值的,主消息值在wMsgFilterMinwMsgFilterMax之外的消息将被过滤掉。如果这两个参数为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结构,然后分解后得到的。第二个参数uMsgMSG结构中的message值是一致的,代表了主消息值。程序中用switch语句来将不同类型的消息分配到不同的处理程序中去。

 

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

 

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

 

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

 

  结束消息循环

 

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

 

  小结

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值