学过C系语言编程的都知道,程序的入口一定是main()函数,在windows系统中也有这样的函数,它叫WinMain函数。WinMain函数是所有windows程序的入口,主要负责注册窗口类,创建并初始化窗口,进入消息循环,以及消息循环检索到WM_QIUT消息时,终止程序执行。
下面详细说明
在此之前,请看下面的代码:
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wndclass; //声明结构体变量
RegisterClass (&wndClass);//注册窗口
CreateWindow (……);//创建窗口
ShowWindow (……);//显示
UpdateWindow (.....);//更新
while(Getmessage(&msg,NULL,0,0))//进入消息循环
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
你随便找一本关于windows编程的书籍来看都会看到类似于上面的代码,可能是完整版,这里只是为了理解程序结构,所以不需要完整的可执行程序。
先说一下WinMain函数的参数意义:
1.建立窗口类
WinMain函数的第一步是要建立、登记应用程序的窗口类。就像小米生产手机一样,首先需要对手机进行设计,什么样子,什么配置等等。窗口类是定义窗口属性的模板,这些属性包括窗口样式、鼠标形状,菜单,窗口函数。只有先设计出小米的外观,才能开始生产小米手机,同样,只有先建立窗口类,才能创建Windows应用程序窗口。
窗口类的定义如下:
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;
它是一个结构体,里面包含设计一个窗口的各种信息,就像小米手机的设计信息需要包括屏幕尺寸,机身厚度,宽度,高度,颜色,电池容量,内存……关于WNDCLASS的详细信息请点击这儿
1)style用于控制窗口的特性
- CS_BYTEALIGNCLIENT: 在字节边界上(在x方向上)定位窗口的用户区域的位置
- CS_BYTEALIGNWINDOW: 在字节边界上(在x方向上)定位窗口的位置
- CS_CLASSDC: 该窗口类的所有窗口实例都共享一个窗口类DC
- CS_DBLCLKS: 允许向窗口发送双击鼠标键的消息
- CS_GLOBALCLASS: 当调用CreateWindow 或 CreateWindowEx
函数来创建窗口时允许它的hInstance参数和注册窗口类时传递给
- RegisterClass 的 hInstance参数不同。如果不指定该风格,则这两个 hInstance 必须相同。
- CS_HREDRAW: 当水平长度改变或移动窗口时,重画整个窗口
- CS_NOCLOSE: 禁止系统菜单的关闭选项
还有几个特性我们有列出来,这些特性有的是可以用“|”来进行组合的。
lpfnWndproc:是一个指向窗口内消息处理的指针,该消息处理函数通常称为窗口函数,用于接收Windows发送给窗口的消息,并执行相应任务。可以理解为生产小米的代工厂(不是很恰当的比喻)。
所以每当开发一个小米手机的时候,就需要对设计参数重新复制,wndclass也是这样子。如下就是一个窗口结构。
wndclass.style =0; // 窗口类型为缺省类型CS_ Class Style
wndclass.lpfnWndProc=WndProc; //定义窗口处理函数
wndclass.cbClsExtra=0; //窗口类无扩展
wndclass.cbWndExtra=0; //窗口实例无扩展
wndclass.hInstance=hInstance; //当前实例句柄
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //窗口的最小化图标为缺省图标
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); // 窗口采用箭头光标
wndclass.hbrBackground=(HBRUSH)(GetStockObject(WHITE_BRUSH)); //窗口背景为白色
wndclass.lpszMenuName=NULL; //窗口无菜单
wndclass.lpszClassName=lpszClassName; //窗口类名为“窗口”
顺带说一下这里面的2个常用函数
- LoadIcon函数 用于加载图标资源,模型如下
HICON
LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName
);
参数lpIconName指明程序图标,可以取一些预设值,
2.GetStockObject函数,用于加载对象资源,原型如下:
HGDIOBJ GetStockObject(int fnObject);
参数fnObject指定对象类型,常见类型如下:
2.注册窗口类
窗口设计完成后,必须要注册窗口类,,当对WNDCLASS结构域一一复制后,就可以注册了,一般调用RegisterClass函数实现窗口类的注册。原型如下:
ATOM RegisterClass(CONST WNDCLASS *lpWndClass)
如果注册失败,RegisterClass返回非0值,程序终止,为0则表示注册成功,程序继续进行。
为什么要注册呢?就是要用户按照一定的要求谁窗口类,不能自己天马行空,想怎么来就怎么来。再用小米手机举例子,小米设计了一款手机,需要去工信部认证,才能上市,批量生产,要是你的手机不和规范,如有窃听功能,甚至手机又爆炸可能,政府是不可能让你上市的,对于汽车就更是如此了。
3.创建窗口
窗口类注册完了,就可以开始创建窗口了,就像小米通过了工信部认证,就可以开始批量生产啦。创建窗口有一个重要的函数CreateWindow,原型如下:
HWND CreateWindow(
LPCTSTR lpClassName,//注册窗口类名
LPCTSTR lpWindowName,//窗口标题名
DWORD dwStyle, //窗口风格
int x,//显示窗口水平位置
int y,//显示窗口垂直位置
int nWidth,//窗口宽度
int nHeight,//窗口高度
HWND hWndParent,//父窗口句柄
HMENU hMenu,//菜单句柄
HANDLE hlnstance,//应用程序句柄
LPVOID lpParam)//指向传递一个窗口的指针型函数
}
4.显示窗口
大家都知道小米手机生产出来后,接下来都要开发布会。窗口创建后,就要把它显示出来,这时需要用到ShowWindow函数,原型如下:
BOOL ShowWIndow(
HWND hWnd,
int nCmdShow
);
其中hWnd是窗口句柄,指定显示哪一个窗口,参数nCmdShow决定窗口的显示模式,这三个模式SW_MINIMIZE(最小化)、SW_SHOWMAXMIZED(最大化)、SW_HIDE(影藏),SW_SHOWNORMAL(普通化)。
WinMain函数调用完ShowWindow后,还需要UpdateWindow函数,将产生一个WM_PAINT消息,该消息使窗口重画,最终把窗口显示出来。
5.创建消息循环
Windows为每个运行程序维护一个消息队列,当单机鼠标按键时,Windows并不是直接报这个时间发送给应用程序,而是翻译成一个消息,并把这个消息,放置到应用程序所属的消息队列中去,形成消息循环,在有应用程序从队列中去获取消息。
同样用小米举个例子,小米手机性价比高,第一批货一抢而空,很多人都要等着买,由于产能限制,只能一批一批的出货,大家只能预定,然后小米根据预定人数决定下次生产订单数。
应用程序从队列中获取消息的方式有2种:意识由应用程序调用GetMessage函数或者PeekMessage函数从消息队列读取一条消息,并将消息放在MSG结构中;二是由Windows调用用户提供的回调函数来获取消息。一般采用的是第一种消息循环:
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
只有当从应用程序列表检索到WM_QUIT消息时,GetMessage函数才会返回FALSE,也就是说一旦进入消息循环,即使没有消息也会一直运行下去。循环中的TranslateMessage函数用于将消息虚拟键转换为字符信息,字符信息再被发送到消息队列中,下一次调用GetMessage函数时将被取出。
例如当键盘上的某个字符键被按下时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的wParam和lParam参数包含的是虚拟键代码和扫码信息。而程序中往往需要字符的ASCII码,TranslateMessage函数就是将WM_KEYDOWN和WM_KEYUP消息组合转换为一条WM_CHAR消息,该消息的wParam参数包含了字符的ASCII码,并将转换后的新消息发送到消息队列中。
*TranslateMessage函数不会修改原有消息,只是产生新消息,而DispatchMessage函数则用于将消息传回给操作系统,由操作系统调用窗口函数对消息进行响应,而窗口函数对消息进行处理
6,。窗口函数
终于到了WinProc函数了。
窗口函数是消息处理函数,用于处理特定消息对应的一些代码,定义了应用程序对可能接收到的不同的消息的响应。以后你会发现,windows编程的重点工作就在这个函数里面,因为消息往往都是类似的(无非鼠标,键盘,控件),对这些消息的处理再是我们工作的重点。前面的框架都是固定的。
窗口函数的名字可以随便取,如Win_Fisrt_proc,只要保证前后一致即可。其原型如下:
LRESULT CALLBACK WndProc( //WndProc名称可自由定义
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
细心的读者会发现这个函数的阐述和MSG结构的前4个成员完全相同。WinProc函数通常包括一个多分支switch结构语句,每个case语句对应一种消息。
LRESULT WINAPI WndProc( HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_DESTROY:
PostQuitMessage(NULL);
break;
case ..
.
.
.
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
读者可能会由点疑问:为什么没看到WinMain函数显示调用Winproc函数呢?
*请注意,WInProc函数头部有一个CALLBACK标记,表明这是一个回调函数。什么是回调函数呢?简单的说就是等着别人来调用的函数,这些函数的原因都是由调用者设计好,使用的时候只要按照原型重新定义一个函数,然后将函数指针传递过去,我们在前面设计窗口类的时候就有WNDPROC这一项,就是告诉系统我这个类的消息处理函数是谁,因此回调函数是严格按照系统的规定进行说明和定义,函数的调用约定,参数都是固定的。就像系统是最高法院,你的winproc是地方最高法院,专门用来处理当地案件,一般发生了一个案件,不会直接由最高法院处理,而是最高法院让事件发生的所在地的地方法院来处理,但是作为地方法院,一切都有按照规定的流程来。
(注:本文参考书籍博客整理而成)