使用 SDL 和使用原始 Windows API 创建的过程基本一致,不同之处在于 SDL 做了一定程度上的简化。例如,注册窗口创建窗口的过程合并为一个 SDL_CreateWindow 函数,消息循环 也从 GetMessage、DispatchMessage 变为了 SDL_PollEvent 函数,原有的窗口过程也被封装成了事件消息,接下来就简单的介绍一下 SDL 创建窗口的基本过程。

1. WinMain 入口函数

首先和创建普通的 Windows 程序一样,创建一个 Win32 桌面程序。流程可以参考第一个 Windows 项目中介绍步骤,先确定入口函数:

//main.c

#include <Windows.h>

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR lpCmdLine, INT nCmdShow)
{
    return 0;
}

2. 初始化 SDL

在使用 SDL 之前,需要初始化 SDL 的子系统,并指定需要使用哪些子模块。具体的接口如下:

int SDL_Init(Uint32 flags)
void SDL_Quit(void);

这两个函数就是 SDL 库用来初始化和注销的两个函数,其中 flags 代表需要初始化子系统的标记位:

SDL_INIT_TIMER timer subsystem
SDL_INIT_AUDIO audio subsystem
SDL_INIT_VIDEO video subsystem; automatically initializes the events subsystem
SDL_INIT_JOYSTICK joystick subsystem; automatically initializes the events subsystem
SDL_INIT_HAPTIC haptic (force feedback) subsystem
SDL_INIT_GAMECONTROLLER controller subsystem; automatically initializes the joystick subsystem
SDL_INIT_EVENTS events subsystem
SDL_INIT_EVERYTHING all of the above subsystems
SDL_INIT_NOPARACHUTE compatibility; this flag is ignored

这些标记位之间可以使用或运算组合。对于初学者,这里推荐直接无脑使用 SDL_INIT_EVERYTHING 宏,这个宏是所有这些标记位的综合。

#define SDL_INIT_EVERYTHING ( \
                SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
            )

将初始化代码加入到 main.c 文件:

//main.c

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR lpCmdLine, INT nCmdShow)
{
	int ret = SDL_Init(SDL_INIT_EVERYTHING);
	if (ret != 0) {
		return -1;
	}

	SDL_Quit();

	return 0;
}

3. 创建 Windows 窗口

接着步骤就是创建窗口,这里可以使用 SDL 库自带的 SDL_CreateWindow 函数:

SDL_Window* SDL_CreateWindow(const char* title,
                             int         x,
                             int         y,
                             int         w,
                             int         h,
                             Uint32      flags)

这个函数的参数非常简单,title 代表的窗口标题,x、y、w、h 分别代表着窗口的位置和大小,最后这个标记位代表着一些常用的窗口设置,例如全屏、最小化、焦点等内容。

默认情况下,你可以将 flags 标记位设为 0,创建窗口代码如下:

SDL_Window* pWin = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);

和创建相对应的,就是销毁:

void SDL_DestroyWindow(SDL_Window* window)

参数 windows 就是 SDL_CreateWindow 返回的指针变量。程序中可以这样使用:

if (pWin) {
	SDL_DestroyWindow(pWin);
}

这里有一个地方需要注意,在 SDL 中,默认的字符集是 UTF8,所以如果你想显示中文标题,需要转换字符编码,因为默认情况下 vs2019 在中文系统中使用的是 GB2312 字符集。

4. 消息循环

在 SDL 创建窗口的过程中,不需要设置窗口回调函数,SDL 已经将其封装到了内部,你可以在消息循环的代码中直接获取你关注的消息,具体可以通过 SDL_PollEvent 函数获得:

int SDL_PollEvent(SDL_Event* event)

其中如果队列中存在事件,则函数返回 1,参数 event 中存放事件的具体内容。在原来的消息循环中,我们需要不断的通过 GetMessage 获取消息,然后通过 TranslateMessage、DispatchMessage 函数转换分派消息,SDL 已将将这些所有的过程都封装到了 SDL_PollEvent 函数中:

int quit = 0;
SDL_Event evt;

while (!quit) {
	if (SDL_PollEvent(&evt)) {
		if (evt.type == SDL_QUIT) {
			quit = 1;
		}
	}
	else {
		//TODO: 渲染窗口
	}
}

其中 SDL_QUIT 代表程序退出事件,当收到这个消息的时候,直接退出循环销毁窗口,注销 SDL退出程序即可。

5. 代码案例

整个窗口创建过程的代码如下:

//main.c

#include <Windows.h>
#include "SDL.h"

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR lpCmdLine, INT nCmdShow)
{
	int ret = SDL_Init(SDL_INIT_EVERYTHING);
	if (ret != 0) {
		return -1;
	}

	SDL_Window* pWin = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
	if (pWin == NULL) {
		return -2;
	}


	int quit = 0;
	SDL_Event evt;

	while (!quit) {
		if (SDL_PollEvent(&evt)) {
			if (evt.type == SDL_QUIT) {
				quit = 1;
			}
		}
		else {
			//TODO: 渲染窗口
		}
	}


	if (pWin) {
		SDL_DestroyWindow(pWin);
	}
	
	SDL_Quit();

	return 0;
}