Windows程序运行原理
1.应用程序,操作系统,硬件之间的关系
这里涉及到消息及消息队列, 操作系统是通过消息机制(Message)来将感知到的事件传递给应用程序的。
操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序。
操作系统对事件做出反应的过程就叫做消息响应
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
2.Windows API
Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API
3.窗口句柄
说白了,窗口句柄就是资源的一个标识,也可以看成是一个指针,操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。按资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。
操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄,在程序中窗口句柄HWND是经常使用的
4.WinMain函数
操作系统调用WinMain函数来启动我们的应用程序,所以了解这个函数是很有必要的。
我们在MSDN中查看这个函数的定义:
int WINAPI WinMain( //windows入口函数
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
5.Windows窗口的创建过程
具体可以分为下面的几个步骤:
1.设计窗口类
//设计窗口类
WNDCLASS wndcls;//窗口类的结构体变量
wndcls.cbClsExtra = 0;//类加载的额外的空间,一般为0
wndcls.cbWndExtra = 0;//窗口的额外分配空间,一般为0
wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);//画刷句柄
wndcls.hCursor = LoadCursor(NULL,IDC_CROSS);//光标句柄,NULL表示使用标准的一套,后面的参数表示具体的某种
wndcls.hIcon = LoadIcon(NULL,IDI_ERROR);//图标句柄,NULL表示使用默认的一套,后面的参数表示具体的某种
wndcls.hInstance = hInstance;//窗口句柄
wndcls.lpfnWndProc = WinSunProc;//回调函数
wndcls.lpszClassName = "HelloWorld";//窗口名称
wndcls.lpszMenuName = NULL;//菜单的名字
wndcls.style = CS_HREDRAW | CS_VREDRAW;//窗口的风格,采用位相或的方式增加功能
2.注册窗口类
RegisterClass(&wndcls);//注册窗口
3.创建窗口类
//创建窗口之前必须定义窗口的句柄-标识窗口
HWND hwnd;
//创建窗口
hwnd = CreateWindow(
"HelloWorld",//类的名称,与之前创建的WNDCLASS名称相同
"MyFirstMFCApp",//窗口名称
WS_OVERLAPPEDWINDOW & ~ WS_MAXIMIZEBOX/*去掉最大化按钮*/,//窗口的默认属性,层叠菜单,系统菜单,最小最大化按钮,使用二进制位相或的技术
0,//窗口左上角x坐标(指定缺省值CW_USEDEFAULT,则设置y坐标的值被忽略)
0,//窗口左上角y坐标
600,//窗口宽度(指定缺省值CW_USEDEFAULT,则设置高度的值被忽略)
400,//窗口高度
NULL,//父窗口句柄,没有父窗口设置为NULL
NULL,//菜单句柄,没有菜单设置为NULL
hInstance,//当前程序实例的句柄
NULL);//暂时不要求这个参数
4.显示窗口类
ShowWindow(hwnd,SW_SHOWNORMAL);//窗口句柄,显示状态(最大化,最小化,正常显示)
5.更新窗口类
UpdateWindow(hwnd);//更新窗口
6.开启消息循环
MSG msg;
//操作系统为每一个程序分配一个消息队列
/*
当GetMessage函数从线程的消息队列中得到WM_QUIT消息后,返回0,程序即刻退出
当GetMessage函数从线程的消息队列中得到的不是WM_QUIT,返回非0,继续循环接收消息
*/
//从消息队列中取出消息:消息变量,所有消息,消息的开始(0接收所有消息),消息的结束(0接收所有消息)
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);//转换相应的消息(WM_CHAR消息)
DispatchMessage(&msg);//派发消息给操作系统,操作系统再调用用户编写窗口时自定义的窗口回调函数-WinSunProc
}
7.Windows窗口的回调函数
下面是一张Windows消息循环的运行原理图,能够帮助我们更好的理解消息的处理过程:
可以看出,最后调用的窗口回调函数是我们自己定义的,这就为我们如何处理消息提供了我们想要的方式。
下面是窗口回调函数(过程函数)的定义
LRESULT CALLBACK WinSunProc(//窗口过程函数--又称为窗口回调函数
HWND hwnd, // handle to window 窗口句柄
UINT uMsg, // message identifier 消息句柄
WPARAM wParam, // first message parameter 第一个消息附加参数
LPARAM lParam // second message parameter 第二个消息附加参数
)
{
switch(uMsg)//判断消息类型
{
//键盘按下消息
case WM_CHAR:
char szChar[20];
sprintf(szChar,"char is %d",wParam);
//窗口句柄, 消息文本, 对话框标题, 对话框类型(0代表MB_OK只有一个确定按钮,MB_YESNO有确定和取消按钮)
MessageBox(hwnd,szChar,"Hello",0);
break;
//鼠标左键按下消息
case WM_LBUTTONDOWN:
MessageBox(hwnd,"mouse clicked","Hello",0);
HDC hdc;//自动确定平台驱动设备类型
hdc = GetDC(hwnd);//参数是窗口句柄(相当于把窗口当画布),获取DC句柄,负责显示
//输出文本函数:dc句柄,文本的开始x,y坐标,文本,字符串长度
TextOut(hdc,0,50,"Hello",strlen("Hello"));
//释放DC,DC是系统内部维护的数据结构,不释放会导致内存泄露
ReleaseDC(hwnd,hdc);
break;
//屏幕重绘消息
case WM_PAINT:
HDC hDC;
PAINTSTRUCT ps;
//为指定的窗口绘画
hDC=BeginPaint(hwnd,&ps);//**只能在WM_PAINT中使用**
//将文字一直保存在窗口上
TextOut(hDC,0,0,"Hello",strlen("Hello"));
//结束绘画,释放DC
EndPaint(hwnd,&ps);//**只能在WM_PAINT中使用**
break;
//窗口关闭消息
case WM_CLOSE:
//MessageBox执行成功返回点击的按钮类型信息
//一定要在这里执行判断
if(IDYES==MessageBox(hwnd,"是否真的结束?","Hello",MB_YESNO))
{
//销毁窗口 发送WM_DESTROY消息
//这个函数执行完后,窗口就会被销毁
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
//0代表退出码
/*
该函数:post一个WM_QUIT消息到线程的消息队列中然后立刻返回
当GetMessage函数从线程的消息队列中得到WM_QUIT消息后,返回0,程序即刻退出
当GetMessage函数从线程的消息队列中得到的不是WM_QUIT,返回非0,继续循环接收消息
*/
PostQuitMessage(0);
/*
在DestroyWindow(hwnd);中判断是否结束
不要在这里执行判断,因为DestroyWindow(hwnd);函数执行
完毕后,窗口已经被销毁,再判断就没有意义了
if(IDYES==MessageBox(hwnd,"是否真的结束?","Hello",MB_YESNO))
{
PostQuitMessage(0);
}*/
break;
default:
//对不感兴趣的消息,让系统进行消息的缺省处理,必不可少的处理
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
窗口回调函数的指定:
wndcls.lpfnWndProc = WinSunProc;//指定这个窗口类的回调函数为WinSunProc
6.Windows函数调用约定
函数调用约定:规定函数参数传递顺序,栈的清除的规则
VC中的函数调用约定:
__cdecl约定:标准C语言调用约定, VC++编译选项默认使用这种方式
__stdcall约定:
标准调用约定,也称为Pascal调用约定,Delphi使用这种约定
除了可变参数的API以外,都是使用__stdcall约定,
在VC中使用这种调用约定,必须加上CALLBACK宏定义 #define CALLBACK __stdcall
回调函数必须使用__stdcall方式,更加直观的表示该函数是回调函数
7.Windows命名约定
8.变量的位特性
在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。该变量的多个位为1时,就是多种特性的组合。
例如:
指定窗口的风格为三种类型的组合
style = CS_VREDRAW | CS_HREDRAW | CS_NOCLOSE;
如果去掉其中的某个特性的方式,用取反(~)之后再进行与(&)运算,就能够实现
style & ~CS_NOCLOSE;//去掉最后一个特性
9.Visual C++程序编译过程图解
只要我们编译过程序,那么这个过程就应该不会陌生吧,生成的中间文件时*.obj,生成的目标文件是.exe文件。
总结
我们在编写Windows程序的时候,一定要学会查找MSDN的文档,MSDN是最好的学习资料。