嵌入只有一个函数:SDL_CreateWindowFrom(hwnd)。
代价是嵌入之后 SDL 不再泵消息,SDL_WaitEvent 不能用了。
第二章讲过把 LibVLC 的画面嵌进 MFC 与 Qt,用的是 set_hwnd。这一节是同一件事,换成了 SDL。
思路完全一致:交出一个窗口句柄,让库在那块画布上画。
区别在「谁泵消息」这条线画在哪。
一、一个函数,一个句柄
三种宿主的句柄来源:
| 宿主 | 句柄从哪来 | 消息循环归谁 |
|---|---|---|
| MFC | GetSafeHwnd() / m_hWnd |
MFC |
| Qt | widget->winId() |
Qt |
| Win32 | CreateWindow 的返回值 |
你自己的 GetMessage |
第三列是全部要点:嵌入之后,SDL 只负责「画」,不再泵消息。
所以 SDL_WaitEvent 不能用了——它会跟宿主的消息循环抢消息,两边都卡。
正确的做法是「自己处理宿主的消息,再转给 SDL」:在 MFC 里重写 PreTranslateMessage,
在 Qt 里装事件过滤器,把键盘与鼠标事件翻译成 SDL_PushEvent 塞进去。
这跟第二章 LibVLC 那个坑是同一个: 嵌入之后「谁管消息」必须指定清楚,否则两边都在等,界面就死了。 区别是 VLC 有自己的线程、不太依赖宿主消息;SDL 的渲染与事件是跟窗口绑在一起的,所以更敏感。
二、尺寸和重绘要自己管
嵌入之后,窗口的尺寸由宿主决定,SDL 不会自动跟着变。
宿主一缩放,你得做三件事:
收到 WM_SIZE(或者 Qt 的 resizeEvent)。
SDL_SetWindowSize,或者干脆重建纹理。
重新算 RenderCopy 的目标矩形,否则画面还是旧尺寸。
不处理的表现很具体:画面还是原来的大小,右下角露出一块底色。 而且它看起来像「渲染没刷新」,其实是「尺寸没告诉它」。
「重建纹理」还是「只改目标矩形」,取舍很清楚:
只改目标矩形:纹理保持原分辨率,缩放由 RenderCopy 做。省一次分配,但放大后会糊。
重建纹理:按新尺寸重新分配。清晰,但每次缩放都要分配一次,拖动窗口时可能卡。
折中做法是「拖动过程中只改矩形,松手后再重建」——跟 04.03 那个「拖动时不 seek、松手才 seek」是同一个思路:把贵的操作推到「停下来」之后。
三、出错了怎么问它
Demo 里传了一个空句柄,SDL 返回空指针,错误串是「That operation is not supported」。 这句话信息量很低——它没说「句柄无效」,只说「不支持」。
原因在驱动上:dummy 驱动根本没有「从已有句柄创建窗口」这个能力, 所以报的是「这个操作不支持」而不是「参数错误」。
这暴露了 SDL 错误处理的一个普遍特点:返回空指针表示失败,原因放在 SDL_GetError() 里,
那个字符串经常只有一句话、指向不精确。
所以调试 SDL 的正确姿势是「把每个返回值和错误串都打出来」:不要写 if (!ptr) return;。
要写成「先打印 SDL_GetError() 再返回」。
这一行 printf 的投入产出比,比任何调试器都高。
最后补一个取舍:如果项目本身就是 MFC 或 Qt 的,值不值得为了显示视频引入 SDL?
如果只是「放一段视频」——不值得,QMediaPlayer 或 LibVLC 更省事。
如果要「自己控制每一帧怎么画」——值得,
因为 SDL 的纹理接口足够薄,而且第八章的 FFplay 就是这么做的。
四、动手 Demo:把 SDL 窗口塞进别人的窗口里
这段列出三种宿主的句柄来源与消息循环归属,再真跑一次「句柄不对」的情况, 看 SDL 到底给出什么样的错误信息。 结果那句话很值得看:它说的是「不支持」,不是「句柄无效」。
# 三种宿主的句柄来源
hwnd_mfc = "GetSafeHwnd()" # MFC
hwnd_qt = "widget->winId()" # Qt
# 嵌入:一个函数
win = sdl2.SDL_CreateWindowFrom(handle)
if not win:
print(sdl2.SDL_GetError()) # 一定要打出来
# 嵌入之后不能再调:会跟宿主抢消息
# sdl2.SDL_WaitEvent(...)
实跑输出:
--------------------------------------------------------------------
把 SDL 窗口塞进别人的窗口里
--------------------------------------------------------------------
**嵌入只有一个函数**:`SDL_CreateWindowFrom(hwnd)`。
**传进去一个宿主窗口的句柄,SDL 就在那块画布上画。**
宿主 句柄从哪来 消息循环归谁
-----------------------------------------------------------------------
MFC `GetSafeHwnd()` / `m_hWnd` MFC
Qt `widget->winId()` Qt
Win32 `CreateWindow` 的返回值 你自己的 `GetMessage` 循环
**第三列是全部要点**:**嵌入之后 SDL 只负责「画」,不再泵消息。**
**所以 `SDL_WaitEvent` 不能用了**——**它会跟宿主的消息循环抢消息,两边都卡。**
**真跑一次「句柄不对」的情况**,看它怎么说:
`SDL_CreateWindowFrom(NULL)` = **失败**
错误信息 = **That operation is not supported**
**「That operation is not supported」这句话信息量很低**,**它没说「句柄无效」,只说「不支持」。**
**因为在 dummy 驱动下根本没有「创建窗口自某句柄」这个能力。**
**这条正好演示了 SDL 的错误风格**:
**返回空指针表示失败,原因放在 `SDL_GetError()` 里,而那个字符串经常只有一句话、指向并不精确。**
**所以调试 SDL 的正确姿势是「先把每个返回值和错误串都打出来」**,**而不是盯着代码猜。**
**最后一个实践细节**:**嵌入之后尺寸由宿主决定**,
**宿主窗口一缩放,你要自己通知 SDL**——`SDL_SetWindowSize` 或者干脆重建纹理。
**不处理的表现是「画面还是原来的大小,右下角露底」**,**而 SDL 不会主动跟着宿主窗口变。**
「失败」加「That operation is not supported」这两行,是本节最好的教材: 它没有说「句柄是空的」,也没有说「参数无效」。
原因刚才讲过:dummy 驱动没这个能力。 但真机上也可能出这句话——比如你把一个「不是窗口」的句柄传进去, 或者那个窗口属于另一个进程。 这两种情况的错误串是一样的,所以只能靠自己排除。
这也解释了为什么 SDL 项目里到处都是 printf(SDL_GetError()):
不是程序员懒,是这个库的错误信息真的不够用。
反过来,这也是好习惯——
它至少告诉你「哪一步失败了」。
最后那句「嵌入之后不能 SDL_WaitEvent」是本章的收尾提醒:
嵌入不是「装进去就完了」,是「分工要重新划」。
宿主管消息、SDL 管画,这条线划不清,两边都会卡。
30 秒实验
- 算一下:宿主窗口从 640×480 拉到 1280×960,
RenderCopy的目标矩形该变成什么? - 判断一下:嵌入之后为什么不能用
SDL_WaitEvent?换成什么? - 判断一下:你的项目是 MFC 的,只需要放一段视频,该引入 SDL 吗?
常见误区
- 嵌入后继续用
SDL_WaitEvent。 它会跟宿主消息循环抢消息,两边都卡。 要自己处理宿主消息再转成SDL_PushEvent。 - 不处理宿主窗口缩放。 画面还是原来的大小,右下角露底。
要接
WM_SIZE去SDL_SetWindowSize或者改目标矩形。 - 写
if (!ptr) return;不打印错误。 SDL 的错误信息本来就少,再不打出来就只能靠猜。 - 以为嵌入就是「把窗口装进去」。 装进去之后消息循环、尺寸、重绘三件事都要重新分工。
自测三问(点开对答案)
1. 嵌入 SDL 窗口用的函数是什么? —— `SDL_CreateWindowFrom(hwnd)`,传宿主的窗口句柄。Qt 用 `winId()`、MFC 用 `GetSafeHwnd()`。 2. 为什么嵌入后不能用 `SDL_WaitEvent`? —— 因为消息循环归宿主了。`SDL_WaitEvent` 会跟宿主抢消息,两边互相等,界面就死了。 3. 宿主窗口缩放时要做哪三件事? —— 接 `WM_SIZE`、调 `SDL_SetWindowSize`(或重建纹理)、重算 `RenderCopy` 的目标矩形。第六章到此为止。这一章把「画」这一层讲完了: 对象、扩展库、YUV 上传、编译、嵌入。第七章回到 FFmpeg——这次不只是调用它,而是看清它的框架长什么样。
- ☐ (本章完,下一节 07.01 FFmpeg编解码框架及原理)