文章目录
前言
在我们操作windows时,我们所点的每一下鼠标,敲的每一个键盘,其实都是一个事件(请求)。windows通过监听这些事件,分析这些事件的信息,给出回应。类比java中的JUI编程中,我们通过给一些控件添加listen,去完成这个控件被使用后的逻辑运行。让我们一步一步的学习windows程序吧。
一、Windows应用程序的基本概念
凡是运行在windows平台上的应用程序都可以叫做windows应用程序。大多数windows都具有图像界面并由事件来驱动运行。
1.Windows应用程序的特点
Windows应用程序的突出特点就是它具有图像界面,程序的进行是由程序用户和系统所发出的事件(键盘事件,鼠标事件,系统事件)推动(驱动)的。
Windows的程序的基本结构如下图
一般程序被系统加载后从主函数进入,程序所有的功能都在主函数中或主函数所调用的函数中完成,直到程序结束后,程序才会从主函数返回到系统。但是Windows程序则不然,由上图可知,当一个Windows程序被系统加载后,首先进入主函数,但是与普通的C语言不同的是,他还存在一个消息处理函数,这也是一个由系统调用的函数,并不由主函数直接调用,所以在主函数中看不到这个函数的调用过程。
WIndows主函数的作用主要分为:创建界面、进入消息循环队列,在这个队列中等待用户事件的产生。消息循环队列接受到消息后,主函数将消息发送给系统,系统调用Windows程序中的事件处理函数并对事件进行处理,处理结束后,事件处理函数返回系统,系统返回主函数的循环中。周而复始,直至程序终止事件发生,Windows程序结束运行并最后返回到系统。关系如图:
回调函数:用函数调用的函数就叫做“回调函数”
2.Windows操作系统与Windows程序的主函数名
起始函数原型。
int WINAPI WinMain(
HINSTANCE hInstance, //当前应用程序实例的句柄
HINSTANCE hPrevInstance, //系统中上一个应用程序实例的句柄
LPSTR lpCmdLine, //指向本程序命令行的指针
int nCmdShow //决定应用程序窗口显示方式的标志
);
早期Windows系统运行在Dos系统上,现在Windows与Dos在系统中位置的关系如下。
3.Windows内核与API函数
操作系统为了保护计算机的安全性,对软件进行了分级,操作系统运行在与保护程度最高的级别,用户程序运行在保护程度最低的级别。特别是叫做内核的那些操作系统的核心代码,普通用户根本无法通过普通调用的方法来访问内核。
Windows系统为了解决系统与用户这种割裂,向用户提供了一些可以由应用程序调用的函数,这些函数的集合叫做API(Application Programming Interface 应用程序接口).从表面上看,和我们普通的函数没有什么区别,但实质上API函数是一个软中断,因为在CPU硬件的设计上限定用户程序只有通过软中断才能进入高保护级别代码。
二、Windows的数据类型
由于windows应用程序不仅数据量非常大,而且数据类型也相当多,为了提高应用程序的辨识度,给许多C基本数据类型都起了别名,这些别名的特点就是其关键字都为大写:
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef unsigned int UINT;
1.句柄
在前面的Windows程序的主函数原始数据中出现了HINSTANCE,这里的变量hInstance就是一个句柄。一个句柄就表示了一个应用程序的唯一标识。即一个编号
为什么需要句柄呢?
在用户调用API函数创建内核对象后,为了系统的安全和隐藏内核对象的实现细节,函数为用户返回的既不是该对象本身,也不是其指针,而是Windows操作系统为这个内核程序对象编制的一个4字节的整数临时编号,这个编号也仅在本应用程序中有效。
常见的句柄类型: 每个句柄类型名都以英文“Handle”开头。其后是该句柄所代表的内核对象类型的简称。
句柄类型 | 说明 |
---|---|
HWND | 窗口句柄 |
HINSTANCE | 当前实列句柄 |
HCURSOR | 光标句柄 |
HFONT | 字体句柄 |
HPEN | 画笔句柄 |
HBRUSH | 画刷句柄 |
HDC | 设备环境句柄 |
HBITMAP | 位图句柄 |
HICON | 图标句柄 |
HMENU | 菜单句柄 |
HFILE | 文件句柄 |
我们只要获得了句柄,就可以在不接触这个对象的情况下进行操作。
2.窗口类WNDCLASS
为了应用程序设计者可以向系统提出自己对窗口的要求,Windows定义了结构WNDCLASS,其声明如下
typedef struct _WNDCLASS {
UINT style; //窗口样式,一般设置为0
WNDPROC lpfnWndProc; //指向窗口函数的指针
int cbClsExtra; //预留的扩展成员,一般设置为0
int cbWndExtra; //预留的扩展成员,一般设置为0
HINSTANCE hInstance; //与本窗口类关联的应用程序实例句柄
HICON hIcon; //窗口图标句柄
HCURSOR hCursor; //窗口光标句柄
HBRUSH hbrBackground; //窗口背景颜色刷句柄
LPCTSTR lpszMenuName; //窗口菜单资源名
LPCTSTR lpszClassName; //本窗口类名
} WNDCLASS;
3.Windows函数的调用说明
在函数的调用过程中,主调函数和被调函数之间需要使用堆栈作为中间缓冲来传递参数,由于有多个参数,为了保证参数传递顺序的正确性,就必须对参数的入栈出栈顺序进行约定,比较常用的两种函数约定为 __stdall与 __cdecl。在设计程序时,应对采用调用约定进行说明。如VC中默认采用 __cdecl。而Win32的API函数都遵循 __stdall调用约定。
与Windows中的数据类型一样,Windows对函数调用约定说明符也起了别名
windef.h
...
#define WINAPI __stdcall
#define CALLBACK __stdcall
...
CALLBACK常用来可以声明一个函数为回调函数。主函数没有使用别名CALLBACK来修饰,而使用了WINAPI,是因为WINAPI除了说明其修饰的函数为回调函数之外,还是一个程序入口。
三、窗口的创建和显示
一个Windows应用程序主窗口的创建主要需要以下过程:
- 使用WNDCLASS这个结构变量定制符合程序需要的窗口
- 将定制的窗口向系统注册
- 以窗口类注册名为参数,调用创建窗口的API函数在内存中创建窗口
- 调用API函数将窗口显示到显示器屏幕上。
1.窗口的定制
示例
WNDCLASS wc; //定义一个窗口类对象
//定制窗口
wc.style = 0; //窗口样式0
wc.lpfnWndProc = WndProc; //将窗口函数的首地址赋予指针lpfnWndProc
wc.cbClsExtra = 0; //无扩展
wc.cbWndExtra = 0; //无预留
wc.hInstance = hInstance; //与本窗口类关联的应用程序实例句柄
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); //图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //窗口背景颜色刷句柄
wc.lpszMenuName = NULL; //窗口菜单资源名
wc.lpszClassName = "myWnd"; //本窗口类名
2.窗口类的注册
为了方便对用户定义的WNDCLASS进行管理,用户程序必须将他们存放到一个专门的存储空间进行备案并同意列表。窗口类注册函数
BOOL RegisterClass(WNDCLASS&wc);
wc为待注册的窗口类。
注册成功后,就可以使用这个窗口类的名称来使用注册的这个窗口类了。
3.窗口的创建
HWND CreateWindow(
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // window-creation data
);
其中第一个参数”LPCTSTR lpClassName“就是 刚才在第一步设置窗口类时所起的类名字,第二个参数是该窗口的标题,后面的四个参数是窗口左上角的坐标和宽高,接下来两个参数”hWndParent“、”hMenu“是窗口的父窗口句柄和菜单句柄,由于没有就填NULL,最后一个参数”lpParam“是附加参数填NULL。
4.窗口的显示
BOOL ShowWindow(HWND hWnd, int nCmdShow); //正常显示窗口
BOOL UpdateWindow(HWND hWnd); //更新窗口
5.消息的循环
OS每次从该进程的消息队列中取出第一条消息,使用函数如下:
BOOL GetMessage(
LPMSG lpMsg, // message information
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax // last message
);
在取消息之前,需要声明一个MSG结构的消息变量,它不需要初始化,直接传入第一个参数,该函数会将取出的消息填入该变量。第二个参数是窗口句柄,表明你要接收那个窗口的消息,如果想接收该进程的所有消息,该参数填写NULL。第三、四个参数是消息过滤的最小最大值,表明你要接受消息的范围,如果接收全部消息,这两个值填0就行。
MSG的结构
typedef struct tagMSG {
HWND hwnd; //产生消息的窗口句柄
UINT message; //小小的表示码(事件种类)
WPARAM wParam; //消息的附加消息1
LPARAM lParam; //消息的附加消息1
DWORD time; //消息的产生时刻
POINT pt; //发送消息时,光标在屏幕上的位置
}MSG;
这个GetMessage有一个BOOL型的返回值,如果取到的消息是WM_DESTROY消息则返回假,所以我们将这个函数放到while循环中,只要有消息就执行循环体,直到用户关闭窗口时发出WM_DESTROY消息后while循环条件为假退出。
在循环体中,我们要处理消息,使用以下两个函数:
TranslateMessage(&msg); //翻译消息
DispatchMessageA(&msg); //将消息传入窗口的回调函数
6.窗口函数
具有窗口界面的Windows应用程序,必须有一个窗口函数来处理消息。窗口函数以参数的方式接受系统传过来的消息并对消息进行处理。由于窗口函数是回调函数,所以需要CALLBACK来说明。除此之外,对窗口函数的声明有两个约束:1.函数名必须与相应窗口结构中指针lpfnWndProc赋予的名称相同;2.其参数和返回值类型必须与系统的要求相符。
窗口函数声明:
LRESULT CALLBACK WndProc(
HWND hwnd; //发送窗口的句柄
UINT message; //系统传递来的消息标识
WPARAM wParam; //消息的附加参数(32位)
LPARAM lParam) //消息的附加参数(32位)
一个简单的窗口函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_LBUTTONDOWN:
{
MessageBeep(0);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);//默认处理
}
return 0;
}
通过上例,就是根据switch结构选择对应的消息进行处理。
四、一个简单的windows程序
要求:用鼠标左键单击程序窗口的用户区,机器发出“叮”的声音。
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_LBUTTONDOWN:
{
MessageBeep(0);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE preInstance, LPSTR lpCmdLine, int nCmdShow) {
HWND hwnd; //定义窗口句柄
MSG msg; //定义一个用来存储消息的变量
char lpszClassName[] = "窗口";
WNDCLASS wc; //定义一个窗口类型变量
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL; //窗口没有菜单
wc.lpszClassName = lpszClassName;
RegisterClass(&wc); //注册窗口类型,使用窗口的话,直接调用这个窗口的名称,即lpszClassName
hwnd = CreateWindow(lpszClassName, "windows", WS_OVERLAPPEDWINDOW, 120, 50, 800, 600,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow); //显示窗口
UpdateWindow(hwnd); //对显示器的显示内容进行更新
while (GetMessage(&msg, NULL, 0, 0)) { //消息循环
TranslateMessage(&msg); //翻译消息内容
DispatchMessage(&msg); //向系统发送消息
}
return msg.wParam;
}