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 秒实验
- 把项目从 x64 改成 Win32,不换 lib,看报什么错(应该是
LNK2038或类型冲突)。 - 算一下:一个 SDL 2 的小版本升级(2.30 到 2.32),头文件与 dll 差两个小版本, 这种情况下哪些 API 可能不可用?
- 判断一下:程序在开发机上能跑,拷到同事机器上「一闪就没」,先查什么?
常见误区
- Debug 配 Release 的 lib。 报
LNK2038或者链接成功但一运行就崩。带d后缀的是 Debug 版。 - x64 程序配 x86 的 dll。 启动报
0xc000007b,这个错误码是「位数不一致」的通用记号。 - 少链
SDL2main.lib。 报「找不到 WinMain」,因为你写的main被 SDL 换成了WinMain。 - 依赖
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 的界面里。答案是一个函数,代价是消息循环得重新分工。
- ☐ (本节完,下一节 06.06 将SDL 2的窗口嵌入MFC或Qt的界面中)