窗口的本质
在学习进程的时候,我们得知了高2G与低2G,即内核与应用层,在应用层,每个进程都由许多的模块组成,而内核实际上本质并没有多大的差别,只是模块不一样罢了
在操作系统的内核空间,有很多模块,但是与我们编程息息相关的两个最重要的模块
为什么说最重要呢?
在之前的学习中,我们知道了怎么创建进程、线程等等所有与编程程序运行相关的功能实现,实际上都是在ntoskrnl.exe模块上实现的
那我们之前使用的API函数有啥关系呢?
我们用的创建进程、线程、终止进程、线程等,全是在这个模块上实现的,表面上感觉好像是kernel32提供的,其实他只是提供了一个接口
win32k是所有和图形界面相关的、消息管理相关的这些功能,全在这模块实现,想要使用的话,就可以使用旁边的两个dll,这两个是有区别的
- 如果你想使用windows现成的,已经画好的图形界面,就使用user32.dll,管这种编程叫GUI
- 如果你想自己画个东西出来,就用gdi32.dll,这种编程叫GDI
不管你用你那个dll最终的实现都是在win32k.sys模块上实现
在前面的学习,我们讲到了句柄表(HANDLE),对于图形界面,我们有一个新的句柄,是 HWND,这个句柄也是索引,不一样的是这个HWND是个全局表的索引
每一个窗口都是在win32k里(也就是在内核)画的,应用层想要使用这些窗口,只要得到这个句柄就行了,并且不同的进程得到同样的句柄返回的是同样的窗口,因为这是全局的
GDI的简单实现
这里不用去深究只是用GDI来了解窗口的本质和消息机制,并不是真的去画窗口
步骤
GDI 图形设备接口(Graphics Device Interface)
1.设备对象(HWND)
2.DC(设备上下文,Device Contexts)
3.图形对象
图像对象 |
作用 |
画笔(Pen) |
影响线条,包括颜色、粗细、虚实、箭头形状等 |
画刷(Brushes) |
影响对形状、区域等操作,如使用的颜色 |
字体(Fonts) |
影响文字输出的字体 |
位图(Bitmaps) |
影响位图创建、位图操作和保存 |
首先,设备对象的意思就是画在那,就比如说,我打开画图的程序
这里是一个窗口,那我就可以在这里画,那这个窗口又是谁画出来的呢?
win32k画的,所以就会有个句柄,这里就先用工具来获取句柄,工具会放在文末
获取到窗口句柄
代码
#include<windows.h>
int main()
{
HWND hwnd;
HDC hdc;
HPEN hpen;
//1.设备对象,画在那
hwnd = (HWND)0x00080A3E; //NULL为桌面
//2.获取设备对象上下文
hdc = GetDC(hwnd);
//3.创建画笔,设置线条的属性
hpen = CreatePen(PS_SOLID, 5, RGB(0xFF, 0, 0));
//4.关联
SelectObject(hdc,hpen);
//5.开始画
MoveToEx(hdc, 0, 400, NULL);//hdc 起始x坐标 起始y坐标 原来的位置,需要的话用指针接收
LineTo(hdc, 400, 400);
//6.释放资源 Get对应Release Create对应Delete
DeleteObject(hpen);
DeleteObject(hBrush);
ReleaseDC(hwnd, hdc
return 0;
}
GetDC
HDC GetDC(
HWND hWnd // handle to window
);
需要传个HWND类型的句柄,返回值为HDC类型,想要画东西,就必须先要往他的内存里画,然后在打印到设备上
LineTo
BOOL LineTo(
HDC hdc, // device context handle
int nXEnd, // x-coordinate of ending point
int nYEnd // y-coordinate of ending point
);
首先是你要在那个内存中画线,后面的两个参数就是终止的x,y坐标,我们的屏幕就像是坐标系,左上角是(0,0)
然后就要设置图形的对象了,比如说画的是线还是面,多长多宽,什么颜色等等,详情请见步骤中的第3条
所以,假如我要画一条线,就要先创建画笔,设置线条的属性
CreatePen
HPEN CreatePen(
int fnPenStyle, // pen style
int nWidth, // pen width
COLORREF crColor // pen color
);
第一个参数就是笔的风格,比如:实线,虚线,波浪线等
第二个参数是线有多宽
CreatePen returns a pen with the specified width bit with the PS_SOLID style if you specify a width greater than one for the following styles: PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT.
如果对于以下样式指定了大于一的宽度 PS_DASH,PS_DOT,PS_DASHDOT,PS_DASHDOTDOT,CreatePen 函数将返回一个具有指定宽度的 PS_SOLID 样式的画笔。
也就是说上面的格式宽度超过了1,就会变成实线
第三个就是颜色,也就是RGB,分别是红、绿、蓝三种颜色,这三种颜色被称为原色(没记错的话),也就是说这三个颜色可以组成任何颜色
COLORREF RGB(
BYTE byRed, // red component of color
BYTE byGreen, // green component of color
BYTE byBlue // blue component of color
);
可以看到这里没个都是1字节,你能看过这种类型的#FF0000,这个代码就是红色,每个字节都是0~FF,可以把这个理解成浓度,而#FF0000就是纯红色,#00FF00是纯绿色,#0000FF就是纯蓝色,除了这三种代码,以外而组成的其他颜色就是中间色
SelectObject
HGDIOBJ SelectObject(
HDC hdc, // handle to DC
HGDIOBJ hgdiobj // handle to object
);
把hdc,和hpen关联起来,在画的时候用我设置的画笔去画,而不是默认的画笔
也可以画一个区域
#define _CRT_SECURE_NO_WARNINGS 1
#include<windows.h>
int main()
{
HWND hwnd;
HDC hdc;
HPEN hpen;
HBRUSH hBrush;
//1.设备对象,画在那
hwnd = (HWND)NULL;
//2.获取设备对象上下文
hdc = GetDC(hwnd);
//3.创建画笔,设置线条的属性
hpen = CreatePen(PS_SOLID, 5, RGB(0xFF, 0, 0));
hBrush = (HBRUSH)GetStockObject(DC_BRUSH);
//4.关联
SelectObject(hdc,hpen);
SelectObject(hdc, hBrush);
//5.开始画
//MoveToEx(hdc, 0, 400, NULL);//hdc 起始x坐标 起始y坐标 原来的位置,需要的话用指针接收
//LineTo(hdc, 400, 400);
SetDCBrushColor(hdc, RGB(0xFF, 0xFF, 0));
Rectangle(hdc, 0,0, 400, 400);
//6.释放资源 Get对应Release Create对应Delete
DeleteObject(hpen);
DeleteObject(hBrush);
ReleaseDC(hwnd, hdc);
return 0;
}
有些API就不介绍了,查文档,挺简单的
(有个问题,win10运行后直接一闪而过了,我在虚拟机winxp运行却提示DC_BRUSH和SetDCBrushColor函数未定义)
消息队列
什么是消息?
当我们点击鼠标的时候,或者当我们按下键盘的时候,操作系统都要把这些动作记录下来,存储到一个结构体中,这个结构体就是消息
比如,使用图形界面的程序,像画图这个程序,可以拖动,可以缩小,可以最大化,可以用笔画画有显示等,这些都是消息,并且存储在一个地方,这个地方就是线程
每个线程只有一个消息队列,因此是一对一的关系
当我们在使用鼠标或键盘时,并不是应用程序先接收到,而是操作系统先接收到动作,然后再发送给你当前进程对应的线程,然后再做出相应的反馈
假如有三个进程,显示了有三个窗口,A、B、C,当我要关闭A窗口时,为什么是A窗口做出反应,而不是别的窗口?
当你点下去的时候,操作系统就会接收到,你在哪里点的?你的坐标?你的动作,左键还是右键等等这些信息封装到结构体里,我们称之为消息,封装好后,这些信息应该给谁呢?我应该把信息放进消息队列里去,但这里有三个进程,并且一个进程不止一个线程,那我该给那个进程的那个线程里的消息队列呢?
这些界面都是win32k模块画出来的,每一个窗口在零环都有一个结构体,里面有许多信息,比如,从哪开始、有多宽、我在什么位置等等,还有一个成员就是当前这个窗口所属的线程是谁,那么上面的疑问也就解开了
当我们点击红叉的时候,操作系统会根据我点的左键还是右键等生成一个消息,这个消息没有办法找到是属于谁的,但是能根据信息找到是那个窗口,所以消息一定是针对窗口的,遍历窗口对象来找到是属于那个窗口
一个线程是可以拥有多个窗口的,但无论是哪个窗口接收到消息,都是存在同样的消息队列