zxing-cpp二维码库静态编译全攻略:32位/64位库构建与链接避坑指南 简介ZXing C静态编译库是一套面向Visual Studio开发者的二维码与条形码生成/识别组件基于开源ZXing项目编译而成支持QR码、Data Matrix、Aztec、UPC、EAN等多种格式适合需要离线集成扫码或批量生成二维码的桌面及嵌入式场景。压缩包共222个文件约2.84MB包含105个头文件、96个cpp源文件以及32位与64位两个静态库lib另有少量cc/c源码、示例exe、Makefile和ChangeLog既可直接链接使用也便于查看底层实现或按需定制。其中include目录提供完整接口声明lib目录按平台分开配合Visual Studio的附加包含目录和附加依赖项即可完成配置。目前已有878人学习下载。对希望避开动态链接麻烦、快速获得可靠条码能力的C开发者来说这套静态库提供了省心的集成路径既保留了ZXing的算法精度又通过预编译库降低了上手门槛两类架构文件齐全适合直接用于项目交付。 去年做设备上位机改造需求里有一项是二维码识别离线、实时、还得能读中文内容。我一开始想省事直接用 OpenCV 自带的 QRCodeDetector实测下来发现它对角度正、图像干净的二维码还行稍微有点旋转、反光或者局部遮挡就直接拉胯。换 Java 版 ZXing 吧C 工程又没法直接用。最后锁定了 zxing-cpp但麻烦事也跟着来了官方仓库默认给的是源码和动态库的编译方式我们现场有一台 32 位的旧插件环境还有一套 64 位的新主程序目标部署机又经常缺各种 VC 运行时。折腾一圈后我决定自己动手把 zxing C 二维码库的 32 位和 64 位静态编译库都编出来。这篇文章记录的就是我从拉源码、配 CMake、踩 OpenMP 的坑到最终把四份静态库Debug/Release × x86/x64接进业务工程的全过程。如果你也想在 C 项目里用 zxing 做二维码生成和识别又不想被动态库的运行时依赖绑死这篇的思路和命令应该能直接帮上忙。1. 为什么非要把 zxing-cpp 编成静态库1.1 动态库让人头疼的地方很多开发者拿到 zxing-cpp 的第一个动作是找预编译的 DLL 或 so或者用 vcpkg 直接装。这本身没问题但放到实际交付场景里就会遇到几件很烦的事动态库需要配套的运行时。MSVC 编译出来的 DLL 依赖 VCRUNTIME目标机器如果没装对应版本的 Microsoft Visual C Redistributable程序起来就弹缺少 VCRUNTIME140.dll。工业现场的老电脑基本都是一堆历史遗留环境装运行库这事经常被客户 IT 拒绝。32 位和 64 位必须分别维护。一个 64 位主程序不能调 32 位的 DLL反之亦然。如果部署环境既有老插件又有新主程序你就得同时维护两份动态库目录稍微一乱就出幺蛾子。动态库版本升级容易引发DLL Hell。项目组其他人不小心替换了某个公共目录下的 zxing DLL你会花很长时间去排查一个根本不是自己代码引入的问题。静态库把这些麻烦全部挡在链接阶段最终产物就是干净的 exe 或业务 DLL不带任何 zxing 相关的动态依赖拷贝到哪台机器都能跑。这一点在设备交付、工控机部署、安全保密要求高的场景里特别实用。1.2 zxing-cpp 在 C 生态里的位置zxing-cpp 是 Java 版 ZXing 的 C 移植GitHub 上独立维护仓库名是 zxing-cpp/zxing-cpp。它不是简单翻译 Java 代码而是完全按 C 的习惯重写了一套 API支持 QR Code、Data Matrix、Aztec、Code 128 等多种格式。生成和识别都有对于大多数应用来说这一个库就够了。注意版本差异。早期 1.x 版本的 API 是 ZXing::QrCode::encode 这类写法从 2.x 开始接口做了大调整改成了 ZXing::CreateBarcodeWriter 和 ZXing::ReadBarcode。我看社区里还有很多老教程写的是 1.x 用法你拿新版源码去对会直接编译不过。这篇文章里的示例基于 2.x 版本具体以你下载到的 core/src/ZXing 目录下的头文件为准。1.3 为什么 32 位和 64 位版本都得留一份我这次同时编两个位数不是强迫症是业务逼的。旧设备里有 32 位插件插件容器是 32 位的不能换新上位机是 64 位要跑更快的图像处理。如果只编 64 位老插件只能望洋兴叹。如果只编 32 位新主程序又用不上。多项目并存时一次性把 32 位和 64 位的静态库都编好后面省下的是反复搭建环境的工夫。每次只要拿起对应位数的 .lib 直接链接就行不用再为谁依赖谁操心。2. 从源码到 x86/x64 静态库的构建命令2.1 拉源码与版本选择首先把源码拉到本地建议直接用 release 分支的 tag别用 master 上的最新提交API 可能正在调整git clone --depth 1 --branch v2.3.0 https://github.com/zxing-cpp/zxing-cpp.git cd zxing-cpp拉下来后先看一眼 CMakeLists.txt 里的版本要求和可配置项我习惯用cmake -LAH在之后查看全部开关不过最省事的是先确认编译器。zxing-cpp 2.x 要求 C17所以 VS2017 15.7 以上的 MSVC、GCC 7、Clang 5 才能编过。你要是还在用 VS2015 或 GCC 5建议先升级工具链别纠结。2.2 Windows用 CMake 分别输出 x86 / x64 静态库Windows 上我用 Visual Studio 生成器关键是-A参数x64 对应 64 位Win32 对应 32 位。构建目录一定要分开同一目录反复切换位数会产生缓存污染。:: 64 位 Release cmake -S . -B build-x64 -G Visual Studio 17 2022 -A x64 ^ -DBUILD_SHARED_LIBSOFF -DBUILD_TESTINGOFF -DZXING_EXAMPLESOFF ^ -DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON cmake --build build-x64 --config Release --parallel :: 32 位 Release cmake -S . -B build-x86 -G Visual Studio 17 2022 -A Win32 ^ -DBUILD_SHARED_LIBSOFF -DBUILD_TESTINGOFF -DZXING_EXAMPLESOFF ^ -DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON cmake --build build-x86 --config Release --parallel构建完成后静态库在 build-x64/core/Release/zxing.lib 和 build-x86/core/Release/zxing.lib 下。如果你要 Debug 版本把--config换成 Debug 再编一次。2.3 Linuxmultilib 下的 32 位静态库Linux 下编 32 位需要系统装了 multilib 支持。以 Ubuntu/Debian 为例sudo apt install gcc-multilib g-multilib # 64 位 cmake -S . -B build-linux64 -DCMAKE_BUILD_TYPERelease ^ -DBUILD_SHARED_LIBSOFF -DBUILD_TESTINGOFF -DZXING_EXAMPLESOFF ^ -DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON cmake --build build-linux64 --parallel # 32 位 cmake -S . -B build-linux32 -DCMAKE_BUILD_TYPERelease ^ -DCMAKE_CXX_FLAGS-m32 -DCMAKE_C_FLAGS-m32 ^ -DBUILD_SHARED_LIBSOFF -DBUILD_TESTINGOFF -DZXING_EXAMPLESOFF ^ -DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON cmake --build build-linux32 --parallel32 位编译最常见的报错是bits/cconfig.h: No such file or directory几乎都是缺 multilib 导致的装上就解决了。2.4 几个关键 CMake 开关开关作用我的建议BUILD_SHARED_LIBSOFF生成静态库本篇文章的核心必须 OFFBUILD_TESTINGOFF跳过测试构建能省很多编译时间建议 OFFZXING_EXAMPLESOFF不编译示例程序示例依赖额外库建议 OFFCMAKE_DISABLE_FIND_PACKAGE_OpenMPON禁用 OpenMP 探测强烈建议设置后面细讲如果你项目中确实需要 Data Matrix 或 PDF417默认配置已经开启了这些 reader不需要额外改。需要看全部开关时执行cmake -LAH build-x64。3. 静态库编译路上的常见坑我踩过的三个典型问题3.1 OpenMP 这个隐形的运行时依赖我踩的第一个坑就是 OpenMP。zxing-cpp 的 CMake 会自动探测系统里的 OpenMP探测到了就默认启用并行加速。如果编成动态库OpenMP 的运行时MSVC 的 vcomp140.dll、GCC 的 libgomp.so会跟着 DLL 一起分发问题不大。但编静态库时OpenMP 的符号会被一起静态链进 zxing.lib 里然后你的主程序链接时就会冒出一堆找不到__kmpc_*或GOMP_*的 undefined reference。更麻烦的是这种报错不像普通函数缺失那样一眼能看出原因很多人会以为是 zxing 库本身坏了。我实际测试下来对二维码识别这种量级的计算开不开 OpenMP 差距不大普通的单张图识别本来就在几十毫秒内。所以直接禁用最省心CMake 里根本不用改源码配置时加一行变量-DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON如果已经编到一半想确认库到底有没有带 OpenMP 依赖Windows 上用 dumpbin 看链接指令dumpbin /directives zxing.lib | findstr /i vcomp libgomp看到vcomp或libgomp就说明 OpenMP 被带进来了。Linux 下用objdump -p libzxing.a | grep NEEDED3.2 /MT 与 /MD 不一致导致的链接报错第二个坑是 MSVC 的运行时库设置。如果你用 VS 图形界面新建工程默认是 /MD多线程 DLL也就是程序要依赖 VCRUNTIME。zxing-cpp 用 CMake 编译时是跟着 CMake 里 CMAKE_MSVC_RUNTIME_LIBRARY 走的。我之前编完库后直接扔给同事他在工程里加了引用一链接就报LNK2038: mismatch detected for RuntimeLibrary: value MD_DynamicRelease doesnt match value MT_StaticRelease原因就是库用的是 /MD他工程用的是 /MT两边标准库实现不一样。解决办法有两种统一为 /MD两边都是 /MD链接干净。缺点是目标机器需要装 VC 运行时。统一为 /MT更省部署事但库和主工程必须都用 /MT。我的选择是 /MT因为静态库本来就是为了不依赖运行库。在 CMake 配置时加-DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreaded注意 Debug 和 Release 的 RuntimeLibrary 也要对应。Debug 用 /MTdRelease 用 /MT如果你库里编的是 Release主工程用 Debug 配置链接也会报错。所以标准做法是四个配置都编一遍这正是第 5 部分做脚本的原因。3.3 源码编译通过但链接失败时的排查路径链接错误比编译错误隐蔽我总结了一套排查流程按顺序来基本能定位先确认位数对不对。32 位主程序链 64 位库最常见的报错是unresolved external symbol或者machine type x64 conflicts with target machine type x86。再确认库是 Release 还是 Debug。直接用 dumpbin 看/headers注意Debug Directories字段或者直接看文件是在 Release 目录还是 Debug 目录。用 dumpbin 查符号有没有。dumpbin /symbols zxing.lib | findstr ReadBarcode如果符号存在但链接失败多半是调用约定或导入方式的问题。最后检查运行时库。把库和主工程的/MT/MD设置对齐。这套流程我后来写成了交接文档团队里其他人再遇到链接问题自己按步骤查一遍基本都能解决。3.4 如何确认手里的 .a / .lib 是 32 位还是 64 位这个热搜词背后其实是很多人被位数坑过。在 Windows 和 Linux 上各有快速方法Windows 下用 VS 自带的 dumpbin要在开发者命令行里跑dumpbin /headers zxing.lib | findstr machine输出里如果是machine (x64)就是 64 位machine (x86)就是 32 位。Linux 下用 objdumpobjdump -f libzxing.a | grep architecture输出elf64-x86-64是 64 位elf32-i386是 32 位。也可以用ar x把静态库里的 .o 解出来再用file xxx.o查看但不如 objdump 直接。4. 在业务工程中接入自编静态库配置与示例代码4.1 用 CMake 引用自编译的静态库实际工程里我推荐写一个 CMake 封装把 zxing 库的路径和头文件都管理起来而不是每次都在 VS 里手动配。下面的写法同时支持 Debug/Release 自动切换add_library(zxing STATIC IMPORTED) set_target_properties(zxing PROPERTIES IMPORTED_LOCATION_DEBUG ${CMAKE_SOURCE_DIR}/third_party/zxing/lib/Debug/zxing.lib IMPORTED_LOCATION_RELEASE ${CMAKE_SOURCE_DIR}/third_party/zxing/lib/Release/zxing.lib INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/third_party/zxing/include ) target_link_libraries(myapp PRIVATE zxing)Linux 下把扩展名和路径改成 libzxing.a 即可。如果你的主工程不是 CMake那就在 VS 的项目属性里把附加包含目录指向 include/zxing附加依赖库指向对应的 .lib同时保证运行库设置和编译库时一致。还要确保主工程写的代码是 C17老工程如果还在用 C14 标准会出现头文件语法报错。4.2 生成二维码最小可用的 writer 示例这里的 API 是 2.x 版本的写法。生成二维码的核心是CreateBarcodeWriter拿到 Barcode 对象后取出像素数据自己输出成图片。为了不给工程引入 libpng 这类额外依赖我直接写了个 24 位 BMP 保存函数Windows 和 Linux 都能直接用#include ZXing/BarcodeFormat.h #include ZXing/WriteBarcode.h #include fstream #include vector #include cstdint #include cstring using namespace ZXing; // 保存 24 位 BMP避免额外依赖 static bool SaveBMP24(const std::string path, int w, int h, const std::vectoruint8_t rgb) { if (rgb.size() static_castsize_t(w * h * 3)) return false; int rowSize ((w * 3 3) / 4) * 4; int dataSize rowSize * h; uint32_t fileSize 54 dataSize; std::ofstream f(path, std::ios::binary | std::ios::trunc); if (!f) return false; uint8_t fileHeader[14] {B, M}; std::memcpy(fileHeader 2, fileSize, 4); fileHeader[10] 54; // 像素数据偏移 f.write(reinterpret_castchar*(fileHeader), 14); uint8_t infoHeader[40] {0}; uint32_t biSize 40; int32_t biWidth w, biHeight h; uint16_t biPlanes 1, biBitCount 24; uint32_t biSizeImage dataSize; std::memcpy(infoHeader, biSize, 4); std::memcpy(infoHeader 4, biWidth, 4); std::memcpy(infoHeader 8, biHeight, 4); std::memcpy(infoHeader 12, biPlanes, 2); std::memcpy(infoHeader 14, biBitCount, 2); std::memcpy(infoHeader 20, biSizeImage, 4); f.write(reinterpret_castchar*(infoHeader), 40); std::vectoruint8_t line(rowSize, 0); for (int y h - 1; y 0; --y) { std::memcpy(line.data(), rgb.data() y * w * 3, w * 3); f.write(reinterpret_castchar*(line.data()), rowSize); } return true; } int main() { auto writer CreateBarcodeWriter(BarcodeFormat::QRCode); auto barcode writer-write(https://example.com, 320, 320); int w barcode-width(); int h barcode-height(); std::vectoruint8_t rgb(w * h * 3); for (int i 0; i w * h; i) { uint8_t v barcode-pixels()[i] ? 0 : 255; // 黑为1白为0 rgb[i * 3] rgb[i * 3 1] rgb[i * 3 2] v; } SaveBMP24(qr.bmp, w, h, rgb); return 0; }这里有个容易困惑的点Barcode 的 pixels() 返回的是 0/1 二值数组不是 0/255。如果你拿到的版本返回的是不同表示用三元判断转一下即可。文件保存成 BMP 后Windows 系统自带看图工具能直接打开不需要装额外软件。4.3 识别二维码从图像像素到文本识别接口和生成接口是对称的传入图像数据和尺寸返回结果。下面示例假设你已经有一块 RGB24 像素缓冲区实际项目里无论是相机帧、截图还是读取文件最终都会落到这种格式#include ZXing/ReadBarcode.h #include iostream using namespace ZXing; // 假设 pixels 是一张 width x height 的 RGB24 数据 void DecodeExample(const std::vectoruint8_t pixels, int width, int height) { DecodeHints hints; // 如果只需要二维码可以显式限制格式提升识别速度 // hints.setFormats(BarcodeFormat::QRCode); ImageView view(pixels.data(), width, height, ImageFormat::RGB24); Result result ReadBarcode(view, hints); if (result.isValid()) { std::cout text: result.text() std::endl; std::cout format: ToString(result.format()) std::endl; } else { std::cout not found std::endl; } }如果你想从文件读图再识别建议引入 stb_image.h 这个单头文件库几十行就能把 PNG/JPG 解码成 RGB 数据然后丢给上面的函数。这个方案比直接让 zxing 去读文件更灵活因为以后接相机、接截图都是同一套代码。4.4 链接错误时的对照表接入阶段最常见的链接问题我做了一张速查表方便对齐现象原因处理machine type x64 conflicts with x86位数不匹配换对应位数的库LNK2038 RuntimeLibrary mismatch/MT 与 /MD 不一致统一运行时库设置LNK2038 iterator debug level mismatchDebug/Release 混用主工程用对应的库版本unresolved external symbol ZXing::xxx库没链接或被裁剪检查链接器输入确认符号路径链接时出现kmpc/ GOMP符号库带了 OpenMP 依赖重新用 CMAKE_DISABLE_FIND_PACKAGE_OpenMPON 编译5. 一个脚本同时产出四种配置的静态库5.1 Windows 批处理脚本示例手动编四次很烦我写了一个批处理一次跑完生成 x86/x64、Debug/Release 四份静态库。核心思路是四套独立构建目录互不干扰echo off set SRCzxing-cpp set GENVisual Studio 17 2022 call :build x64 Release call :build x64 Debug call :build x86 Release call :build x86 Debug goto :eof :build echo Building %1 %2 if %1x64 (set ARCHx64) else (set ARCHWin32) cmake -S %SRC% -B build-%1-%2 -G %GEN% -A %ARCH% ^ -DCMAKE_BUILD_TYPE%2 ^ -DBUILD_SHARED_LIBSOFF -DBUILD_TESTINGOFF -DZXING_EXAMPLESOFF ^ -DCMAKE_DISABLE_FIND_PACKAGE_OpenMPON ^ -DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreaded cmake --build build-%1-%2 --config %2 --parallel exit /b 0跑完后把四个 lib 文件统一拷贝到一个目录里配合 4.1 的 CMake 封装后续工程引用就非常省事了。5.2 文件名命名与目录约定我自己的目录设计是这样third_party/ zxing/ include/zxing/ # 头文件 lib/ x64/release/zxing.lib x64/debug/zxing.lib x86/release/zxing.lib x86/debug/zxing.lib用位数加构建类型区分目录比在文件名上做文章更清晰。这样 VS 工程里只要根据当前平台切换库目录就行不用把文件名改来改去。Linux 同理libzxing.a 按位数放x64和x86两个目录。5.3 分发给别人时带上哪些东西静态库分发比动态库简单但也不是只丢一个 lib 文件就完事。至少要包含三样头文件目录整个 include/zxing 目录不能省否则对方没法编译。四份静态库x86/x64 各一份 Release如果你和对方都可能调试再把 Debug 也带上。一段说明注明编译器版本MSVC v143、GCC 11 等、C 标准C17、运行时库设置/MT 还是 /MD以及 OpenMP 是否关闭。我一开始觉得写说明很啰嗦后来发现这个说明能省掉无数个为什么我链接不过的咨询。记住库本身编对了不等于别人能用对。工具集版本对不上静态库同样可能不兼容这也是为什么我坚持在分发说明里写上MSVC v143 /MT C17这几个关键字。我自己现在养成的习惯是任何第三方静态库进仓库时旁边必须放一个 README记录编译时间、命令、工具链版本。半年后再接手新机器或者队友换电脑翻一下 README 跑一遍脚本所有配置就全回来了。这个习惯比起把坑再踩一遍成本低太多了。本文还有配套的精品资源点击获取