🎬 第六章 SDL 2开发库及高级应用
📄 一句话概括

SDL 的编译错误几乎全在一件事上:三样东西必须对齐——位数、运行库、版本。 这四类错误都跟代码无关,但报错信息看起来都像代码有问题。

📝 本节和前面几节的关系

前面四节的代码都能跑,因为它们跑在 Python 绑定上,配置这一层被人替你做了。 到了 C++ 里自己配,问题全冒出来。 这一节把四类错误一次认全,顺便解释 Demo 里那两个不一样的版本号到底是什么回事。

一、两个版本号,谁高谁低有讲究

SDL 有两套版本号:编译期的来自头文件(SDL_MAJOR_VERSION 等宏), 运行期的来自 dll(SDL_GetVersion)。

Demo 里是 2.30.10 对 2.32.10,运行期更高,这是安全的: 新 dll 兼容旧头文件编出来的代码。

反过来就有问题了:用新头文件编、跑到旧 dll 上,代码调用了旧 dll 里不存在的函数, Windows 上的表现是「程序一闪就没」,连错误框都不稳定出现。

判断顺序要记住:「昨天还能跑今天不能跑」→ 先查 dll 是不是被换了; 「换台机器就不能跑」→ 先查那台机器上的 dll 版本。 这两种场景占了 SDL 部署问题的一大半。

还有一条更隐蔽的:Windows 会从 exe 所在目录和 PATH 里找 dll,而 PATH 的顺序不确定。 所以机器上装了多个版本的 SDL2.dll 时,你到底加载了哪个是随机的——排查这种问题的唯一办法是把 dll 拷到 exe 旁边,用哪个就放哪个。

二、四类错误,一张表认全

现象 含义 九成的原因
LNK2019 未解析外部符号 声明有、实现没链上 附加依赖项漏了 lib
LNK1104 无法打开文件 那个 lib 根本不存在 库目录指错了
LNK2038 检测到不匹配 两边编译选项不一致 Debug/Release 混了
启动报 0xc000007b 位数不一致 x64 程序配了 x86 的 dll

这四行有个共同点:报错信息都在说「代码里某个符号」,而真正的问题在配置里。 所以判断方向应该是「先看属性页,再看代码」。

LNK2038 那一行值得单独说: 它的完整信息里会带 RuntimeLibrary 和 _ITERATOR_DEBUG_LEVEL 两个词, 意思就是「你这边用 Debug 运行库、那边用 Release 运行库」。 这是 Debug/Release 混用的典型症状, 而它的表现往往不是链接失败,是「链接成功但一运行就崩」。

还有一条 SDL 特有的:SDL2.dll 必须跟 SDL2main.lib 配套。 少链 SDL2main 会报「找不到 WinMain」——因为在 Windows 上 SDL 把 main 换成了 WinMain, SDL2main 就是那个转接层。 这个错最迷惑人:你明明写了 main,链接器却说找不到入口。

三、把它变成一张检查清单

与其记错误码,不如记「三样对齐」: 位数对齐:项目是 x64,lib 与 dll 都得是 x64。 运行库对齐:Debug 配 d 后缀的 lib,Release 配不带后缀的。 版本对齐:头文件与 dll 是同一套,运行期不低于编译期。

三样都对齐了,SDL 的编译几乎不会出问题。 反过来,任何一样不对都会报出一堆看起来像「代码错」的信息。

还有三个操作层面的习惯,能省很多时间: 把 dll 拷到 exe 旁边,不依赖 PATH。 用属性表 .props 存配置,Debug 与 Release 共用一份。 第一次跑之前先打印 SDL_GetVersion,确认加载的是哪个 dll。

第三条是本课反复强调的那个思路:「先让程序自己说出环境,再谈功能」。 因为环境错误的报错信息,永远指向错误的方向。

四、动手 Demo:版本对了才跑得起来

这段打印编译期与运行期两个版本并比较,再把四类常见错误列成表。 两个版本号不一样这件事,本身就是最好的教材——它证明「头文件」和「dll」是两个独立的东西。

v = sdl2.SDL_version()
sdl2.SDL_GetVersion(ctypes.byref(v))

hdr = (sdl2.SDL_MAJOR_VERSION,
       sdl2.SDL_MINOR_VERSION,
       sdl2.SDL_PATCHLEVEL)
run = (v.major, v.minor, v.patch)
print(hdr, run, hdr == run)

# 结论:运行期不低于编译期就行
# 反过来(新头 + 旧 dll)会「一闪就没」

实跑输出:

--------------------------------------------------------------------
版本对了才跑得起来
--------------------------------------------------------------------
**先看这台机器上的两个版本**:
  编译期头文件:**2.30.10**
  运行期 dll:**2.32.10**
  一致吗:**不一致**

**不一致是常态,而且不一定出问题**——SDL 2 的小版本之间 API 是兼容的,
**只要运行期不低于编译期就行**。**这里运行期(2.32.10)高于编译期(2.30.10),所以没问题。**

**反过来就有问题了**:**用新头文件编出来的程序,跑到旧 dll 上,会因为找不到符号而启动失败。**
**这个错在 Windows 上表现为「程序一闪就没」,连错误框都不稳定出现。**

**VS 里链接期/运行期最常见的四种错,一张表认全**:

  现象                         含义                    九成的原因
  --------------------------------------------------------------------------------
  `LNK2019` 未解析外部符号     声明有、实现没链上      附加依赖项漏了 lib
  `LNK1104` 无法打开文件       那个 lib 根本不存在     库目录指错了
  `LNK2038` 检测到不匹配       两边编译选项不一致      Debug/Release 混了
  启动报 0xc000007b            位数不一致              x64 程序配了 x86 的 dll

**这四行的共同点是「都跟代码无关」**,**但报错信息看起来都像代码有问题。**
**所以判断顺序应该是先看配置、再看代码**,
**尤其是「昨天还能跑,今天不能跑」这种场景**——**那是配置被改了,不是代码被改了。**

**还有一条 SDL 特有的**:**`SDL2.dll` 必须跟 `SDL2main.lib` 配套。**
**少链 `SDL2main` 会报「找不到 WinMain」。**
**因为在 Windows 上 SDL 把 `main` 换成了 `WinMain`**,**`SDL2main` 就是那个转接层。**
**这个错最迷惑人**:你明明写了 `main`,链接器却说找不到入口。

这里「不一致」不是故障,是常态。 Python 扩展是用某个版本的 SDL 头文件编的,而实际加载的 SDL2.dll 来自另一个包——两件事互不知情。

判断规则只有一句:运行期不低于编译期。 因为 dll 是「实现」,头文件是「约定」,新实现满足旧约定没问题,旧实现满足不了新约定。

四类错误那张表里最该记的是 0xc000007b:它是「位数不一致」的通用错误码, 而且它不只出现在 SDL 里——任何 x64 程序加载了 x86 的 dll 都是这个码。 看到它,第一反应就该是「查位数」。

最后那句「你明明写了 main,链接器却说找不到入口」要留个印象: 它说明「入口点」这件事是可以被库替换的。 SDL 在 Windows 上用自己的 WinMain 接管了流程,再由 SDL2main 转回你的 main。 少链那个 lib,转接层就没了。

30 秒实验

  1. 把项目从 x64 改成 Win32,不换 lib,看报什么错(应该是 LNK2038 或类型冲突)。
  2. 算一下:一个 SDL 2 的小版本升级(2.30 到 2.32),头文件与 dll 差两个小版本, 这种情况下哪些 API 可能不可用?
  3. 判断一下:程序在开发机上能跑,拷到同事机器上「一闪就没」,先查什么?

常见误区

  1. Debug 配 Release 的 lib。 报 LNK2038 或者链接成功但一运行就崩。带 d 后缀的是 Debug 版。
  2. x64 程序配 x86 的 dll。 启动报 0xc000007b,这个错误码是「位数不一致」的通用记号。
  3. 少链 SDL2main.lib。 报「找不到 WinMain」,因为你写的 main 被 SDL 换成了 WinMain。
  4. 依赖 PATH 找 dll。 机器上有多个版本时加载哪个不确定,把 dll 拷到 exe 旁边最稳。
自测三问(点开对答案) 1. 编译期版本与运行期版本,谁高谁低才安全? —— 运行期不低于编译期才安全。新 dll 满足旧头文件的约定没问题; 反过来代码会调用旧 dll 里不存在的函数。 2. `0xc000007b` 通常是什么原因? —— 位数不一致——x64 程序加载了 x86 的 dll,或者反过来。 这是 Windows 的通用错误码,不只 SDL 会出现。 3. 为什么少链 `SDL2main.lib` 会报「找不到 WinMain」? —— 因为 SDL 在 Windows 上把入口换成了 `WinMain`, `SDL2main` 是那个把它转回你的 `main` 的转接层。

编译通了,最后一个问题很实际:SDL 的窗口能不能塞进 MFC 或 Qt 的界面里。答案是一个函数,代价是消息循环得重新分工。

相关笔记