☰
Windows下Qt5 MinGW编译libmodbus:从链接失败到跑通的完整指南
2026/10/18 21:36:19 网站建设 项目流程

简介:libmodbus 在 Windows 平台 Qt5 MinGW 环境下的测试工程,面向需要快速评估 libmodbus 可用性的 Modbus 通信应用开发者,完整演示了库文件在 Qt 项目中的配置、链接与基本调用方式。工程覆盖了建立 Modbus 主从连接、读写寄存器、串口或 TCP 通信等典型环节,可以帮助开发者避开 MinGW 环境下外部库编译和链接过程中的常见坑,从而把精力集中在业务逻辑上。压缩包仅 39KB,共 21 个文件,其中 9 个头文件与 4 个 C 源文件构成 libmodbus 相关代码,2 个 C++ 源码配合 1 个 UI 文件完成 Qt 测试界面,1 个 pro 工程文件便于 qmake 直接构建,另有 1 个说明文件和 3 个 URL 快捷方式记录参考文章,整体目录十分精简。已有 978 人学习浏览,适合作为一本可运行的入门范例;通过查看工程文件和源码,能直接了解 Qt5 项目如何链接第三方库、导出或调用 DLL,以及如何编写简单的 Modbus 测试界面,为后续功能扩展打下基础。

1. 为什么在 Windows 上编译 libmodbus + Qt5 MinGW 总在链接阶段翻车

把 libmodbus 拿到 Windows 的 Qt5 MinGW 环境里做测试,是我最近给一个跨平台工具加 Modbus 协议时踩过最深的一串坑。libmodbus 在 Linux 下走 autotools 一路很顺,换到 Windows 后,链接库叫法、串口命名、工具链 ABI 差异全冒出来了,稍微不注意就卡在“undefined reference”或“找不到 DLL”上,整个下午都在跟 CMake 生成器较劲。Qt5 本身在 Windows 上又同时存在 MSVC 和 MinGW 两套套件,选错编译套件比代码写错还要命。这篇文章就把我在某跨平台系统里从零配到最终跑通的完整过程写出来,环境、参数、坑、验证一条线走完,给同样卡在 libmodbus qt5 编译上的人一个能直接照抄的答案。

2. 环境准备:Qt 5.15.2、MinGW 8.1.0 与 CMake 的一次性配齐

在碰 libmodbus 源码之前,先把 Windows 侧的工具链理顺。我在这次测试里用的是 Qt 5.15.2 的 MinGW 版本,工具链是随 Qt 一起发布的 MinGW 8.1.0 64-bit,CMake 用 Qt 安装目录里自带的那份 3.16 以上版本。这三样必须匹配成一套,尤其是“Qt 是 MinGW 编的,编译器也要用 MinGW 的”,否则后面链接库的时候会出现各种看不懂的符号错误。

2.1 安装 Qt 5.15.2 的 MinGW 组件

Qt 从 5.15 开始在线安装器依然是主流方式。安装时不需要把 Qt 全部勾上,只勾两个地方:一个是Qt 5.15.2下的MinGW 8.1.0 64-bit,另一个是Tools下的MinGW 8.1.0 64-bit。如果你和我一样只在 Windows 上用,MSVC 2019 那套组件可以不装,省得后面在 Qt Creator 里切换套件时手滑选错。

我习惯把 Qt 装在默认的C:\Qt,因为路径短,后续写 .pro 和 CMake 都不容易出转义问题。装完后先去确认一下核心工具是否真的在:

ls C:/Qt/5.15.2/mingw81_64/bin/qmake.exe ls C:/Qt/Tools/mingw810_64/bin/g++.exe

这两个文件一个代表 Qt 库本身,一个代表编译工具链。如果第二个不存在,说明安装时没勾 Tools 里的 MinGW,后面 CMake 生成构建脚本时会找不到编译器。我在第一次装的时候漏掉过这步,结果 CMake 一直提示 “C compiler cannot create executables”,后来回头补装 Tools 组件才解决。

2.2 下载 libmodbus 源码包并解压

libmodbus 的源码我用的是官网发布页的 tar.gz 包,稳定版直接选 3.1.x 系列即可。也可以从源码仓库 git clone,两者源码内容一致,区别只是有没有.git目录。解压后放在D:\work\libmodbus,注意路径里不要带中文和空格,CMake 在 Windows 下对带空格路径的处理偶尔会抽风,这种玄学问题没必要主动去踩。

源码目录里主要看两个地方:src文件夹放着modbus.c、modbus-rtu.c、modbus-tcp.c,CMakeLists.txt 在根目录。Windows 下编译不需要碰configure和Makefile.am,那是 autotools 的产物。

cd D:/work/libmodbus ls src/modbus.c src/modbus-rtu.c src/modbus-tcp.c ls CMakeLists.txt

如果 CMakeLists.txt 不存在,说明你下载的是一个很老的 autotools 版本,建议换成 3.1.x 新版。新版对 Windows 的 CMake 支持已经比较成熟,省掉手工写 Makefile 的麻烦。

2.3 把 CMake 和 MinGW 放进当前窗口的 PATH

这一步不能省。libmodbus 的编译依赖cmake、g++、mingw32-make,它们其实都已经跟着 Qt 装好了,但 Windows 默认 PATH 里找不到。我一般单独开一个 cmd 窗口,只在当前会话里设置 PATH,不污染系统环境:

set PATH=C:\Qt\5.15.2\mingw81_64\bin;C:\Qt\Tools\mingw810_64\bin;C:\Qt\Tools\CMake_64\bin;%PATH% where g++ where cmake where mingw32-make

这三条where命令必须全部返回路径。如果mingw32-make找不到,说明 Qt 装的是 MSVC 版工具链目录名不对;如果cmake找不到,到 Qt 安装目录下找一下Tools\CMake_64\bin\cmake.exe,真实路径以它为准。每次重新打开 cmd 窗口都要重新执行一次 set,因为这个 PATH 只在那个窗口存活。

提示:不要在系统环境变量里永久加这些路径,尤其不要加在系统 PATH 最前面。否则其他依赖特定版本的软件可能被 Qt 的 DLL 干扰。

3. 编译 libmodbus:CMake 生成 MinGW Makefiles 与参数取舍

libmodbus 源码拿到手后,别急着打开 Qt Creator 去 qmake,得先把库本身编出来。Windows 下我优先用 CMake,而不是 autotools,因为 CMake 对 MinGW 的支持是显式声明生成器,错误信息也更直白。

3.1 CMake 参数:生成器、构建类型和安装路径

在源码根目录下建一个build子目录,所有临时文件都在里面,避免污染源码。执行 CMake 时最关键的是-G参数,这里必须写"MinGW Makefiles"。写错成MSYS Makefiles或者Unix Makefiles,CMake 会按另一套规则找 make,大概率直接失败。

cd D:/work/libmodbus mkdir build cd build cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=C:/local/libmodbus

-DCMAKE_BUILD_TYPE=Release表示编译优化级别,如果不指定,默认可能是空参数,生成的是无优化版本,测试结果多少受影响。-DCMAKE_INSTALL_PREFIX表示最终安装目录,我习惯统一放到C:/local/libmodbus,这样后面 Qt 工程引用头文件和库文件的路径清晰,不用在项目里塞一大堆../..相对路径。

这里还有一个隐藏参数值得提前盯住:-DBUILD_SHARED_LIBS。CMake 默认生成的库类型取决于项目里add_library的写法,libmodbus 属于纯 C 库,默认一般是静态库。静态库链接简单、部署不用带 DLL,测试阶段我强烈建议先用静态库;如果你非要动态库,就显式加-DBUILD_SHARED_LIBS=ON。两种方式编译流程一样,只是产物不同。

3.2 用 cmake --build 完成编译和安装

生成构建脚本之后,执行编译和安装:

cmake --build . --config Release cmake --install .

cmake --build会自动调用mingw32-make,不需要你手敲 make,也不需要在命令行里手动指定-j4,CMake 会按依赖关系逐个编译。

如果这一步报错,先看错误信息最后几行。常见的一种情况是编译器路径里带空格,CMake 解析失败,此时把源码目录里整个build文件夹删掉,重新从 cmake 开始。增量构建在 Windows 下偶尔会带着旧配置一起编,所以我遇到莫名其妙报错时都习惯这样清一次:

cd D:/work/libmodbus rm -rf build mkdir build cd build cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=C:/local/libmodbus cmake --build . --config Release

编译过程静默几秒到十几秒不等,没有任何输出反而是好事。多核心机器上如果想明确指定并行度,可以改成cmake --build . -j4,但一般情况下不写也能跑。

3.3 确认产物:头文件、静态库和导入库

安装完成后检查一下目录里是否出现了应有的文件:

ls C:/local/libmodbus/include/modbus ls C:/local/libmodbus/lib

头文件目录下应该有modbus.h、modbus-rtu.h、modbus-tcp.h、modbus-version.h。lib目录下如果是静态库,会看到libmodbus.a;如果是动态库,会看到libmodbus.dll.a和真正的libmodbus-5.dll(dll.a 只是导入库,不能直接执行)。

我在第一次编动态库时,以为有了 libmodbus.dll.a 就够了,结果 Qt 程序编译链接都通过,一运行就报缺 libmodbus-5.dll。所以静态库在测试阶段真的省心,先把它作为首选。

提示:如果你编的是静态库,后续 Qt 工程里不要再用-DBUILD_SHARED_LIBS=ON重新编一次,同一个工程里混用动态和静态库很容易造成符号重复定义。

4. 集成到 Qt5 工程:INCLUDEPATH、LIBS 与三种链接方式

libmodbus 编译安装完之后,下一步是把这个 C 库接进 Qt5 的工程里。这里最坑的是头文件路径和链接库命名的对应关系,搞错了在 Qt Creator 里会得到两套完全不同的报错。

4.1 先搞清楚 -lmodbus 在 MinGW 下会找什么库

Qt 的 .pro 文件里写LIBS += -lmodbus,MinGW 链接器会按照固定顺序去搜索几种文件:libmodbus.dll.a、libmodbus.a、modbus.lib等。也就是说,你在 CMake 里用-DBUILD_SHARED_LIBS决定生成的是静态还是动态,但 .pro 里写的都是-lmodbus,链接器会自动匹配。

但有一个隐蔽问题:如果C:/local/libmodbus/lib目录下同时存在libmodbus.a和libmodbus.dll.a,链接器优先找哪一个取决于具体顺序。为了避免不确定性,我一般只会保留一种库文件,测静态就把动态库文件挪走,测动态就把静态库挪走。

4.2 静态库链接方式:一条 .pro 直接跑通

这是我最推荐的测试方式。在 Qt Creator 里新建一个纯 C++ 控制台工程,或者沿用已有的 Qt Widgets 工程,把 .pro 文件里加上两行:

INCLUDEPATH += C:/local/libmodbus/include LIBS += -LC:/local/libmodbus/lib -lmodbus

INCLUDEPATH指向include,代码里引用头文件时写#include "modbus/modbus.h"。如果你把 INCLUDEPATH 写成C:/local/libmodbus/include/modbus,那么代码里就得写#include "modbus.h"。这两种写法都合法,但千万别混着写,否则 Qt Creator 的自动补全和编译器头文件搜索会打架。

LIBS 里的-L后面必须用正斜杠。Windows 原生路径反斜杠在 qmake 里会被当成转义处理,C:\local里的\l经常被解析成奇怪字符,这是我在多个项目里亲眼见过的翻车现场。

4.3 动态库链接方式:记得把 DLL 送到 exe 旁边

如果你非要动态库,链接写法跟静态库几乎一样,区别在于运行时。CMake 安装后,真正的libmodbus-5.dll在安装目录的bin文件夹里,lib目录下的libmodbus.dll.a只是导入库,不能直接加载。

ls C:/local/libmodbus/bin

在 Qt Creator 里点运行时,程序会从 exe 所在目录查找 DLL。如果找不到,最简单的办法是把 DLL 手动复制到构建输出目录debug/或release/。我后来嫌手动复制麻烦,直接在 .pro 里加了一个自动复制命令:

QMAKE_POST_LINK += $$quote(copy /Y C:/local/libmodbus/bin/libmodbus-5.dll $$OUT_PWD)

这个设置会在每次编译完成后自动把 DLL 拷到输出目录。不过它只对当前工程生效,换成其他工程还得再写一遍,所以我只在快速验证时用它。

4.4 源码直接编进 Qt 工程:不依赖外部库的另一条路

有些项目为了保持环境一致,不想让 libmodbus 以独立库的形式存在,而是直接把源码编进 Qt 工程。这条路不是不行,但有一个前置坑:libmodbus 源码里的modbus-version.h是 CMake 或 autotools 生成出来的,直接拿源码 src 目录编译会缺这个头文件。

我试过一次,做法是把编译生成的C:/local/libmodbus/include/modbus/modbus-version.h复制回源码src目录,然后在 .pro 里列出源文件:

INCLUDEPATH += $$PWD/../../libmodbus/src SOURCES += \ $$PWD/../../libmodbus/src/modbus.c \ $$PWD/../../libmodbus/src/modbus-rtu.c \ $$PWD/../../libmodbus/src/modbus-tcp.c

这样做的好处是最终 exe 里就直接包含协议栈,连 DLL 都不需要;坏处是每次换机器、换 Qt 版本,都要保证modbus-version.h和源码版本匹配。如果只是做测试,我依然推荐 4.2 的静态库方案,省事且不容易踩到源码内置依赖的坑。

5. 常见问题排查:从 undefined reference 到 COM 口打不开的四个坑

无论按哪种姿势集成,Windows + Qt5 MinGW 环境下总有几个高频问题绕不开。下面这五条是我实际踩过并在现场帮别人修过的,每条都按“现象 → 原因 → 解决”来写,方便对照。

5.1 链接报错 undefined reference tomodbus_new_rtu

现象:编译能过,代码里明明#include "modbus/modbus.h"也没报错,但链接阶段抛出一串undefined reference to modbus_new_rtu。

原因:绝大多数情况是 LIBS 里的路径写错了,最常见的是-L后面用了反斜杠。qmake 对C:\local\libmodbus\lib的解析会翻车,导致链接器根本没找到libmodbus.a。少部分情况是链接顺序问题,-lmodbus被放在其他依赖库前面,MinGW 链接器按顺序解析符号,库放太靠前时会认为符号未定义。

解决:先把 LIBS 改成LIBS += -LC:/local/libmodbus/lib -lmodbus,正斜杠必须用。然后在 Qt Creator 里执行一次“清除”再重新构建,别只点增量编译。如果还是报错,直接在命令行里用where libmodbus.a确认库文件真的在lib目录下,有些时候 CMake 安装目录和你想的不一样。

5.2 头文件路径坑:include 和 include/modbus 只差一层

现象:编译直接报fatal error: modbus/modbus.h: No such file or directory。

原因:libmodbus 安装后的头文件在include/modbus子目录下,但你 .pro 里的 INCLUDEPATH 写成了C:/local/libmodbus/include/modbus,代码里又按#include "modbus/modbus.h"去找,等于在include/modbus/modbus/modbus.h,当然找不到。

解决:统一做法:.pro里INCLUDEPATH += C:/local/libmodbus/include,源文件里写#include "modbus/modbus.h"。如果你喜欢直接把头文件目录指到位,那么源文件写#include "modbus.h"。两种方案都行,但团队协作时最好在工程说明里指定一种,不然换人接手就是一场灾难。

5.3 运行时弹窗提示缺少 libmodbus-5.dll

现象:编译链接全部成功,Qt Creator 一点运行,exe 直接报错“找不到 libmodbus-5.dll”,或者在命令行启动时提示缺失。

原因:你链接的是动态库导入库libmodbus.dll.a,程序加载时按动态链接去找真正的 DLL,而 DLL 不在 exe 所在目录,也不在 PATH 里。这和依赖库本身没关系,是 Windows 动态链接的基本规则。

解决:把C:/local/libmodbus/bin/libmodbus-5.dll复制到 exe 输出目录。Qt Creator 的构建目录通常是工程目录下的debug或release,手动复制一次就能跑。如果你希望每次自动处理,用前面第 4.3 节提到的QMAKE_POST_LINK复制命令。想彻底省事,还是直接改为静态库。

5.4 用错编译器套件:MSVC 的 Qt 链接 MinGW 编译出来的库

现象:在 Qt Creator 里选的是“Desktop Qt 5.15.2 MSVC2019 64bit”,头文件和源码都能编过,但链接时出现非常奇怪的unresolved external symbol,而且符号名还带一堆下划线前缀,或者直接提示“machine type mismatch”。

原因:MSVC 和 MinGW 的 C++ ABI 完全不同,MinGW 编译出来的.a库无法被 MSVC 链接器消费。即使 libmodbus 是纯 C 库,MinGW 的导出符号和 MSVC 也不兼容。Qt Creator 里如果不小心把构建套件切成 MSVC 版 Qt,就必然翻车。

解决:打开 Qt Creator 左侧“项目”面板,把构建套件切换成Qt 5.15.2 MinGW 64bit。如果下拉列表里没有,说明第二章节里的 Tools MinGW 组件没装全,回到安装器补装后重启 Qt Creator。这个问题的判断特征是“头文件能找,链接就挂”,以后遇到直接先检查套件,别在 .pro 里折腾。

5.5 COM10 以上端口打不开:Windows 串口命名规则

现象:程序里modbus_new_rtu("COM10", 9600, 'N', 8, 1)调用后,modbus_connect返回 -1,打印modbus_strerror提示系统找不到指定文件或设备打不开。

原因:Windows 对串口设备的名字处理在 COM1 到 COM9 之间是直接映射,但从 COM10 开始,系统内部需要\\.\COM10形式才能识别。直接传COM10,CreateFile 会把它当作相对路径或文件来打开,结果自然失败。这个特性折磨过很多从 Linux 迁移过来的开发者,因为 Linux 上只有/dev/ttyS0那套命名,没有这个坑。

解决:在代码里构造串口号时,主动拼上\\.\前缀。Qt 的 C++ 写法:

int portNumber = 10; QString portName = QString("\\\\.\\COM%1").arg(portNumber); modbus_t *ctx = modbus_new_rtu(portName.toStdString().c_str(), 9600, 'N', 8, 1);

这里\\\\.\\在字符串里实际表示\\.\,注意别写少了。如果你用的 libmodbus 版本较新,某些版本内部会自动处理 COM10,但我测试时发现不同版本行为不一致,保险起见还是自己在调用前拼好。

6. 验证跑通:虚拟串口 + Qt5 控制台程序读从站寄存器

编译链接都过了,但 libmodbus 到底能不能读写,还是得用一个最小案例跑一遍。我在 Windows 上习惯用虚拟串口工具创建一对互联的 COM3 和 COM4,然后让 Modbus 从站模拟器挂在 COM4,Qt 程序在 COM3 上发 RTU 请求,两端能对上就算验证通过。

6.1 虚拟串口与从站模拟配置

先创建虚拟串口对,把 COM3 和 COM4 关联起来。然后在从站模拟器里选择 COM4,地址设为 1,波特率 9600,数据格式 8N1,在要保持寄存器起始地址上预先填入几个值,比如地址 0 到 3 分别填 100、200、300、400。虚拟机上的主站程序要连接 COM3,参数一致。

创建一个 QCoreApplication 控制台工程,链接方式和前面第四章一致,源码用下面这段最小读取逻辑:

#include <QCoreApplication> #include <QDebug> #include "modbus/modbus.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); modbus_t *ctx = modbus_new_rtu("COM3", 9600, 'N', 8, 1); if (ctx == nullptr) { qCritical() << "modbus_new_rtu failed"; return 1; } modbus_set_slave(ctx, 1); if (modbus_connect(ctx) == -1) { qCritical() << "modbus_connect failed:" << modbus_strerror(errno); return 1; } uint16_t regs[4] = {0}; int rc = modbus_read_registers(ctx, 0, 4, regs); if (rc == -1) { qCritical() << "read failed:" << modbus_strerror(errno); } else { qDebug() << "read" << rc << "registers:" << regs[0] << regs[1] << regs[2] << regs[3]; } modbus_close(ctx); modbus_free(ctx); return 0; }

这段代码里,modbus_new_rtu构造 RTU 上下文,modbus_set_slave告诉库要访问从站地址 1,modbus_read_registers一次读取 4 个寄存器。运行后如果一切都对,Qt 控制台会打印出read 4 registers: 100 200 300 400。如果返回 -1,先看modbus_strerror(errno)里说的 errno,多半是串口占用或从站参数不匹配。

从那以后我每次在 Windows 上做串口协议栈相关工程,都会强制把“编译第三方库、建最小工程、虚拟串口验证”这条流程走一遍,提前把工具链和 DLL 部署问题暴露在桌面上,而不是等到现场设备连不上才去怀疑协议栈。这套环境搭起来确实有点繁琐,但 libmodbus 本身只是个 C 库,难的是工具链匹配和 Windows 的部署习惯,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询