N多年没有写过 Window 程序了。为了研究 WebRTC 源码,这两天重新学习一下。还记得上大学的时候看过 《Windows95 程式设计》台湾版,对那本书印象极为深刻。一是当时国内确实没有一本写的那么深入的书籍,二是那本书翻译的特别好,让人一看就特别明白。10多年过多了,当时的情景还记忆犹新,也可见那本书写的有多好了。
Windows开发有很多知识点,窗口啊,句柄啊,消息啊,重绘啊,baba …..,但一个 Windows 程序的核心就是一个消息处理机制。
Windows程序运行的基本原理
Windows程序是消息为驱动的,所以它的核心就是消息的传递与处理。如鼠标消息、键盘消息,Timer消息,窗口的创建与消毁等等。那么,Windows程序是在哪儿处理消息呢?是否掌握了它,就控制了Windows程序的核心呢?答案是肯定的,它就是 WndProc 函数。所有的消息都要经过这个函数处理。
Windows 程序有两种消息,一种是队列消息,它通过 DispatchMessage 函数分发给 WndProc 函数,像鼠标消息、键盘消息,Timer消息都是这类消息。另一种是非队列消息,它是系统函数直接发送给 WndProc 函数的,像窗口的创建与消毁消息,WM_COMMON消息等等都是非队列消息。
最简单的 Windows 程序
一个最简单的 Windows 程序都包括哪些内容呢?下面我们详细介绍一下:
WinMain 函数
我们都知道无论是Windows程序,还是Linux程序,也无论是C/C++,还是 Java语言,它们都有一个 main 函数。更准确点说应该叫“程序入口点”。
我们写程序时,一般都以 main 开头,编译器在编译该程序时,会将 main 函数地址写入到可执行文件的文件头中,这就是“程序入口点”了。
在执行程序时,操作系统首先通过程序加载器将要运行的程序加载到内存中,然后重新计算符号地址表。一切准备就绪后,才跳到程序入口点,将一条条指令送入CPU流水线开始执行程序。这就是程序的运行的基本流程。
因此,我们可以知道每个程序都有一个入口点。但是否一定以 main 开头呢? 其实,只要编译器能识别出入口点就可以,不必非要以 main 为标志。对于 Windows 程序就是这样,它就不使用 main作为入口点,而是换成了 WinMain 作为程序入口点。格式如下:
1 | int CALLBACK WinMain( |
实现消息中心函数 WndProc()
前面我已经介绍了 WndProc 是 Windows 程序的消息中心,所有的消息都要在这个函数中处理。如 窗口创建时发送的 WM_CREATE 消息,如果我们不处理它,Windows 操作系统就不会显示创建的窗口。
但 Windows 中有那么多消息,我们每个都处理岂不是要累死人?所以 Windows 很贴心的提供了一个API,就是 DefWindowProc 函数。该函数对所有的 Windows 消息都做了默认处理,如果我们很懒的话,可以将所有消息都交由它就好了。
有没有坐过山车的感脚?开始觉得很苦闷,突然又拨云见日了。嘿嘿!
1 | LRESULT CALLBACK WndProc( |
注册窗口类
我们在创建窗口之前要注册一个窗口类,它是干啥用的呢?就是告诉操作系统,我要创建个什么样子的窗口,是啥背景色,鼠标是啥样子的,程序叫啥名子等等。
有了这个窗口类,我们就可以创建不同样式的窗口了,这样是不是觉得很方便呢?当然,一般情况下我们都使用默认样式!
这个窗口类除了设置样式外,其实它更重要的作用是指定 WndProc 函数,也就是为 Window 程序指定 “消息处理中心”。消息中心是谁,完全是由 RegisterClass 说了算,它说消息处理中心是 WndProc 就是 WndProc,它说 ABC 那就是 ABC。
一般我们调用注册窗口的代码都长的像下面这样子:
1 | // 类名 |
创建窗口
创建窗口就比较简单了,高多少,宽多少,透明的还是非透明的,可显示还是不可显示,标题栏上要写啥字等等,这些都是由创建窗口
说了算。形式如下:
1 | // 创建窗口 |
显示窗口
窗口创建完了,还要主动调ShowWindows
函数让窗口显示出来,否则它是不会出来干活的。形式如下:
1 | // 显示窗口 |
循环处理,检索与分发消息
这部分工作是在 WinMain 函数中要做的事儿。在 WinMain 中写一个循环,不停的从系统消息队列中取消息。
如果此时没有消息,则该线被程阻塞,并将CPU资源释放;如果有消息,需要判断是不是退出消息?如果不是,使用 DispatchMessage 将该消息分配出去。如果是退出消息,则退出消息循环,程序结束。代码如下:
1 | void WinMan(...){ |
以上就是一个最简单的窗口 Window 程序。了解了上面这些知识,大家是不是觉得即使不用 MFC 也可以写出一个很不错的 Windows 程序呢?
重要函数详细介绍
WinMain
1 | int CALLBACK WinMain( |
- hInstance:句柄,就是一个内存地址,在该地址上有该程序的基本信息。
- hPrevInstance:总是NULL,没啥用。
- lpCmdLine: 用命令行启动时的命令,有兴趣的可以自己打印出来。
- nCmdShow:程序启动时的显示方式,是隐藏,还是显示,是最大化,还是最小化显示。
注册窗口
1 | typedef struct tagWNDCLASS { |
- style :设置窗口样式。可以不设置。
- lpfnWndProc :这个字段特别重要,设置消息处理函数,它是消息的中心。
- cbClsExtra :不用设置。
- cbWndExtra :不用设置。
- hInstance :窗口句柄,与WinMain中的一样。
- hIcon :窗口图标。如果是NULL,使用默认图标。
- hCursor :设置光标样式。可以不设置
- hbrBackground :设置窗口背景色。
- lpszMenuName:菜单名。如果为NULL说明没有菜单。
- lpszClassName:这个参数要提供,长度不超过 256。
创建窗口
1 | HWND WINAPI CreateWindow( |
- lpClassName : 与注册的类名子一致。
- lpWindowName :窗口标题栏名子。
- dwStyle :窗口外观样式。
- x :窗口起始位置 x。
- y :窗口起始位置 y。
- nWidth :窗口宽度。
- nHeight :窗口高度。
- hWndParent :父窗口,没有的话设置为NULL
- hMenu :窗口菜单,没有设置为NULL
- hInstance : 窗口句柄。
- lpParam :符加数据,没有设置为 NULL
小结
通过上面的介绍,我想你首先知道了Windows程序是由消息驱动的,真正负责消息处理的函数是 WinProc,它是在调用 RegisterClass 时指定的。通过 RegisterClass 我们还可以给窗口指定样式,并最终由 CreateWindow 创建出来。同时我们还可以总结出,通过 6 大步既可以创建出一个最简单的 Windows程序,这6步分别是:
- 设置入口点,WinMain。
- 创建 WinProc 函数。
- 注册窗口类。
- 创建窗口。
- 显示窗口。
- 循环处理,检索与分发消息
至此,一个Windows程序窗口已经展现在你面前了。 希望本文能对你有所帮助!
谢谢!