MinGW 编出来的 exe,缺的从来不是头文件和库——是运行时那三个 DLL。 编译通过只是第一步,能双击打开才算完。
前三节讲的是「怎么起进程」,这一节往回退一步: 你写的那个调用 ffmpeg 的程序,自己是怎么被编出来的。 用的是 MinGW(g++),跟 VS 那套不是一回事。
一、MinGW 与 MSVC 的两处不同
第一处:头文件与库的名字不一样。
MSVC 链接的是 .lib 导入库,MinGW 既可以直接链 .dll,也可以用 .a/.dll.a。
所以从 VS 项目抄过来的「附加依赖项」,在 MinGW 里要换个写法。
第二处:MinGW 自带一套 GCC 运行库,而这套运行库是动态的。
也就是说,你编出来的 exe 依赖 libgcc_s_seh-1.dll 与 libstdc++-6.dll。
这两样在 MinGW 的 bin 目录里,不在你的 exe 旁边,
所以「在我机器上能跑,拷给别人就报缺模块」。
VS 那边没这个问题,因为它默认静态链接运行库(/MT)。
这也是很多人「VS 里好好的,换 MinGW 就一堆缺 DLL」的真正原因——
不是代码变了,是运行库的链接方式变了。
二、编译命令的四个部分
一条能用的 MinGW 编译命令,拆开就是四段:
这一段 管什么
-IC:/ffmpeg/include 找得到头文件 -LC:/ffmpeg/lib 找得到库文件 -lavformat -lavcodec -lavutil 链哪几个库 -static-libgcc -static-libstdc++ 运行库塞进 exe
四段缺一不可,而且缺哪一段的报错完全不一样:
缺 -I 报「找不到头文件」,缺 -L 报「找不到 -lavformat」,
缺 -l 报「undefined reference」,缺最后一段什么都不报——
编得出来,跑起来才报缺 DLL。
最后这种最难查,因为编译阶段一路绿灯。
三、链接顺序不是小事
GNU 的链接器是从左到右扫描的:遇到一个库,就把当前还没解析的符号拿去里面找, 找完了就丢。所以被依赖的库必须写在依赖者的右边。
libavformat 依赖 libavcodec,libavcodec 依赖 libavutil,
正确的顺序就是 -lavformat -lavcodec -lavutil。
写反了,报的是一屏 undefined reference to av_...,
看着像代码写错、像库没链上,其实是顺序错。
这个坑有个很省事的自查方法:把所有 -l 参数按「谁依赖谁」从大到小排一遍。
排不出来就把最底层的 -lavutil 复制一份放到最后——
重复写同一个库不会报错,这是 GNU 链接器允许的。
四、运行时的三个 DLL
MinGW 编出来的程序,运行时可能要找这三个:
DLL 谁带来的 什么情况下必须
libgcc_s_seh-1.dll GCC 运行库 默认就要 libstdc++-6.dll C++ 标准库 用了 STL 就要 libwinpthread-1.dll 线程模型 静态链接也躲不掉
第三个最容易漏:很多人加了 -static-libgcc -static-libstdc++ 就以为万事大吉,
结果换了台机器还是报缺 libwinpthread-1.dll——
因为它属于 win32 线程模型那一层,得另加 -static 或 -Wl,-Bstatic。
另外一个更省事的办法:直接用 -static 全静态。
代价是 exe 变大(几 MB 起),好处是拷到任何一台 Windows 上都能双击打开。
做工具类程序,这个交换通常是划算的。
五、报错长什么样,先认下来
「缺 DLL」在程序里的样子,跟「找不到命令」完全不是一回事。
前者是系统在加载阶段失败,报的是「找不到模块」;
后者是你调 CreateProcess 时路径不对,报的是「找不到文件」。
这两种错经常被混为一谈,因为中文提示都很含糊。 记住一条分界线: 错误里提到具体 DLL 名字的,是运行库问题;提到 exe 路径的,是调用问题。
六、一句总结
MinGW 的三个门槛依次是:找得到头文件、链得上库、跑得起来。 前两个报错清楚,第三个最阴—— 因为它在编译阶段一点征兆都没有,只在你把 exe 拷到别人机器上时爆发。 所以 MinGW 项目的发布清单里,永远要有「目标机器上双击能不能打开」这一条。
七、动手 Demo:编译环境体检:从「有没有编译器」查起
本机没有装 MinGW,也没有 gcc/cl,所以这一节只能把「查」这件事做实。 查的顺序就是排查的顺序:先看有没有,再看缺什么。
import ctypes, shutil, subprocess
for exe in ("g++", "gcc", "cl", "clang++"):
print("%-8s %s" % (exe, shutil.which(exe) or "没有"))
try:
subprocess.run(["g++", "--version"], capture_output=True)
except FileNotFoundError as e:
print("直接调用 g++ 会抛:", type(e).__name__, e.errno)
try:
ctypes.CDLL("libgcc_s_seh-1.dll")
except OSError as e:
print("缺 DLL 时报的是:", str(e).split("(or")[0].strip())
实跑输出:
--------------------------------------------------------------------
12.1.4 MinGW 调用 FFmpeg 命令行
--------------------------------------------------------------------
**① 先体检:这台机器上到底有没有编译器**
找的是 找到了吗
----------------------------------------------------------
g++ 没有
gcc 没有
cl 没有
clang++ 没有
mingw32-make 没有
**PATH 上一共 36 个目录,其中 18 个里面真有 exe——**
**一个编译器都没有。**
**所以这一节只能讲机制。但机制比命令重要,**
**因为 MinGW 的麻烦九成不在编译,而在【编译完了跑不起来】。**
**② 「不是内部或外部命令」在程序里长什么样**
你做的事 系统给你的答复
------------------------------------------------------------------
直接调 g++ FileNotFoundError [WinError 2]
在命令行窗口敲 g++ 'g++' 不是内部或外部命令
**同一件事,两个说法:命令行窗口说「不是内部或外部命令」,**
**代码里抛 `FileNotFoundError`。**
**所以看到这个异常,第一反应不该是「代码写错了」,而是「PATH 里没有它」。**
**③ MinGW 编出来的 exe,旁边必须有三个 DLL**
DLL 谁带来的 少了会怎样
----------------------------------------------------------------------------
libgcc_s_seh-1.dll GCC 运行库 启动就报缺模块
libstdc++-6.dll C++ 标准库 同上,且报错不提 C++
libwinpthread-1.dll 线程模型 同上,`-static` 也躲不掉
**「缺 DLL」的报错在程序里是这个样子:**
加载的东西 系统的答复
----------------------------------------------------------------------
libgcc_s_seh-1.dll Could not find module 'libgcc_s_seh-1.dll'
**注意它说的是「找不到这个模块【或者它的某个依赖】」——**
**这句话把排查方向直接指歪了:你以为缺的是 libgcc,**
**其实常常是它的依赖(比如 vcruntime)不在。**
**④ 一条能用的编译命令,四个部分**
g++ main.cpp -o main.exe ^
-IC:/ffmpeg/include ^ REM 头文件在哪
-LC:/ffmpeg/lib -lavformat -lavcodec -lavutil ^ REM 库在哪、链接谁
-static-libgcc -static-libstdc++ REM 把两个运行库塞进 exe
**四个部分缺一不可:`-I` 管「找得到头文件」,`-L` 管「找得到库文件」,**
**`-l` 管「链哪一个」,最后那行管「运行时别再找 DLL」。**
**链接顺序也有讲究:`-lavformat` 要写在 `-lavcodec` 前面,**
**因为前者依赖后者——顺序反了,报的是一堆「undefined reference」,**
**看着像代码写错,其实是命令行顺序错。**
四行「没有」,说明这台机器压根没有编译器——所以后面全是机制,不是实测。
第二段说明「不是内部或外部命令」在代码里长什么样:FileNotFoundError。
第三段是缺 DLL 的报错原话:Could not find module ...。
两种错长得完全不一样,这就是分界线的实证。
30 秒实验
- 把
shutil.which换成where g++的输出:看命令行与程序两条路的说法差别。 - 用
dumpbin /dependents(VS)或objdump -p(MinGW)看一个 exe 依赖哪些 DLL:练一次就记住了。 - 给 MinGW 加
-static重编一次:看 exe 从几百 KB 变成几 MB。
常见误区
- 编过了就当完事。运行库缺失只在跑的时候才暴露。
- 把
-l顺序当无关紧要。GNU 链接器从左到右扫,顺序错了就是一屏 undefined reference。 - 只加
-static-libgcc -static-libstdc++就以为全静态。libwinpthread-1.dll还在。
自测三问(点开对答案)
1. MinGW 编的 exe 拷到别的机器为什么可能打不开? —— 缺 libgcc_s_seh-1.dll / libstdc++-6.dll / libwinpthread-1.dll。 2. `-lavformat` 为什么必须写在 `-lavcodec` 前面? —— GNU 链接器从左到右扫描,被依赖的库要放在右边。 3. 「找不到模块」与「找不到文件」分别指什么? —— 前者是 DLL 加载失败,后者是 exe 路径不对。- ☐ 12.1.4 完——下一节 12.1.5 Linux 系统下 C++ 调用 FFmpeg 命令行