简介:面向Windows 64位平台的ZXing C++动态链接库,为需要在C++项目中集成二维码/条形码识别能力的桌面软件、自动化读码设备开发者,提供即拿即用的完整SDK。包内已封装好zxing-cpp-2.3.0的构建产物,包含全部头文件、导入库、运行时DLL以及libiconv、libzbar64等第三方依赖DLL,亲测可运行,能省去自行编译ZXing和排查依赖缺失的耗时环节;目录结构清晰,include、lib、bin分层存放,便于项目直接引用。压缩包共36个文件,主要是27个.h头文件(用于调用识别接口)、3个.dll动态库和1个.lib导入库,附4个CMake配置与LICENSE授权说明,整体仅1.47MB,轻量易部署。已有210人学习下载,适合快速接入扫码功能并做Windows离线部署,也可作为C++项目集成条形码识别能力的参考依赖。 最近在Windows平台上做扫码功能,需要在一个老旧的C++桌面项目里集成二维码识别。项目现状是所有模块都编译成动态链接库,主程序只负责装配,所以我一开始就没打算把条形码识别源码整个拖进工程,而是想把ZXing C++编译成Windows动态链接库,对外只暴露几个识别接口。这套方案折腾了大概两天,中间踩了导出符号、运行库不一致、编码格式好几个坑,写出来给准备用zxing-cpp做Windows DLL集成的朋友当参考。
ZXing C++(仓库对应的是zxing-cpp)是目前用得比较顺手的开源条码/二维码识别库,支持QR Code、Data Matrix、Aztec、PDF417这些主流码制,C++17标准,代码质量不错,文档虽然简洁但够用。下面的过程我都按Windows + Visual Studio 2022 + CMake这套组合来讲,其他版本大差不差,遇到差异我会单独说明。
1. 为什么非要把ZXing C++编译成动态链接库
1.1 从源码直编到DLL的典型使用场景
很多人第一反应是:既然zxing-cpp是开源库,我直接把源码文件加进自己的Visual Studio工程里编译不就行了?这种思路在小工具、一次性脚本里没毛病,但放到真实业务项目里很快就会难受。
这次我面临的项目有几个硬约束:主程序是闭源分发的,不能把所有源码捆在一起;团队里其他模块用不同语言写,但都遵循"主程序加插件DLL"的扩展方式;另外二维码识别只是其中一个功能,不想让构建系统为它引入一堆依赖。把这些条件摆出来,最合理的做法就是把ZXing C++编成一个独立DLL,定义好头文件接口,业务侧只跟这套接口打交道。
这种"以DLL为单位交付能力"的方式在Windows生态里很常见,好处也直接:编译一次,多个模块复用;识别库内部升级时,只要接口不变,调用方不用重新编译;出问题也容易定位,依赖关系清楚,不像源码级集成那样一旦冲突就互相污染。
1.2 动态链接和静态链接怎么选
编译ZXing C++时可以选择静态库(.lib)或动态链接库(.dll)。静态库的优点是部署简单,不用额外带一堆DLL,程序启动也少一步加载耗时;缺点是会把识别代码整体塞进可执行文件,分发包变大,而且如果主程序里还用了其他版本的ZXing符号,很容易出现链接冲突。
动态库的思路是让ZXing代码独立成文件,主程序通过导入库(import library)在编译期引用,运行时再加载到底。代价是交付时必须把dll一起带走,还要注意VC++运行库版本匹配。做商业软件、插件系统、或者多语言调用的场景,我建议直接上动态链接库。ZXing C++本身对DLL构建是支持的,不过它默认的CMake配置更偏向静态库,需要手动把开关掰过来,具体见下一节。
2. 编译前的准备:版本选择、工具链和CMake选项
2.1 源码选型:用zxing-cpp而不是Java版
ZXing最早是Java实现的,后来社区搞出了C++移植版,也就是zxing-cpp。注意这两个仓库的下载地址和构建方式都不同,如果搜"ZXing C++"搜到了Java版仓库,会绕很多弯路。现在zxing-cpp的主分支在GitHub上直接维护,发布版本也比较规律,我是直接拉取的master分支。
选版本时有一点要想清楚:如果项目以后会长期维护,最好锁一个release tag,而不是一直跟着master跑。我这次用的是当时最新的release版本,编译选项和接口都以它为准。zxing-cpp对C++标准要求是C++17,这意味着Visual Studio 2017以上都能编,但2015及以下肯定不行,别在这种老编译器上浪费时间。
2.2 工具链准备:VS、CMake和架构选择
Windows下建议直接用Visual Studio自带的工具链。我用的是VS 2022,安装的时候记得勾选"使用C++的桌面开发",否则连cmake generator都找不到。CMake版本不要太旧,3.20以上的都行,太老的版本对VS 2022的支持不完整,配置阶段就会报奇怪的错。
架构选择是这里最容易被忽略的:DLL的位数必须和调用端一致。如果你的业务程序是32位,就算系统是64位的Windows,也得编译32位的ZXing DLL。这跟操作系统位数无关,只看调用进程的PE头。Visual Studio的CMake生成器里,x64和Win32是两套不同的配置,编译前先确认你要哪一套。
2.3 关键CMake开关
zxing-cpp的CMake选项不算复杂,但有几个直接决定能不能得到我们想要的DLL:
BUILD_SHARED_LIBS=ON:这是最核心的开关,设置为ON才会生成动态链接库,默认情况下它会生成静态库。BUILD_TESTING=OFF:关闭测试代码,节省编译时间。ZXING_EXAMPLES=OFF:示例程序不参与构建,我们是拿来用的,不是研究示例的。CMAKE_INSTALL_PREFIX:指定安装目录,后面把头文件、lib、dll统一收拢时用。
如果只识别二维码,还可以通过ZXING_READERS把其他码制关掉,比如只保留QRCode。不过我的场景里后续可能要用Data Matrix,所以没有做这个裁剪。裁剪的好处是DLL体积更小,但改起来要重新编译,建议根据实际业务定。
3. 在Windows下把zxing-cpp编译成DLL的完整过程
3.1 拉取源码并配置构建目录
先把源码拿下来:
git clone https://github.com/zxing-cpp/zxing-cpp.git cd zxing-cpp然后创建一个单独的build目录,不要让生成的中间文件污染源码目录。用VS 2022时,我习惯这样配置:
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TESTING=OFF \ -DZXING_EXAMPLES=OFF \ -DCMAKE_INSTALL_PREFIX=D:/libs/zxing-cpp-install-A x64指定生成64位工程。如果输出的是32位DLL,改成-A Win32。这一步如果报CMake找不到编译器,基本是VS的C++ workload没装全,回安装器里补一下就行。
3.2 编译与安装,拿到dll/lib/头文件
配置成功后会生成一个Visual Studio解决方案(.sln),但不需要手动打开VS点来点去,直接用CMake命令行编译更省事:
cmake --build build --config Release cmake --install build--config Release很重要,默认不写的话可能编出来是Debug版。安装完成后,D:/libs/zxing-cpp-install下会有include目录、lib目录,以及bin目录里的zxing.dll。到这里基本成功一半。
我编出来的文件大概是这样的结构:
D:/libs/zxing-cpp-install/ ├── include/ZXing/ # 所有对外头文件 ├── lib/ # zxing.lib 导入库 └── bin/ # zxing.dll 动态链接库3.3 第一次运行就崩:导出宏和运行库的问题
这一步我要单独拿出来说,因为是我踩得最狠的坑。第一次按上面的流程编完后,我在一个测试程序里链接了zxing.lib,编译全通过,结果一运行就报"无法定位程序输入点"或者直接崩溃,查下来是两个根源。
第一个是导出符号问题。zxing-cpp本身的头文件里有一部分类没有显式标注__declspec(dllexport),某些内部符号在CMake的BUILD_SHARED_LIBS=ON下不一定全部导出。解决办法是在CMake配置时额外加一个选项,让MSVC自动导出所有符号:
-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON加上之后,链接器会把所有可导出的符号都放进DLL导出表,调用方就不会再碰到找不到符号的问题。这个选项对老项目或者接口不明显的库非常有用,缺点是会多导出一些内部实现细节,但对业务侧没影响。
第二个是运行库不一致。ZXing DLL本身用VS编译,默认会使用动态运行库/MD,也就是依赖vcruntime140.dll这些系统组件。如果你的调用工程被设置成了静态运行库/MT,两边用的堆分配器不同,DLL内部申请的内存交给外部释放,或者反过来,轻则内存错误,重则启动直接崩。Windows下的基本规矩是:谁分配谁释放,且所有模块的运行时配置要一致。我的做法是调用工程也统一用/MD发布版配置,问题就消失了。
4. 在自己C++工程里调用ZXing.dll
4.1 工程配置:头文件、导入库、运行时DLL
编译完ZXing,接下来要把它接进业务工程。在Visual Studio里最直接的做法:
- 附加包含目录:把
include目录加进去,让#include "ZXing/ReadBarcode.h"能找到头文件。 - 附加库目录:把
lib目录加进去,并在"附加依赖项"里写zxing.lib,链接器会用它去匹配zxing.dll里的导出符号。 - 运行时目录:把
zxing.dll拷贝到可执行文件输出目录,或者放到系统PATH里,否则启动时会报"找不到zxing.dll"。
这三个环节少一个都不行。特别是第三步,很多人只配了编译期的头文件和lib,忘了拷贝DLL,结果在装了VS的开发机上跑没问题,换到干净机器就报错。
4.2 写一个最简二维码识别程序
配置好工程之后,识别二维码的代码非常直白。zxing-cpp的核心入口是ZXing::ReadBarcode,它接收一个ZXing::ImageView,返回ZXing::Result。我从一张图片文件读取灰度数据并识别:
#include "ZXing/ReadBarcode.h" #include "ZXing/ImageView.h" #include <cstdio> #include <vector> #include <fstream> // 只做演示:读取一张8位灰度BMP文件,生成灰度缓冲区 std::vector<uint8_t> LoadGrayBmp(const char* path, int& width, int& height) { // 这里略去BMP解析细节,业务里一般从相机/截图拿到内存数据 // 真实使用直接传 buffer、width、height、format 即可 return {}; } int main() { int width = 0, height = 0; auto pixels = LoadGrayBmp("qrcode.bmp", width, height); ZXing::ImageView view(pixels.data(), width, height, ZXing::ImageFormat::Lum); auto result = ZXing::ReadBarcode(view); if (result.isValid()) { printf("识别结果: %s\n", result.text().c_str()); } else { printf("未识别到条码\n"); } return 0; }注意LoadGrayBmp我刻意省略了实现,因为业务里图像数据来源五花八门:摄像头帧、截图、OpenCV Mat、内存映射文件。只要最终能拿到uint8_t*图像指针、宽高和像素格式,后续构造ImageView的方式完全一样。ImageFormat::Lum表示8位灰度图,如果是RGB/BGR,要换成对应的RGB、BGR等枚举,否则识别率会大打折扣。
4.3 图像数据的坑:从哪里拿灰度像素
如果图像是彩色图,直接上ImageFormat::RGB或者BGR编译没问题,但ZXing内部还是会做灰度转换,性能差一点。如果图源是相机帧,很多SDK本来就提供灰度格式,直接走Lum是最省事的。
更隐蔽的坑是内存对齐和步长。有些相机输出的图像一行末尾有padding,宽高算出来的字节数每行并不等于width * channels,如果你直接把整块buffer塞进ImageView,会出现图片倾斜、识别失败。处理方法是按行拷贝到紧凑buffer,或者确认步长(stride)确实等于宽度乘通道数后再传。zxing-cpp的ImageView构造函数目前不接收stride参数,所以遇到这种编码,必须先处理成紧凑格式。
还有一次我在灰度图识别率低的问题上折腾了很久,最后发现是像素格式标错了:图片实际是BGR,我却传了RGB,通道顺序反了之后二维码模块完全解不出来。遇到识别失败,先检查格式枚举,再怀疑算法,这个顺序能省很多时间。
5. 给别人交付和后续升级的几个易翻车点
5.1 别漏了VC++运行库
编好的zxing.dll依赖Visual C++运行库。在开发者机器上,VS装了没问题;但交付到普通Windows机器,可能没有vcruntime140.dll。两个解决办法:一种是在安装包里带上VC++ Redistributable并静默安装;另一种是把运行库组件作为合并模块一起打进去。不管选哪种,不要在交付文档里写"如果报错请自己装运行库"就完事,用户不会领情的。
想确认dll到底依赖哪些运行库,可以用Dependencies这类工具打开zxing.dll,看它依赖的模块列表。我自己的习惯是打包前在干净的虚拟机里跑一次,这一步基本能排查掉90%的"我这能跑,你那不能跑"问题。
5.2 32位/64位和Debug/Release混用
前面提过位数必须匹配,这里再展开说。Visual Studio里默认的"Debug"和"Release"不只是编译器优化区别,还涉及运行库选择。如果ZXing DLL是Release版,调用工程是Debug版,虽然能链接、能启动,但调试器里看变量可能乱七八糟,某些跨模块的string返回也会异常。我踩过一次:Debug程序加载了Release版DLL,result.text()返回的中文直接乱码,排查到后面才发现是调试/发布混用。
最好的做法是ZXing DLL准备两套,Debug和Release各编一次,通过不同的输出目录区分调用。zxing-cpp编译不算慢,两套完全值得。
5.3 内部函数不导出、升级时ABI变化
即便加了CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON,也建议在对外接口上显式定义一套干净的API,而不是让调用方直接用ZXing::ReadBarcode这种库内部接口。因为库升级时,类结构、namespace、枚举值可能变化,直接暴露内部接口会让DLL升级变成一场灾难。我这次就用一个简单的C风格包装层包住了核心调用,对外只暴露int RecognizeCode(const uint8_t* image, int width, int height, int format, char* out, int outLen)这一类函数,内部再转调ZXing接口。
这样即使zxing-cpp升级,最多改包装层,业务侧一行不用动。DLL的ABI也更稳定,不会被C++ name mangling、编译器版本差异折腾到崩溃。
5.4 打包验证清单
最后分享一个我每次打完包都要过的清单,都是实际出过问题的点:
- [ ] 新机器上能否直接运行,不弹"找不到vcruntime140.dll"
- [ ] 调用端位数和ZXing DLL位数是否一致
- [ ] Debug版调用Debug版DLL,Release版调用Release版DLL
- [ ] 图像格式枚举是否正确,灰度图别标成RGB
- [ ] 图像buffer是否为紧凑排列,排除了stride padding干扰
- [ ] 中文识别结果在控制台/界面上展示时,编码有没有被GBK和UTF-8搞乱
其中最后一条在Windows下尤其常见。zxing-cpp识别出的文本是UTF-8编码,但Windows控制台默认代码页经常是GBK,直接printf("%s")打出来的中文就是乱码。我自己都是统一在业务层转成UTF-16再用Windows API输出,或者用SetConsoleOutputCP(CP_UTF8)处理。这个不是库的问题,但几乎每个人都会撞一次。
这套ZXing C++ DLL的集成方案,我现在已经放到项目里稳定跑了一段时间。识别速度和精度都够用,DLL也就几百KB,分发压力不大。如果有条件,建议你们也把zxing-cpp的版本固定下来,并且每次升级前跑一遍自己的二维码测试集,别只看单元测试过了就放行,毕竟条码图片千奇百怪,码制、污损、光照都会影响结果,只有真实业务数据才能告诉你这次升级到底值不值得。
本文还有配套的精品资源,点击获取