Window程序内部机制(上)
Window程序内部机制
API:Windows操作系统本身提供各种各样的函数,而这些函数是应用程序开发人员编程时调用的接口,即应用程序接口(API,Application Programming Interface)。
SDK:软件开发包(Software Development Kit),包含了API函数库、帮助文档、使用手册、辅助工具等资源。
窗口:
1.一个应用程序至少要有一个窗口,称为主窗口。
2.一个应用程序窗口通常包含:标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,滚动条。(注:包含的这些不能说是窗口)
窗口分为:客户区(windows应用程序管理)和非客户区(应用程序管理外观与操作)。
3.电脑开机启动,进入windows系统后,显示的桌面,是一个桌面窗口。
4.父窗口与子窗口,子窗口的形式有按钮、单选按钮、复选框、组框、文本框等。
句柄:
句柄有两种含义:
1.是一个特殊的智能指针,当一个应用程序引用其他系统的内存块或者对象的时候,就用到了这种句柄。
2.我们现在所讲编写Win32窗口程序的这种句柄,它不是一个智能指针。它是用来标识应用程序中的不同对象和同类中的不同的实例,是windows用来标识应用程序中使用唯一的整数值,即大小为4字节(在64位程序中是8字节)。windows系统大量使用了句柄来标识对象,比如:一个窗口、按钮、图标、滚动条、输出设备、控件、文件等对象,均是通过句柄来访问相应的对象的信息。
消息:
typedef struct tagMSG{
HWND hwnd; //标识窗口,标识某个活动窗口
UINT messge; //消息的标识符,不同消息对应的数值不同,数值一般为WM_XXX
WPARAM wParam; //消息的附加消息
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
详细说明各个参数:
------------------
HWND:一个消息一般都是和某一个窗口有关系的,用来标识窗口。发送一个消息, 就表明该消息由指定的窗口接受。
比如有A、B、C三个窗口。如果A窗口发送给C窗口,就标识一个窗口进行传送数据,而不是B窗口去接收数据。。
-----------------
UINT messge;消息的标识符,即消息的名称。不同消息对应的数值不同,数值由于不方便记忆,所以一般定义为为WM_XXX宏(XXX是相应的字母),在查看定义中有各自确切的数值。比如WM_CHAR,表示消息是字符消息,在VS查看定义中的确切数值为0x0102。
------------------
WPARAM和LPARAM:WPARAM是16位短整数(查看定义为WPARAM->ULONG_PTR- >unsigned int),而LPARAM是32位整型变量(查看定义:LPARAM->LONG_PTR->long )。这两个都是Win16系统遗留下来的产物,到了Win32API,都是32位,大小是一 样的。
两者分别代表不同的含义:
在孙鑫教程中是这样说的:
WPARAM:消息投递到消息队列中的时间(通常也代表控件的ID)
LPARAM:消息投递到消息队列中的鼠标的当前位置
比如:某控件的通知消息、可接受消息,习惯上用LPARAM。
使用自定义消息时,程序员可以任意指定这两个参数代表各自不同的含义。
--------------------
time:指定的时间发布的消息,即消息存放消息队列的时间。
--------------------
pt:消息放入消息队列的鼠标坐标。
-------------------
消息队列:
含义:应用程序执行之后,系统会为该程序创建一个消息队列,用来存放程序创建 的窗口的消息。即:执行某操作(比如按下鼠标左键)-->WM_LBUTTONDOWN消息存放在消息队列--> 应用程序取出消息并作出响应。
进队消息与不进队消息:
进队消息:由系统存放到应用程序的消息队列中,然后由应用程序取出并发送。
不进队消息:系统调用窗口过程时直接发送给窗口。
WinMain函数
含义:Windows程序的入口点函数,与main函数作用相同。
int WINAPI WinMain(
HINSTANCE hInstance, //该程序当前运行的实例的句柄,是个数值。
HINSTANCE hPrevInstance, //当前的实例的前一个实例的句柄
LPSTR lpCmdLine, //指向应用程序命令行的字符串的指针,不包括执行文件名。
int nCmdShow //指明窗口如何显示
);
该函数的功能是被系统调用,作为一个32位应用程序的入口点。系统调用WinMain函数时,把这4个参数传递给应用程序。
参数说明如下:
-------------------------
hInstance:该程序当前运行的实例的句柄,是个数值。(注意只有运行才算是) 。一个应用程序可以有多个实例,每一个实例都有一个句柄值。
-------------------------
hPrevInstance:当前的实例的前一个实例的句柄。在Win32环境下,不起作用,数值为NULL。
-------------------------
lpCmdLine:是一个以空终止的字符串,指定传递给应用程序的命令行参数。例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击 这个文件时将启动记事本程序(notepad.exe),此时系统会将D:\sunxin.txt作为 命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路 径名后,就在窗口中显示该文件的内容。
-------------------
nCmdShow:指明窗口如何显示。比如最大化、最小化、隐藏等。
例如:
SW_HIDE:隐藏窗口并且**另外一个窗口。
SW_SHOW:**一个窗口并以原来的尺寸和位置显示窗口。
窗口的创建
创建一个窗口的步骤:
1.设计一个窗口类
2.注册窗口类
3.创建窗口
4.显示以及更新窗口
1.设计一个窗口类:
typedef struct _WNDCLASS {
UINT style; //指定窗口样式。
WNDPROC lpfnWndProc; //函数指针,指向窗口过程函数
int cbClsExtra; //类附加内存
int cbWndExtra; //窗口附加内存
HINSTANCE hInstance; //包含窗口过程的程序的实例句柄。
HICON hIcon; //指向窗口的图标句柄
HCURSOR hCursor; //指向窗口的光标句柄
HBRUSH hbrBackground; //指向窗口的背景画刷句柄
LPCTSTR lpszMenuName; //菜单名字
LPCTSTR lpszClassName; //窗口类的名字
} WNDCLASS, *PWNDCLASS;
style:指定窗口样式。
如:CS_NOCLOSE(禁用系统菜单的Close命令,这将导致窗口没有关闭按钮)。即命令为CS_XXX。(Class style类样式,16位常量,都只有某位为1)
----------------------
lpfnWndProc:函数指针,指向窗口过程函数(即回调函数),你必须使用CallWindowProc函数调用窗口过程。
窗口过程函数被调用的过程:
(1)在设计窗口类时,将窗口过程函数的地址赋值给IpfnWndProc成员变量,主要作用是保存窗口过程函数的地址。
(2)调用RegsiterClass(&wndclass)注册窗口类,从而系统便知道窗口过程函数的地址。(因为窗口类中包含了这个函数的地址)
(3)当应用程序接收某一窗口的消息时,调用DispatchMessge(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。
即以上3点可这样简单的描述:
窗口类(IpfnWndProc保存窗口过程函数的地址)-->RegsiterClass(注册窗口类,间接知道窗口过程函数的地址)-->调用窗口过程函数对消息进行处理。
在Visual StudioS中,选择代码“WENDCLASS”转到“typedef WNDCLASSA WNDCLASS;”,选择“WNDCLASSA ”转到这个结构体“WNDCLASSA ”,对该结构体一个参数为“ WNDPROC lpfnWndProc;”选择“WNDPROC”转到有以下定义:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
LRESULT:是一个long类型
CALLBACK:是_stdcall类型。
从这可知,WNDPROC是一个CALLBACK类型的函数指针,即被定义为指向窗口过程函数的指针类型。
------------------------
cbClsExtra:为WNDCLASS结构体分配和追加一定字节数的附加内存空间,称为类附加内存,用于存储类的附加信息。一般初始化为0,我们通常把该参数设置为0。
------------------------
cbWndExtra:窗口附加内存。应用程序可用想和部分内存存储窗口特有的数据。内存初始化为0.
------------------------
hInstance:包含窗口过程的程序的实例句柄。
-----------
hIcon:指定窗口的图标句柄。如果设置为NULL,即系统提供默认的图标。自定义图标时,可调用LoadIcon函数加载一个图标资源,返回系统分配给该图标的句柄。
HICON LoadIcon(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpIconName // name string or resource identifier
);
hInstance:图标句柄。如果加载系统的标准图标,该参数为NULL。
lpIconName:LPCTSTR实际上定义为“CONST CHAR*”,即指向字符常量的指针。图标的ID是一个整数,我们需要用MAKEINTRESOURCE宏把资源ID标识符转换为LPCTSTR类型,因 此我们编写LoadIcon函数的第二参数,输入字符串即可,即"ID_IXXX"。
-----------------
hCursor:指定窗口类的光标句柄。如果该参数为NULL,也将会有光标的形状。通过 LoadCursor函数加载光标,即定义如下:
HCURSOR LoadCursor(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpCursorName // name or resource identifier
);
作用和用法与hIcon相同。
-----------------
hbrBackground:指定窗口类的背景画刷句柄。通过调用GetStockObject函数得到系统的 标准画刷。定义如下:
HGDIOBJ GetStockObject(
int fnObject // stock object type
);
fnObject:获取对象的类型,即比如背静画刷这个对象。
GetStockObject也可以运用到其他句柄,比如:获取画笔句柄,字体句柄等。
------------------
lpszMenuName:以空终止的字符串,指定菜单资源的名字。设置为NULL时,系统默认该窗口没有菜单。
------------------
lpszClassName:以空终止的字符串,指定窗口类的名字。
2.注册窗口类:
注册窗口类的定义如下:
ATOM RegisterClass( CONST WNDCLASS *lpWndClass // class data);
lpWndClass:指向窗口类对象的指针。
----------------------
3.创建窗口:
HWND CreateWindow(
LPCTSTR lpClassName, //注册的窗口类
LPCTSTR lpWindowName, //窗口的名字,显示在标题栏
DWORD dwStyle, // 窗口的样式,而注意WNDCLASS中style是窗口类的样式
int x, //窗口坐标x,CW_USEDEFAULTM默认左上角并忽略y参数
int y, // 窗口坐标y
int nWidth, // 窗口的宽度,CW_USEDEFAULTM默认宽度和高度
int nHeight, // 窗口的高度
HWND hWndParent, // 当前被创建的父窗口,子窗口有WS_CHILD样式
HMENU hMenu, //窗口菜单的句柄
HINSTANCE hInstance, //窗口所属的应用程序实例的句柄。(是应用程序的句柄)
LPVOID lpParam // WM_CREATE消息的附加参数传入的数据指针。创建多文档界面的客户窗口时,
//该参数必须指向CLIENTCREATESTRUCT结构体。其他窗口设置为NULL。
);
dwStyle: 窗口的样式。该参数的值比如下:
WS_OVERLAPPED:层叠的窗口,具有一个标题栏和一个边框。
WS_CAPTION:创建一个标题栏的窗口。
WS_SYSMENU:创建一个带有系统菜单的窗口,必须同时设定WS_CAPTION。
--------------------
如果CreateWindow函数返回系统为该窗口分配的句柄,否则,返回NULL。
4.显示以更新窗口:
(1)显示窗口
BOOL ShowWindow(
HWND hWnd, // handle to window
int nCmdShow // show state,比如隐藏该窗口,SW_HIDE
);
--------------------
(2)更新窗口
BOOL UpdateWindow(
HWND hWnd // handle to window
);
UpdateWindow函数发送WM_PAINT消息刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,并没有放入消息队列中。
消息循环
BOOL GetMessage(
LPMSG lpMsg, // message information
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax // last message
);
GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中。
---------------
lpMsg:指向消息(MSG)结构体
---------------
hWnd:指定接受属于哪一个窗口的消息。通常设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。
---------------
wMsgFilterMin:指定要获取的消息的最小值,通常设置为0.
wMsgFilterMax:指定要获取的消息的最大值。
如果wMsgFilterMin和wMsgFilterMax都设置为0,则接收所有消息。
-----------------------------
GetMessage的返回值:
1.返回值为-1:意思是接收出现了错误。比如hWnd是无效的窗口句柄或者lpMsg是无效的指针。
2.返回值为0:接收到WM_QUIT的消息并结束消息循环。
3.返回值为正整数:正常接受消息,保证程序处于运行状态
------------------------------------------------------------
编写的消息循环代码如下:
MSG msg;
while(GetMessge(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
TranslateMessage函数:用于将虚拟键消息转换成字符消息。将虚拟键(比如WM_KEYDOWN)转换成一条WM_CHAR消息。
即调用该函数,字符被取出的过程:
1.调用GetMessge函数并在while判断其返回值是否为真。
2.若为真,执行TranslateMessage函数,转换成字符消息。
3.下一while循环,调用GetMessge函数时,字符消息被取出,把字符消息放入消息队列中。。
-------------------
DispatchMessage函数分派一个消息到窗口过程(实际上是DispatchMessage函数将消息回传给操作系统,而操作系统调用窗口过程函数对消息进行处理),由窗口过程函数对消息进行处理。
DispatchMessage函数对消息处理的详细步骤:
1.操作系统接收到应用程序的窗口消息,将消息放入该应用程序的消息队列中。
2.应用程序在消息循环中调用GetMessge函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理。例如:放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
3.调用DispatchMessage将消息回传给操作系统。
4.系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。
-------------------
与GetMessge实现相似的功能的PeerMessage函数:
BOOL PeekMessage(
LPMSG lpMsg, // message information
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax, // last message
UINT wRemoveMsg // removal options
);
前面4个参数和GetMessage函数的4个参数作用相同。
wRemoveMs:指定消息获取的方式。若设置PM_NOREMVE,消息不会从消息队列中被移除。
-----------------------
SendMessage函数和PostMessage函数的区别:
SendMessage函数:SendMessage函数将消息直接发送给窗口,并调用该窗口的窗口过程进行处理。在窗口过程对消息处理完毕之后,该函数才返回(所以SendMessage发送的消息为不进队消息)。
PostMessage函数:PostMessage将消息放入与创建窗口的线程相关联的消息队列后立即返回。
PostThreadMessage函数:用于向线程发送消息,MSG结构体中的hwnd为NULL。