前面的文章中已经对 Windows 窗口 的概念进行了简单的介绍,并且写了一个简单的 HelloWorld 程序,不过显然那和咱们平时见到的窗口不太一样,而这章的内容就是讲解一个基本的 Windows 窗口程序创建的全部过程,这是也是接下来游戏开发的基础,因为所有的游戏都属于 Windows 操作系统上的窗口程序,即使全屏游戏也一样如此。

整个窗口创建过程虽然复杂,但是万年不变,并且有属于自己的模板,一开始如果你无法理解程序的每一个细节,特别是对 Windows 窗口的运行机制或者 API 接口不熟悉的情况下,可以直接使用固定的程序模板开始接下来的学习,随着深入用到的功能越来越多,自然而然的就会慢慢掌握。

抛开细节,创建一个窗口大致可以分为四个步骤:

  1. 注册窗口类。可以简单理解为根据自己对窗口样式的需求定义一个包含窗口信息的结构体。

  2. 创建窗口。可以简单理解为使用上一步注册的窗口结构体定义一个变量。

  3. 实现窗口过程函数。可以简单的理解定义一个让操作系统执行的函数。

  4. 消息循环。可以简单的理解为执行一个 while 循环,循环的逻辑主要是监视用户对窗口的动作和行为,然后将结果通过上一步的窗口过程函数通知给用户。

上面的 简单理解 并不一定是操作系统中真正的逻辑,只是为了方便初学者的理解简化类比而来。

在真正进入接下来的窗口创建教程之前,这里需要对你学过的C语言知识做一些考察和回顾,请回答下面的问题:

  • 你是否知道什么是指针?
  • 你是否知道什么是结构体?
  • 你是否知道什么是函数?
  • 你是否知道什么是函数指针?
  • 你是否知道什么是回调函数?

如果你不知道上面的前三点请直接关闭网页回去继续学习C语言,如果你知道前四点并使用过那么可以看一看下面的内容,如果你全部知道并使用过可以直接跳过下面的内容直接开始下一节的学习了!

因为这套教程的基础是你必须掌握C语言的语法知识,所以我不准备介绍前三点内容,这里直接讲什么是函数指针,以及什么是回调函数。

1. 函数指针(Function Pointer)

我们都知道指针变量存储的内容就是一个地址信息,而指针的类型确定了指向内容的类型。

int *p 指向的是一个整形(int)变量。 Point *p 指向的是一个结构体(Point)变量。

既然这样那指针为什么不能指向一个函数呢?答案是肯定的。

//定义函数 cm_to_inches
double cm_to_inches(double cm) {
    return cm / 2.54;
}

//将函数变量 cm_to_inches 赋值给 func1 变量
double (*func1)(double) = cm_to_inches;

//输出结果
printf("%f\n", func1(15.0));

上面的代码中 func1 就是一个函数指针,cm_to_inches 这个函数的声明和函数变量 *func1 是一致的,所以二者是可以赋值的,就像两个整形赋值一样,可以类比下面的代码:

//定义整形 int
int cm_to_inches = 15;

//将整形变量 cm_to_inches 赋值给 func1 变量
int func1 = cm_to_inches;

//输出结果
printf("%d\n", func1);

有些人觉得 double (*func1)(double) = cm_to_inches; 即长又诡异,所以使用了另一种别名的方式进行替换:

typedef double (*FUNC1)(double);

//定义函数 cm_to_inches
double cm_to_inches(double cm) {
    return cm / 2.54;
}

//将函数变量 cm_to_inches 赋值给 func1 变量
FUNC1 func1 = &cm_to_inches;

//输出结果
printf("%f\n", func1(15.0));

如果你对 typedef 不是很熟悉,请立刻回去翻一翻语法书,并感受下面的三条语句:

typedef int myinteger;
typedef char *mystring;
typedef void (*myfunc)();

等价于:

myinteger i;   // is equivalent to    int i;
mystring s;    // is the same as      char *s;
myfunc f;      // compile equally as  void (*f)();

请仔细体会最后的两个语句!

2. 回调函数(Callback Function)

如果说 函数指针 是语言相关的话,回调函数 就是一个语言无关的概念了。回调函数这个名字起的很好,可以明显感受到它有点 “返过来调用的意思”,它还有一个被大众熟悉的称号:“好莱坞法则”。

don’t call us, we’ll call you.

其实回调函数以及不是单纯的手段了,它已经上升到了一种架构的层次,这个回调手法其实被多种设计模式所使用,特别在异步编程中,函数本身是一阶公民的语言更是如此。JavaScript 就是重灾区,甚至产生了 “回调地狱” 这种神奇的 “意大利恶魔” !

callback

回调函数到底是什么?其是你可以把两个词拆开来看:回调函数 。回调函数首先是一个你需要自己实现内部逻辑的一个 函数,函数内部可以处理不同状态下的多种逻辑策略,最后将函数的调用权交给第三方(操作系统、程序插件等等),当第三方检测到某些状态发生的时候,会通过执行该函数通知你,这个通知的过程叫做 回调

如果你还是不是很明白,咱们就举一个现实中常见的例子:

假如你在自己的卧室写作业,你母亲在厨房做饭。这时候,你感觉很饿,所以想知道什么时候才能做好饭。你现在有两种手段处理这个事情:

  • 你暂时停止写作业,快速跑去厨房问你妈。如果没做好,你回来继续写,然后隔一段时间,再去问你妈,你不断轮询这一过程,直到饭做好为止。

  • 你暂时停止写作业,快速跑去厨房问你妈。和上次不同,你交代你妈,做好饭后喊你,然后你回去继续写作业,直到你妈做好饭喊你为止。

注意到差别了么?第一种在程序中用 轮询 来实现,第二种程序中用 回调 来实现。

所以总结来说:回调是替代轮询的一种策略方法。之所以叫做回调函数,是因为回调策略一般和函数本身是绑定关系,而C语言中,函数指针就是实现回调策略的一种技巧,这种技巧常被称为 回调函数

在 Windows 编程中,操作系统通过 回调函数 告诉你发生了什么事件,例如鼠标移动、键盘响应、窗口最大化、程序退出、计算机休眠等等,你只需要定义一个回调函数,并将这个回调函数的指针交给操作系统即可,按照这个回调函数的功能,该函数也被称为 窗口过程函数,表示窗口在运行过程中 Windows 不断调用的函数。