
如果你在 Windows 上做 Qt 开发又希望给界面加上三维可视化能力那 VTK 基本是绕不过去的一块硬骨头。尤其是项目里已经用了 MINGW 工具链却要编译 VTK-8.1.0 的时候网上的教程要么含糊其辞要么只讲 MSVC 版本真正从零讲清楚 MINGW 流程的少之又少。这篇文章把我自己在 Windows 10 下用 Qt 5.12.3 MINGW 7.3.0 源码编译 VTK-8.1.0 的完整过程、报错记录和最终接入 Qt 项目的经验一次写完希望能帮你省掉几天的折腾时间。1. 为什么非得自己编译MINGW 和 MSVC 的库“水火不容”1.1 官方预编译包都是 MSVC 版很多刚接触 VTK 的朋友第一反应是去官网下载现成的二进制包这想法没错VTK 官方确实提供编译好的安装包。但问题在于这些二进制安装包几乎清一色是用 MSVCMicrosoft Visual C工具链编译出来的它们面向的是 Visual Studio 用户而不是 MINGW 用户。假如你手上装的是 MINGW 版 Qt也就是 Qt 官方带mingw73_64前缀那套环境然后直接去链接用 MSVC 编译出来的 VTK 库链接阶段马上就会遇到一大片undefined reference或 LNK 错误。刚开始你可能怀疑是库没配好反复检查路径后来才发现根本原因不在路径而在工具链本身。这也是为什么网上很多“为什么我配置了 VTK 但还是编译不过”的提问最后答案都指向同一个方向MSVC 编出来的 C 库MINGW 基本是没法直接拿来用的。反过来也一样MINGW 编出来的库MSVC 也认不了。1.2 ABI 差异是罪魁祸首C 没有统一的二进制接口标准。MSVC 和 GCC 在底层实现上有几个关键差异名称修饰规则不同、标准库实现不同、异常处理模型不同。这意味着同一个类在两套编译器眼里它的构造函数、成员函数、虚函数表排布都可能长得完全不一样。打一个直白的比方你用 MSVC 编译的库相当于一份用“浙江方言”写的说明书MINGW 这边的程序是用“广东方言”写的两边虽然都是说中文但关键术语对不上自然无法协作。由于这些差异发生在编译和链接的最底层不是用户在上层能通过配置绕过去的所以唯一的办法就是换同一套编译器编译所有依赖。1.3 编译前你需要接受的三个事实在你准备开始编译之前有三件事最好先想清楚。第一VTK 源码编译需要的时间不短。即使关闭了大量不用的模块只保留渲染、Qt 支持等核心部分单机四核处理器也要二十到四十分钟。如果你选择全模块默认编译一个小时以上是常态。第二编译过程中大概率会出现一两个奇怪的错误。这不代表你的操作有问题而是 VTK 8.1.0 这个版本发布于 2017 年之后的编译器环境、Qt 版本、CMake 版本都发生了不少变化出现兼容性警告、编译告警是正常现象。第三编译 VTK 8.1.0 本身不是目的接入你自己的 Qt 工程才是目的。所以不要只盯着编译成功那一瞬间还要留出时间处理运行时的 DLL 问题。这一步才是真正容易让人崩溃的地方后面我会专门讲。2. 环境和版本配对选对工具链能少踩一半坑2.1 我的环境清单先列一下我这边的实际环境你可以直接对标项目版本操作系统Windows 10 专业版 64 位QtQt 5.12.3官方在线安装器安装MINGWQt 自带的 MinGW 7.3.0 64-bitCMake3.16.5CMake GUI 版本VTK8.1.0 官方源码包这里有个细节值得注意Qt 5.12.3 的安装器里自带的编译器是 MinGW 7.3.0 64-bit位于C:\Qt\Qt5.12.3\Tools\mingw730_64这个版本和 VTK 8.1.0 的源码兼容性表现最好也是我测试下来最稳定的组合。2.2 为什么是 Qt 5.12.3 MinGW 7.3.0关于版本选择我想多说几句。很多教程会让你用 Qt 5.9.x 或者 Qt 5.15.x但实际上这两个选择都有各自的坑。Qt 5.9.x 自带的是 MinGW 5.3.0这个编译器版本太老对 C 11/14 的标准支持不够完整而 VTK 8.1.0 的部分模块用到了较新的 C 特性编译时很容易报std::to_string不存在、C11 is needed之类的错误。你需要费时间去修源码或者加补丁完全不值得。Qt 5.15.x 及更高版本的问题相反官方安装器不再直接内置 MinGW你需要自己去 MinGW-w64 官网下载编译器。这样一来编译器版本不可控而且 Qt 5.15 默认使用 Qt6 时代的一些新编译选项和 VTK 8.1.0 搭配时会发生一些莫名其妙的 CMake 检测错误。所以最终我选择了中间地带 Qt 5.12.3。它自带 MinGW 7.3.0编译器足够新能完整编译 VTK 8.1.0同时它还是 Qt5 时代的经典版本和 VTK 8.1.0 的支持列表完全对得上。如果你想照抄配置这个组合是最省心的。2.3 目录规划和 PATH 环境变量安装完所有工具之后先别急着打开 CMake先把目录和环境变量处理干净。源码目录、构建目录、安装目录这三者的路径都不能包含中文和空格。我见过有人在F:\软件\VTK\构建目录下编译结果 CMake 或者 make 在某个环节直接报路径错误这类问题排查起来非常痛苦。我自己的目录规划是D:\VTK\VTK-8.1.0 源码解压目录 D:\VTK\vtk-build 构建目录 D:\VTK\VTK-install 安装目录接下来把 MinGW 的 bin 目录加入系统 PATH。打开“系统属性 - 环境变量”在用户变量里找到Path把C:\Qt\Qt5.12.3\Tools\mingw730_64\bin添加到最前面。之所以要提前加是因为我们待会要在命令行里直接运行mingw32-make这个程序依赖 gcc、g 等一系列工具它们都在同一个 bin 目录下。加完之后打开一个 cmd 窗口验证一下输入gcc --version如果能看到版本信息说明环境变量生效了。这一步配置不对后面 CMake 配置阶段就会直接卡在“找不到 make 程序”这个错误上。3. CMake 里的一次性正确配置3.1 源码目录和构建目录的约定打开 CMake GUI上面两个输入框先填好Where is the source code:D:/VTK/VTK-8.1.0Where to build the binaries:D:/VTK/vtk-build这里必须强调一点不要直接在源码目录里构建。VTK 源码很大构建过程会产生大量中间文件如果全部堆在源码目录里后续想清理、想重新配置都会非常麻烦。新建一个独立的 build 目录是 CMake 的推荐做法也是我多年来一直坚持的规范。填写完路径后CMake 会弹出一个选择编译器的窗口。这里要选MinGW Makefiles然后点击 “Next” 指定 C 和 C 编译器。C 编译器选gcc.exeC 编译器选g.exe它们都在上面那个 MINGW bin 目录下。3.2 关键选项逐个过进入 CMake 配置界面后第一件事是勾选右上角的Grouped和Advanced。Advanced尤其重要因为后面我们要改的VTK_QT_VERSION默认是隐藏的不勾选根本看不到。接下来按照下面几个步骤来配置。第一步在搜索框输入CMAKE_PREFIX_PATH。这个变量是告诉 CMake “Qt 安装在哪”。点开它把 Qt 5.12.3 的 MINGW 库目录填进去也就是C:/Qt/Qt5.12.3/5.12.3/mingw73_64。注意路径要用正斜杠反斜杠在 CMake 的配置脚本里有时会被当作转义字符容易出错。第二步在搜索框输入CMAKE_INSTALL_PREFIX把它改成D:/VTK/VTK-install。这是最终安装目录我们后面用到 include、lib、bin 都在这个目录下找。第三步搜索CMAKE_BUILD_TYPE设置为Release。这是老生常谈了Debug 编译的 VTK 库又大又慢日常开发用 Release 完全够了。如果你确实需要调试 Qt VTK 代码后期可以再单独编一版 Debug但初次编译不要选 Debug时间成本太高。第四步搜索VTK_QT_VERSION把它从默认值改成5。这个选项决定了 VTK 编译时是去检测 Qt4 还是 Qt5。VTK 8.1.0 的年代比 Qt5 普及稍早一些有些环境默认还会去找 Qt4不主动改成 5 就会走到错误的分支上去。第五步搜索VTK_Group_Qt个别版本会显示为VTK_GROUP_QT用搜索框输QT就能找到。把这个选项勾上它的作用是启用所有和 Qt 关联的模块包括最核心的Module_vtkGUISupportQt和Module_vtkGUISupportQtOpenGL。这两个模块是 VTK 嵌入 Qt 界面的关键少了它们你连QVTKOpenGLWidget这个类都找不到。到这里最核心的配置已经完成。你可以先点击一次Configure如果顺利通过再继续处理模块裁剪。3.3 按需裁剪模块VTK 默认会打开非常多的功能模块包括 Web、MPI、影像处理、并行计算等等。这些模块对于只用 Qt 做三维数据显示的人来说基本上都用不上反而会拖长编译时间、增加出错概率。我的建议是保留这几个组组选项设置原因VTK_Group_RenderingON渲染是核心必须保留VTK_Group_QtONQt 支持必须保留VTK_Group_ImagingOFF不做影像处理就关掉VTK_Group_StandAloneOFF减少示例和测试代码VTK_Group_WebOFF和 Web 无关就关掉VTK_Group_MPIOFF没装 MPI 就关掉在修改这些选项时你可能会发现有些模块的开关状态会相互联动。比如关掉VTK_Group_Imaging后会有几个相关的Module_vtkImaging*自动变灰这是正常的依赖关系导致的不需要强行干预。裁剪完之后再次点击Configure。这次过程会稍长因为 CMake 会检查可用的库和依赖控制台和 GUI 底部会滚动大量日志。如果看到红色高亮的CMake Error不要立刻放弃先看一下具体信息八成是路径或者 Qt 检测的问题下面我会说。3.4 Configure 和 Generate 期间的报错处理我帮你总结了三种最常见的报错以及我当时的处理方式。报错一Could NOT find Qt5 相关组件这个错误的典型文本是Could NOT find Qt5Widgets (missing: Qt5Widgets_DIR)解决办法就是检查CMAKE_PREFIX_PATH是否指向了 Qt 的 MINGW 库目录。很多人容易把这里填成C:/Qt/Qt5.12.3这是错的。CMAKE_PREFIX_PATH应该指向到包含lib/cmake/Qt5Widgets的层级也就是C:/Qt/Qt5.12.3/5.12.3/mingw73_64。改完路径后记得删除缓存重新 Configure否则 CMake 会记住旧路径。报错二Could not find a make program文本一般是CMake Error: Generator: execution of make failed这个几乎可以肯定是 MINGW 的 bin 目录没有加入 PATH。回到上文第 2.3 节重新检查环境变量然后关掉当前 cmd 和 CMake GUI重新打开再试。报错三Policy CMP0025 或其他 Policy 警告这类Policy开头的警告通常不会中断编译可以暂时忽略。它们只是 CMake 在提示你相关模块使用了过时的 CMake 写法VTK 8.1.0 源码里存在这类问题很正常不影响编译结果。所有配置项没有红色报错之后点击GenerateCMake 会在构建目录里生成Makefile文件。看到生成成功说明 CMake 这一步完成了接下来进入真正的编译阶段。4. 开编mingw32-make 的完整实战4.1 第一跑多线程编译打开命令提示符进入构建目录cd /d D:\VTK\vtk-build然后执行编译命令。这里要注意Windows 下 MINGW 的 make 工具名字是mingw32-make而不是make。这点和 Linux 环境不同很多从 Linux 转过来的人会在这一步卡住。多线程参数-j建议设置为核心数加一。我的机器是四核八线程所以用mingw32-make -j8第一次编译时终端会刷出大量编译日志。看到Building CXX object...这种行不断滚动就对了。这里有两点经验一是编译过程中如果出现某个.cpp文件编译错误最好先把终端日志往上翻定位到最早的红色error:位置而不是看最后的输出因为最后一行往往只是中止位置不一定是根因。二是不要开-j过大的值。十二线程、十六线程虽然理论上更快但在 Windows 上偶尔会因为竞争问题频繁报错重启反而更慢。实测八线程是个稳妥值。4.2 编译错的几个经典案例MH 编译期间会遇到几个有代表性的错误我把它们单独拿出来说。案例一std::to_string is not a member of std这个错误在 Qt 5.9.x MinGW 5.3.0 的环境下非常常见。原因就是 GCC 5.3.0 对 C11 的支持还不完整。解决办法有两个升级 MinGW 到 7.3.0 以上或者在 CMake 里给CMAKE_CXX_STANDARD手动设置成 14。如果你用的是我推荐的 Qt 5.12.3 MinGW 7.3.0 组合这个错误基本不会出现。案例二error: snprintf was not declared in this scope这个错误有时出现在 VTK 的某些第三方依赖模块中。原因是 Windows 环境下snprintf函数的声明在不同头文件间存在差异。最简单的处理办法是关掉那个报错模块或者给 CMake 添加-D_CRT_SECURE_NO_WARNINGS定义。不过更多时候这个错误出现在某些没有被裁剪干净的测试模块里关掉对应模块问题就消失了。案例三内存不足导致的链接失败VTK 编译后期会进行大量静态库和动态库的链接。如果你的机器内存小于 8G或者后台开了多个大型程序链接阶段可能会报collect2.exe: error: ld returned 5 exit status这通常不是代码问题而是内存不够。我当时的做法是临时关闭 Chrome、关闭杀毒软件后台扫描重新执行一次mingw32-make -j8。已经编译过的源文件不会被重新编译链接阶段会继续从断点走完不用整锅重来。案例四某个模块编译失败但其他模块正常VTK 的构建系统是按模块进行构建的比如vtkRenderingQt这些 Qt 相关模块如果编译失败错误信息会停留在某个具体文件上。这种时候先确认是不是 Qt 版本问题。如果是回到 CMake 重新检查VTK_QT_VERSION和CMAKE_PREFIX_PATH改完之后不要重新编译整个工程直接在构建目录里执行mingw32-make -j8 vtkGUISupportQt单独编译目标模块效率会高很多。4.3 install 与产出验证整个编译过程完成后还需要执行安装命令把编译产物集中到之前设置好的安装目录mingw32-make install这一步会把几百个头文件、静态库、动态库和 cmake 配置文件统一拷贝到D:/VTK/VTK-install下。实际上你完全可以直接用构建目录里的产物但安装目录结构更清晰“include / lib / bin”三层分明后续配置 Qt 工程时引用起来非常方便。安装完成后可以到安装目录下验证一下产出D:\VTK\VTK-install\include\vtk-8.1下应该能看到大量.h文件。D:\VTK\VTK-install\lib下应该有libvtkCommonCore-8.1.a、libvtkGUISupportQt-8.1.dll.a一类文件。D:\VTK\VTK-install\bin下应该有大量vtk*.dll文件。这三个目录只要有内容说明 VTK 编译和安装已经成功。此时你手上已经有了一套完整的 MINGW 版 VTK 库接下来就可以接入 Qt 工程了。5. 接入你自己的 Qt 工程5.1 pro 文件怎么写假设你已经有一个 Qt Widgets Application 工程打开.pro文件在末尾追加以下内容INCLUDEPATH D:/VTK/VTK-install/include/vtk-8.1 LIBS -LD:/VTK/VTK-install/lib \ -lvtkCommonCore-8.1 \ -lvtkCommonDataModel-8.1 \ -lvtkCommonExecutionModel-8.1 \ -lvtkRenderingCore-8.1 \ -lvtkRenderingOpenGL2-8.1 \ -lvtkInteractionStyle-8.1 \ -lvtkGUISupportQt-8.1这里有三个容易踩的坑。第一库名写法。MINGW 环境下VTK 生成的库文件有两种扩展名.a是静态链接库.dll.a是动态库的导入库。-l参数会同时搜索这两种形式所以你只需要写去掉lib前缀和扩展名后的名字比如libvtkCommonCore-8.1.dll.a对应-lvtkCommonCore-8.1。第二链接顺序。VTK 的两个模块之间也有依赖关系如果出现undefined reference且错误指向 VTK 内部函数大部分情况是链接顺序不对。GCC 的链接器从左到右解析符号所以基础模块要放在后面。上面示例里的顺序已经把vtkCommonCore放在最后这是比较稳妥的排法。第三Debug 和 Release 模式要区分。如果你在 Qt Creator 里选择 Debug 模式构建链接的就是 Qt 的 debug 库而你的 VTK 是 Release 编译的。这种情况下虽然通常也能跑起来但偶尔会碰到运行期崩溃。最省事的做法是在“工具 - 选项 - Kits”里为这个工程单独设置 Release 构建模式统一用 Release 库。5.2 一个可直接跑通的最小 Demo为了验证 VTK 和 Qt 集成是否正确我建议你新建一个只有基础窗口的工程把下面的代码放进去跑一遍。这个 Demo 会在窗口中显示一个 3D 圆锥体。在main.cpp中写入#include QApplication #include QMainWindow #include QSurfaceFormat #include QVTKOpenGLWidget.h #include vtkConeSource.h #include vtkPolyDataMapper.h #include vtkActor.h #include vtkRenderer.h #include vtkRenderWindow.h #include vtkAutoInit.h VTK_MODULE_INIT(vtkRenderingOpenGL2) VTK_MODULE_INIT(vtkInteractionStyle) int main(int argc, char *argv[]) { QSurfaceFormat::setDefaultFormat(QVTKOpenGLWidget::defaultFormat()); QApplication app(argc, argv); QMainWindow mainWindow; mainWindow.resize(800, 600); QVTKOpenGLWidget *vtkWidget new QVTKOpenGLWidget(mainWindow); mainWindow.setCentralWidget(vtkWidget); vtkNewvtkConeSource cone; vtkNewvtkPolyDataMapper mapper; mapper-SetInputConnection(cone-GetOutputPort()); vtkNewvtkActor actor; actor-SetMapper(mapper); vtkNewvtkRenderer renderer; renderer-AddActor(actor); renderer-SetBackground(0.3, 0.4, 0.6); vtkWidget-GetRenderWindow()-AddRenderer(renderer); mainWindow.show(); return app.exec(); }需要注意QSurfaceFormat::setDefaultFormat必须在QApplication创建之前调用这是 VTK 官方文档明确要求的。如果不加这一行部分显卡驱动下渲染窗口会出现黑屏或闪烁。VTK_MODULE_INIT宏在 VTK 8.1.0 里需要包含vtkAutoInit.h才能生效它负责在程序启动时初始化渲染模块。如果你用的是高版本 VTK可能改成了vtkModule.h但 8.1.0 这个写法是没问题的。5.3 运行时 DLL 与 0xc000007b代码编译通过程序一启动却弹出错误框The application was unable to start correctly (0xc000007b)这个错误在 Windows 下非常经典原因绝大多数情况下是“64 位和 32 位混用”或者“MSVC 编译的 DLL 混入了 MINGW 程序”。具体到我们的场景最可能的原因是程序运行目录里混入了从别处拷贝来的 MSVC 版 VTK DLL。解决思路是这样的先把D:/VTK/VTK-install/bin下的所有vtk*.dll拷贝到程序的可执行文件旁边。不要试图只拷几个自己认为用得到的 DLL因为 VTK 模块之间存在复杂依赖关系你搞不清哪个模块依赖了哪个最省事的方法是全量拷贝。验证是否真的是 DLL 混用可以用 MINGW 自带的工具查看 DLL 依赖objdump -p your_app.exe | grep DLL Name或者在命令行里进入D:\VTK\VTK-install\bin执行objdump -p vtkCommonCore-8.1.dll | grep DLL Name看输出的依赖项里是否有MSVCP140.dll、VCRUNTIME140.dll这类 MSVC 运行库。如果有说明这个 DLL 是 MSVC 版它就不应该出现在 MINGW 程序的运行目录里。另外Qt 程序还需要用windeployqt工具把 Qt 自己的运行时依赖补齐windeployqt your_app.exe出来的文件列表如果还报缺 DLL就用Dependency Walker这类工具继续排查。一般来说0xc000007b 排查到这一步都能定位到根因。6. 把经验总结成可复用的原则6.1 CMakeCache 的坑编译完成之后如果你的相关配置发生变化比如换了 Qt 版本、换了安装目录再重新 Configure 时经常会遇到一些奇怪的异常行为。这是因为构建目录下有一个CMakeCache.txt文件它记录了第一次 Configure 时的所有变量值之后你再修改界面上的选项CMake 会优先信任 Cache 里的旧值。所以只要你换了 Qt、换了解压路径、换了 MINGW 版本最干净的做法是直接删掉整个 build 目录重新配置。不要试图在旧 cache 基础上调整因为那些隐藏的依赖关系已经写死在 cache 里了手动清理很容易遗漏。我当时就是因为“省时间”在旧目录里反复改结果改了一下午都没通过删掉重建 20 分钟就搞定。这个教训希望你不要再经历一遍。6.2 Debug 与 Release 匹配VTK 编译时选择的CMAKE_BUILD_TYPE会决定它链接的 Qt 库是 debug 还是 release 版本。假如你编译的是 Release 版 VTK那么在 Qt Creator 里构建自己的工程时最好也用 Release 模式。反过来如果你要调试 VTK 源码需要单独编一份 Debug 版 VTK。这里有一个很容易被忽视的点Debug 版 VTK 库和 Release 版 VTK 库的文件名虽然相同但它们的 ABI 不兼容。如果你把 Debug 版 VTK 的 DLL 拷贝到 Release 版程序目录下程序会直接崩溃或者报“无法定位程序输入点”。所以 DLL 拷贝时一定要确认版本来源目录不要从两个不同编译目录里混着拷。6.3 要不要升到 VTK 9按照标题写的是 8.1.0所以这次全程围绕 VTK 8.1.0 展开。但我必须提醒一句如果你做的是全新项目没有历史包袱直接考虑 VTK 9 会更好。VTK 9 对 Qt5 和 Qt6 的支持更规范CMake 配置也简化了很多不再需要手动调VTK_QT_VERSION编译过程顺滑不少。但如果你手上有一批已经基于 VTK 8.1 API 写的代码或者依赖的第三方库绑定了 VTK 8.x 版本那老老实实按本文流程走就是最合理的选择。VTK 8.1 和 VTK 9 在 API 上有一些破坏性变更比如vtkRenderWindow的访问方式、vtkNew的某些用法迁移工作量并不是一个周末能搞定的。6.4 一个拷贝 DLL 的效率技巧最后分享一个小技巧。每次编译完工程把 VTK 的 DLL 拷贝到输出目录这一步非常频繁手动操作很容易漏。我习惯在自己的工程.pro文件里加一行后处理命令让 qmake 在链接完成后自动拷贝QMAKE_POST_LINK $$quote(cmd /c xcopy /Y /D \D:\\VTK\\VTK-install\\bin\\*.dll\ \$$OUT_PWD\\debug\\\)这行命令会在每次构建完成后把 VTK 安装目录下的所有 DLL 同步到工程的 debug 输出目录。/D参数表示只拷贝比目标文件新的文件所以增量构建时拷贝速度极快不会让你每次都在等待拷贝中浪费时间。如果你用的是 Release 模式把路径里的debug改成release即可。这个技巧我自己用了很久能少做很多机械劳动。