说实话把 ZXing C 用到自己的 Windows 工程里是我一直觉得有点绕的事。明明网上已经有现成的二进制包可以下载但一旦牵扯到工业扫码、视频流识别、内网离线部署这些真实场景最终的库还是要自己用 CMake 严格构建一遍才放心。这篇文章专门讲怎么在 VS2022 上完成 ZXing C 的 CMake 配置和构建把配置参数、构建命令、链接方式和排坑思路一次讲清楚适合已经在写 C 或者正准备给项目加二维码/条码识别能力的开发者。1. 为什么需要自己动手构建 ZXing C1.1 ZXing C 到底能解决什么问题ZXing C 是经典开源条码库 ZXing 的 C 移植版本主要做两件事从图像里识别出条码/二维码内容以及把字符串反向生成对应的码图。读码部分覆盖了 QR Code、Data Matrix、Aztec、PDF417 这些常见二维码也支持 EAN-13、EAN-8、UPC-A、UPC-E、Code 39、Code 93、Code 128、ITF 和 Codabar 这类一维码。对很多业务来说前面那一两行“支持格式”就是选型的关键依据因为不同行业的码制差异很大物流面单偏 DATAMATRIX零售标签偏 EAN/UPC设备巡检和电子票务则常年和 QR Code 打交道。一个库能覆盖这么多码型意味着你不用在项目里同时引入好几个专门的小算法库维护成本直接降下来。它的输入也很直白喂进来一张图像的像素数据它返回解析结果。你可以喂从文件解码出来的位图也可以直接喂工业相机的灰度帧甚至喂一块自己手工拼接的内存缓冲区。这点在 Windows 原生开发里特别实用因为 C 工程往往已经用了图像采集 SDK 或者自研的图像管线数据本来就是裸像素正好可以省去来回转格式的开销。1.2 使用场景和选型逻辑我接触过的实际场景大致分成两类。一类是桌面端的读码小工具用户拖一张截图进去就出结果这种场景对二进制体积不敏感但对“识别率”要求高因为用户拿来的图可能是手机拍的、打印的、反光的样样都有可能。另一类是自动化设备上的常驻进程工业相机拍完图经过灰度化、二值化、倾斜校正等预处理之后才交给解码器。这类进程长时间运行要求库稳定、可预测、不能有莫名奇妙的内存抖动而且最好所有代码用同一个运行时库链接避免跨模块分配内存再跨模块释放。这两类场景其实都指向同一个结论用源码自己构建一个定制过的 ZXing C而不是随便拉一个预编译包回来凑合用。自建库的好处是你能准确知道这个二进制是用什么编译器、什么运行库、什么优化级别做出来的。对于长期维护的工程项目来说这种“确定性”比省半小时构建时间重要得多。1.3 直接使用二进制包为什么不够有人可能会问NuGet、vcpkg 里不是有现成包吗确实有装起来也快但遇到过几次实际问题之后我就学乖了。第一预编译包的运行时库默认是 /MD也就是动态 CRT如果整个产品线为了部署方便统一用 /MT 静态 CRT你接入 ZXing 时链接器就会报 LNK2038 之类的运行时冲突。第二预编译包一般把读码、写码、测试、示例全都编进去了体积和依赖不可避免偏大而有些内网项目恰恰要求最小裁剪只留 QR 解码。第三一旦出问题时你可能连个带调试信息的库都找不到没法跟进原生函数栈。所以我的建议很直接如果你的项目是长期维护、对发布物有明确要求的 Windows C 工程那花费一两个小时把 ZXing C 从源码通过 CMake 构建一次非常值得。接下来就说具体怎么做。2. 构建前的准备版本、组件、源码2.1 VS2022 的最小安装组合VS2022 的安装要比很多人大得多但构建一个库其实不需要全装。在安装器里勾选“使用 C 的桌面开发”工作负载就够了里面包含 MSVC 编译器、Windows SDK、CMake 工具和 MSBuild。如果没有特别需要不建议再勾一堆 .NET 或 UWP 组件纯属浪费时间。打开“单个组件”页面可以顺手确认几个关键项MSVC v143 生成工具、Windows 10/11 SDK、C CMake 工具。装了这些之后系统命令行里就能直接用 cmake 命令。构建 ZXing C 对编译器的要求是支持 C17VS2022 默认工具集 v143 完全没问题不需要额外切换语言标准。还有一点容易被忽略先确认 CMake 版本。VS2022 自带 CMake但有些项目源码对最低 CMake 版本有要求。你可以在命令行输入cmake --version看一眼如果版本偏低建议单独装一个较新的 CMake并把它的目录放到 PATH 最前面。构建 ZXing C 这种中型库CMake 版本太老不是不能用但碰到新语法和策略改动时排查成本会很高。2.2 源码获取和目录规划获取源码我用的是传统方式从 ZXing C 的官方源码仓库克隆。这里有个经验仓库务必放到一个纯英文、没有空格、没有中文拼音混排的路径下比如D:\dev\zxing-cpp。Windows 下 CMake 偶尔会在处理中文路径时出一些莫名其妙的编码问题报错还不直观干脆从源头避开。目录规划上我的习惯是采用外部构建也就是源码目录和构建目录分离。比如D:\dev\zxing-cpp # 源码目录 D:\dev\zxing-cpp\build\vs2022-x64 # 构建目录这样做的理由很简单源码树保持干净随时可以换一个构建目录重新配置如果从 MSVC 切换到 MinGW或者从 Debug 换到 Release 的独立多配置构建都不会污染源码。另外构建目录建议放在源码内部但明显命名为build的子目录这样清理也方便需要推到 Git 时直接在忽略列表里写build/即可。2.3 几个关键的 CMake 配置项ZXing C 的 CMake 配置项不算多但有几个会直接影响你的产出物形态。我整理了一个常用开关速查表配置项作用常见取值BUILD_SHARED_LIBS控制构建动态库还是静态库ON生成 DLLOFF生成静态库ZXING_BUILD_READERS是否编译读码模块默认 ONZXING_BUILD_WRITERS是否编译写码模块默认 ONZXING_BUILD_TESTS是否编译测试工程默认 OFF通常不需要打开ZXING_BUILD_EXAMPLES是否编译官方示例建议 OFF省时间ZXING_EXAMPLES_USE_OPENCV示例是否依赖 OpenCV后续如果不做图像 I/O 实验关闭即可CMAKE_INSTALL_PREFIX指定安装目录建议单独设一个目录比如D:\dev\zxing-cpp\installCMAKE_MSVC_RUNTIME_LIBRARY指定 MSVC 运行时方式MultiThreadedDLL对应 /MD这里面BUILD_SHARED_LIBS是控制静态还是动态的关键。如果你只在自己的 exe 里用静态库最省心如果多个模块都要用同一个库或者想让它独立升级动态库更灵活。第一次构建建议先静态库省去拷贝 DLL 的麻烦。3. 实操两种配置方式与完整构建命令3.1 用 CMake GUI 生成 VS 工程如果你平时习惯图形界面第一步先打开 CMake GUI在“Where is the source code”里填源码目录在“Where to build the binaries”里填构建目录比如D:/dev/zxing-cpp/build/vs2022-x64。点击 Configure第一次配置会弹个窗让你选生成器。选择Visual Studio 17 2022Optional platform for generator 选x64然后点 Finish。CMake 会开始探测编译器、解析配置项。这一步如果报错十有八九是源码目录里有非英文字符或者 VS2022 的 C 组件没装全。配置完成后CMake GUI 会显示一组变量。你可以在这里直接改BUILD_SHARED_LIBS改成OFFZXING_BUILD_TESTS改成OFFZXING_BUILD_EXAMPLES改成OFFCMAKE_INSTALL_PREFIX设置成你想要安装的目录然后再次点击 Configure 保存新值最后点 Generate。生成完成后目录里会出现.sln解决方案文件双击就能在 VS2022 里打开。这种方法对不熟悉命令行的人最友好而且能直观看到所有开关适合第一次接触 CMake 的开发者。3.2 用命令行完成配置、构建和安装命令行做法适合把操作流程脚本化因为我发现只要同一个人操作多次迟早会忘掉某次到底勾了哪些参数。写成一个固定命令换台机器也能复现。我的推荐组合是cmake -S . -B build/vs2022-x64 \ -G Visual Studio 17 2022 \ -A x64 \ -DCMAKE_INSTALL_PREFIXD:/dev/zxing-cpp/install \ -DBUILD_SHARED_LIBSOFF \ -DZXING_BUILD_TESTSOFF \ -DZXING_BUILD_EXAMPLESOFF \ -DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreadedDLL解释一下这些参数的意思。-S .表示源码目录就是当前所在目录-B build/vs2022-x64指定构建目录。-G指定生成器为 VS2022 的 MSBuild 工程-A x64指定架构。-DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreadedDLL是强制所有目标使用动态 CRT也就是 /MD这样和你工程里统一的运行时一致。配置完成之后执行构建cmake --build build/vs2022-x64 --config Release --parallel--config Release在使用 VS 生成器时用来选择多配置中的一个。--parallel会让 MSBuild 自动按 CPU 核数并行编译效果等同于打开大型工程的/m参数能明显缩短构建时间。构建成功后想把头文件和库文件收集到一个干净的目录里执行cmake --install build/vs2022-x64这样D:/dev/zxing-cpp/install下就会生成include和lib目录后续给别的工程引用时只需要指到这个路径。3.3 构建产物与文件结构构建完成之后去构建目录里看一眼你会发现在Release子目录下生成了若干文件。如果是静态库核心产物是ZXing.lib如果是动态库会有ZXing.dll、导入库ZXing.lib以及一个可能的ZXing.pdb调试符号文件。这里顺带说一句命名细节因为 CMake 的 target 名是ZXing不管静态还是动态生成的库文件名可能都叫ZXing.lib。不同的是静态库是一个完整的代码库动态库的ZXing.lib则是个导入库。所以我后来在自动化脚本里会先用dumpbin /headers ZXing.lib看一眼是不是 DLL 对应的导入库防止认知错位。安装目录里的 include 结构通常长这样install/include/ZXing/ ReadBarcode.h WriteBarcode.h BarcodeFormat.h DecodeHints.h ImageView.h Result.h ...这些就是对外公开的头文件你写代码时通过#include ZXing/ReadBarcode.h引用即可。但注意这只是我常见的一个布局不同版本的头文件数量可能略有差异以你实际安装出来的为准。4. 把 ZXing 接进你自己的项目4.1 链接目标与头文件布局构建库本身只是第一步把它接进自己的工程才是大多数人的目的。CMake 里最优雅的用法是直接找库find_package(ZXing REQUIRED) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE ZXing::ZXing) target_include_directories(my_app PRIVATE D:/dev/zxing-cpp/install/include )如果你的工程和 ZXing 源码在同一个 CMake 项目里也可以直接add_subdirectory(vendor/zxing-cpp) target_link_libraries(my_app PRIVATE ZXing::ZXing)两条路都行。第一种适合 ZXing 作为独立三方库预先构建好的场景第二种适合你想让整个解决方案一起编译的场景。我个人的建议是优先用find_package因为构建和使用的边界清晰以后换 ZXing 版本只需要重新构建安装不用动主工程的源码。4.2 一个最小可运行的读码 Demo库接进来之后最直接的验证方式就是跑一个读码例子。下面这个例子假设你已经有一张灰度图数据在内存里可以是自己读文件转出来的也可以是从相机帧里截出来的。#include iostream #include vector #include ZXing/ReadBarcode.h #include ZXing/ImageView.h int main() { int width 128; int height 128; std::vectoruint8_t pixels(width * height, 255); // 把这个数组替换成真实图像数据就可以验证真实扫码效果 ZXing::ImageView image(pixels.data(), width, height, ZXing::ImageFormat::Lum); auto result ZXing::ReadBarcode(image); if (result.isValid()) { std::cout 识别结果: result.text() std::endl; std::cout 码型: ZXing::ToString(result.format()) std::endl; } else { std::cout 未识别到条码 std::endl; } return 0; }这段代码在意的是“输入长什么样的数据接口怎么接受”。ImageView接收裸像素指针、宽、高和像素格式ReadBarcode返回Result。实际工程中图像通常来自 OpenCV 的cv::Mat或者自研图像库只需要把图像数据的指针和参数传进来即可。注意直接把全白像素数组传给 ZXing 是肯定识别不出来的所以这个 demo 第一次跑通拿到“未识别到条码”也算正常它验证的是编译链接链路没问题。想立刻看到效果就准备一张不复杂、不带背景干扰的二维码灰度图再转成 uint8 数组喂进去。4.3 动态库和静态库链接时怎么选静态库和动态库的选择其实是个工程权衡我把常见对比列一下维度静态库动态库部署直接编进 exe不用带额外文件需要把 DLL 放到 exe 旁边或系统搜索路径体积exe 偏大exe 小但 DLL 独立更新换库必须重新链接发布可以只替换 DLL前提是接口 ABI 保持兼容调试调试信息完整栈回溯方便调试符号需要单独维护和发布团队协作每个模块都集成一份容易冲突统一放一个地方依赖清晰我个人在多数业务型 Windows 项目里倾向用静态库因为发布物只有一个 exe用户体验简单。如果你的系统是插件式架构每个插件都要用 ZXing那用动态库更合理因为内存只有一份拷贝不会出现好几份静态库各自为政的隐患。这个选择最好在一开始就定不然后面换起来牵一发动全身。5. 常见问题与排查技巧实录5.1 LNK2019 / LNK2001 链接错误最常见的是一个多小时前编译通过链接时却爆一堆unresolved external symbol。我的排查顺序是这样的先确认链接的是不是正确的库文件。ZXing.lib到底是静态库还是动态库导入库可以通过 dumpbin 看文件头判断。如果库是静态库但你代码里用了导出宏或者__declspec(dllimport)相关约定就会对不上。另一个可能性是BUILD_SHARED_LIBS的设定和你的工程不一致。比如我建的时候用了BUILD_SHARED_LIBSON生成动态库然后又想当静态库去链接自然找不着符号。遇到这个问题时我会直接打开 CMakeCache.txt 搜一眼里面记录的BUILD_SHARED_LIBS确认实际构建配置。还有一种情况是库链接顺序问题。MSVC 的链接器对静态库的顺序比较敏感如果 A.lib 依赖 B.lib而你只写了 A.lib有时会把符号解析放到后面才查找。解决办法是把 ZXing.lib 放在依赖它的一方后面或者在 CMake 里别手动写target_link_libraries的裸字符串直接用它提供的ZXing::ZXing目标CMake 会帮你处理顺序。5.2 RuntimeLibrary 不一致这个问题我在第一节提过具体表现是链接器报出类似LNK2038 mismatch detected for RuntimeLibrary然后你是怎么编译的、是什么运行库就会写得清清楚楚。VS2022 里Release 默认是 /MD也就是动态 CRT如果你为了发布方便把工程改成 /MT而 ZXing 仍然是默认的 /MD就会冲突。解决办法是让两组代码统一。最干净的方式是在 ZXing 构建时也用同样的配置if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:DebugDLL) endif()这是一条 CMake generator expression在 Release 下生成MultiThreadedDLL对应 /MD在 Debug 下生成MultiThreadedDebugDLL对应 /MDd。把这行配置放在 ZXing 的 CMake 配置里能保证整个树统一。如果你的工程确实要用 /MT那就把最后的DLL去掉生成 /MT 和 /MTd。关键是所有参与链接的目标运行时库必须站到同一边。5.3 头文件找不到或 API 对不上头文件找不到一般就是 include 目录没指对。VS 的 C 工程里右键项目打开属性页找到VC 目录或C/C - 常规 - 附加包含目录把install/include加进去。选 CMake 工程的话就不用手工加因为上面target_include_directories已经替你写好了。API 对不上则多半是版本问题。ZXing C 的接口在几个大版本之间有过调整比如老代码用ZXing::BarcodeFormat::QRCode新版本可能改成枚举风格的BarcodeFormat::QRCode还有构造参数变化。我的经验是以官方示例代码和头文件注释为基准别凭网上三年前的博客猜接口。版本升级时优先对比ReadBarcode.h和Result.h这两个文件的改动绝大多数不兼容都可以在这里看到。5.4 运行期缺 DLL 和其他环境问题如果你选择的是动态库编译链接都过了一运行却弹窗说找不到ZXing.dll这就是典型的部署问题。最简单的处理是把 DLL 拷贝到生成的 exe 旁边或者设置 PATH 环境变量指向 DLL 所在目录。正式项目里通常会在 CMake 里加一个post-build命令把 DLL 自动复制到可执行文件输出目录省得每次构建后都手动拷。还有一类隐蔽问题来自操作系统文件路径过长。ZXing 这种大型库在构建过程中会产生很深的临时路径如果源码放在C:\Users\用户名\文档\一些很长的目录下偶尔会触发 Windows 路径长度限制。我后来习惯把源码和构建目录都放在盘符根目录附近比如D:\dev\下这个问题基本绝迹。如果你的编译错误看起来跟具体代码完全无关可以优先怀疑项目路径长度和字符集。6. 实操总结我的习惯和后续扩展方向6.1 用 CMakePresets 固化配置经历了手动敲 CMake 命令、反复确认开关之后我现在更推荐把配置写成CMakePresets.json放在工程里这样 VS2022 能直接识别命令行也能一键复用。一个非常简单的版本长这样{ version: 3, configurePresets: [ { name: vs-x64-release, displayName: VS2022 x64 Release, generator: Visual Studio 17 2022, architecture: x64, binaryDir: ${sourceDir}/out/build/vs-x64-release, cacheVariables: { BUILD_SHARED_LIBS: OFF, ZXING_BUILD_TESTS: OFF, ZXING_BUILD_EXAMPLES: OFF, CMAKE_INSTALL_PREFIX: ${sourceDir}/out/install } } ], buildPresets: [ { name: vs-x64-release, configurePreset: vs-x64-release, configuration: Release } ] }后续只需要cmake --preset vs-x64-release就能完成配置cmake --build --preset vs-x64-release就能构建换台新机器也不会因为手抖漏参数。6.2 值得进一步扩展的几个方向构建完 ZXing C 之后能力边界一下就扩展了不少。实际工程里我建议下一步做三件事一是把读码模块封装成一个异步任务类因为解码往往是整个识别链路里比较耗时的一环丢到工作线程能保证 UI 不卡二是针对自己的图像做预处理实验比如自动旋转、放大、直方图均衡化很多识别失败不是 ZXing 解码能力不行而是图像质量太差三是把结果转换成统一的数据结构比如在库层只返回Result在业务层再包一层自己的ScanRecord避免将来换库时波及业务代码。这个库值得反复折腾。第一次跑通整个 CMake 流程后你后续编译它基本就是一条命令的事。真正宝贵的反而是你在配置过程中建立起的体系认知什么选项影响二进制形态、运行时一致性有多重要、链接错误从哪儿查起。这些经验会跟着你迁移到其他 C 库上每次都会用得到。
阅读完成 · 觉得有帮助?