您的位置:首页 > 其它

Windows程序运行机制

2014-07-20 17:23 148 查看
     在此声明,以下程序转载自原文地址,在此表示感谢!其中,我也加入了一些自己的理解,希望对大家的学习起到帮助作用。
一、API与SDK

     Windows操作系统提供了各种各样的函数,以方便我们开发Windows应用程序,这些函数是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数,所有主要的Windows函数都在Window.h头文件中进行了说明微软提供的API函数大多是有意义的单词组合,每个单词的首字母大写。MSDN是微软为开发人员提供的一套帮助系统,其中包含大量的开发文档、技术文章和示例代码。SDK的全称是Software
Development Kit,中文译为软件开发包。SDK实际上就是开发所需资源的一个集合,包括API函数库、帮助文档、使用手册、辅助工具等资源

二、窗口与句柄

     窗口是Windows应用程序中一个非常重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接受用户的输入,以及显示输出一个应用程序窗口通常包括标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条。窗口可以分为客户区和非客户区。客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框统称为窗口的非客户区,它们由Windows系统来管理,而应用程序则主要管理客户区的外观及操作。在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。在Windows程序中,有各种各样的资源(窗口,图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。

三、消息与消息队列

     在Windows中,不仅用户程序可以调用系统的API函数,系统也会调用用户自定义程序模块,这个调用是通过消息队列来进行的,从而完成用户自定义功能。Windows程序设计是一种基于事件驱动方式的程序设计模式,主要是基于消息响应的。用户与应用程序交互时,操作系统感知事件,将事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程。

1)、消息

     在Windows程序中,消息是由MSG结构体定义的。MSG结构体的定义如下:
typedef struct tagMSG {
HWND   hwnd;
UINT   message;
WPARAM wParam;
LPARAM lParam;
DWORD  time;
POINT  pt;
} MSG;
第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。

第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏的形式,WM是Windows Message的缩写,XXX对应某种消息的英文拼写的大写形式。在程序中,我们通常都是以WM_XXX宏的形式来使用消息的。

第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。wParam、lParam表示的信息随消息的不同而不同,如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。在VC++开发环境下通过goto definition查看WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。

最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。

2)、消息队列

每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应。这种消息机制,就是Windows程序运行的机制。

3)、进队消息和不进队消息

     Windows程序中的消息可以分为“进队消息”和“不进队消息”。进队的消息将由系统放入到应用程序的消息队列中,然后由应用程序取出并发送。不进队的消息在系统调用窗口过程时,直接发送给窗口。不管是进队消息还是不进队消息,最终都由系统调用窗口过程函数对消息进行处理。

 

4)、WinMain函数

      当Windows操作系统启动一个程序时,调用该程序的WinMain函数(实际是由插入到可执行文件中的启动代码调用的)。WinMain是Windows程序的入口点函数,与DOC程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束编写一个完整的Win32程序,该程序实现的功能是创建一个窗口,并在窗口中响应键盘及鼠标消息,程序实现的步骤为:

A、WinMain函数的定义

B、创建一个窗口

C、进行消息循环

D、编写窗口过程函数

四、WinMain函数的定义
WinMain函数的原形声明如下:
int WINAPI WinMain(
HINSTANCE hInstance,  // handle to current instance
HINSTANCE hPrevInstance,  // handle to previous instance
LPSTR lpCmdLine,      // pointer to command line
int nCmdShow          // show state of window
);
WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的。

第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例注意,只有运行中的程序实例,才有实例句柄。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数;

第二个参数hPrevInstance表示当前实例的前一个实例的句柄,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用;

第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。要在VC++开发环境中向应用程序传递参数,可以单击菜单Project-Setting,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数;

第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。

关于WinMain函数前的修饰符WINAPI,其实就是_stdcall。

五、窗口的创建

A、创建一个完整的窗口,需要经过下面几个操作步骤:

B、设计一个窗口类

C、注册窗口类

D、创建窗口

E、显示及更新窗口

1、设计一个窗口类

     一个完整的窗口具有许多特征,包括光标、图标、背景色等。在创建一个窗口前,必须对该类型的窗口进行设计,指定窗口的特征。Windows提供了WNDCLASS结构体来定义窗口特征,该结构体定义好一个窗口所具有的基本属性。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;
第一个成员变量style指定了这一类型窗口的样式,常用的样式:

CS_HREDRAW:当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口的宽度时,将不会重绘窗口。

CS_VREDRAW:当窗口垂直方向上的宽度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口的宽度时,将不会重绘窗口。

知识点:在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的类常量,这些常量都只有某一位为1。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。

第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数的实现机制是:

1)、定义一个回调函数;

2)、提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;

3)、当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理;

针对Windows的消息处理机制,窗口过程函数被调用的过程如下:

1)、在设计窗口类的时候,将窗口过程函数的地址付给lpfnWndProc成员变量;

2)、调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址;

3)、当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统,系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。

     一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一特定的窗口类相关联,基于该窗口类创建的窗口使用同一个窗口过程。

lpfnWndProc成员变量的类型是WNDPROC,在VC++开发环境中使用goto definition功能,可以看到WNDPROC的定义:

typedef LRESULT (CALLBACK * WNDPROC)(HWND,UINT,WPARAM,LPARAM);

LRESULT和CALLBACK实际上是long和_stdcall.从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型。注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同。

知识点:在函数调用过程中,会使用栈。_stdcall与_cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。

第三个成员变量cbClsExtra:Windows为系统中的每一窗口类管理一个WNDCLASS结构。在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0;

第四个成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0;

第五个成员变量hInstance指定包含窗口过程的程序的实例句柄;

第六个成员变量hIcon指定窗口类的图标句柄,这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标。在为hIcon变量赋值时,可以调用LoadIcon函数加载一个图标资源,返回系统分配给该图标的句柄。
HICON LoadIcon(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpIconName   // icon-name string or icon resource
);
LoadIcon的第二个参数是LPCTSTR类型,实际被定义为CONST CHAR *,即指向字符常量的指针,而图标的ID是一个整数。对于这种情况,我们需要MAKEINTERSOURCE宏把资源ID标识符转换为需要的LPCTSTR类型。

知识点:在VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑。

在VC++中,资源是通过标识符(ID)来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定ID的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX,图标资源为:IDI_XXX。采用这种方式时,我们在程序中使用资源ID时,可以一目了然

第七个成员变量hCursor指定窗口类的光标句柄,这个成员变量必须是一个光标资源的句柄。

第八个成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们可以调用GetStockObject函数来得到系统的标准画刷,GetStockObject函数不仅可以用于获取画刷句柄,还可以用于获取画笔、字体和调色板的句柄。

第九个成员变量lpszMenuName是一个以空终止的字符串,制定菜单资源的名字。如果使用菜单资源的ID号,那么需要用MAKEINTERSOURCE宏来进行转换。要注意,菜单并不是一个窗口。

第十个成员变量lpszClassName是一个以空终止的字符串,指定窗口类的名字。

六、消息循环

     在创建窗口、显示窗口、更新窗口后,需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,需要调用GetMessage()函数,该函数的原型声明如下:
BOOL GetMessage(
LPMSG lpMsg,         // address of structure with message
HWND hWnd,           // handle of window
UINT wMsgFilterMin,  // first message
UINT wMsgFilterMax   // last message
);
参数lpMsg指向一个消息结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中;

参数hWnd指定接收属于哪一个窗口的消息。通常将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息;

参数wMsgFilterMin指定要获取的消息的最小值,通常设置为0;

参数wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0,则接受所有消息。

      GetMessage函数接收到除了WM_QIUT外的消息均返回非零值。对于WM_QIUT消息,该函数返回零。如果出现了错误,该函数返回-1。编写的消息循环代码如下:
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
    GetMessage函数只有在接收到WM_QIUT消息时,才返回0。此时,while语句判断的条件为假,循环退出,程序才有可能结束运行。在没有接收到WM_QIUT消息时,Windows应用程序就通过这个while循环来保证程序始终处于运行状态;

TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数包含的是虚拟键代码和扫描码等信息,而我们在程序中需要得到某个字符的ACSII码,TranslateMessage 函数就可以将WM_KEYDOWN和WM_KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中;

DispatchMessage函数分派一个消息到窗口过程,由窗口过程对消息进行处理。DispatchMessage实际上是将消息回传给操作系统,由操作系统调用窗口过程对消息进行处理。

Windows应用程序的消息处理机制:

1、操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中;

2、应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理;

3、应用程序调用DispatchMessage,将消息回传给操作系统。消息由MSG结构体对象来表示,其中包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递;

4、系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。

提示:从消息队列中获取消息还可以调用PeekMessage函数;发送消息可以使用SendMessage和PostMessage函数。SendMessage函数将消息直接发送给窗口,并调用该窗口的窗口过程进行处理。在窗口过程对消息处理完毕后,该函数才返回。PostMessage函数将消息放入到与创建窗口的线程相关的消息队列后立即返回。

七、编写窗口过程函数

      一个Windows应用程序的主要代码部分就集中在窗口过程函数中,该函数的声明形式如下:
LRESULT CALLBACK WindowProc(
HWND hwnd,      // handle to window
UINT uMsg,      // message identifier
WPARAM wParam,  // first message parameter
LPARAM lParam   // second message parameter
);
     窗口过程函数的名字可以任意取,但函数定义的形式必须和上述声明的形式一致。WindowProc函数的4个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第1个参数hwnd就标识了接收消息的特定窗口。在窗口过程函数内部使用switch/case语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理。

八、 小结

     创建一个Win32应用程序的步骤:编写WinMain函数,可以在MSDN上查找并复制,设计窗口类,注册窗口类,显示并更新窗口,编写消息循环,编写窗口过程函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows