简介:ZXing.Net是一款面向C#/.NET开发者的开源条码类库,支持生成与解析QR Code、Data Matrix、PDF417等一维/二维条码,可用于桌面、Web和移动应用中的扫码识别、电子票务、产品追溯等场景。该zip压缩包包含85个文件,主体为dll程序集、XML接口文档和PDB调试符号,另含少量winmd、pri等Windows Runtime适配文件,整体大小12.16MB。压缩包按net20、net35、net40、net45、netstandard等目标框架分目录整理,开发者可根据项目环境直接引用对应版本,快速集成二维码生成与解析能力。目前已有840人学习下载,适合需要为C#项目补充条码功能的初中级工程师参考使用。有了这套类库,可以省去自行编译源码的步骤,拿到即用的dll、说明文档与调试符号;生成时还可自定义尺寸、编码字符集与外观,解析时也能自动识别多种条码格式,有助于提升开发效率和排查集成问题。 做管理系统的朋友应该都有同感:业务一跑起来,二维码和条码的需求几乎是躲不掉的。设备巡检要贴二维码、工单流转要扫码、库存盘点要打条码,就连给外部客户交付数据,都常被要求"给我个二维码扫一扫"。
那会儿我手上有个内部工具项目,需要在完全不联网的内网环境里生成和识别二维码。第一个想到的当然是调用各家云平台的API,结果一测试直接卡死——内网机器根本没有外网权限,在线接口这条路直接堵死。于是我开始找离线可用的.NET类库,先后试了几种方案,最后在ZXing.net这棵树上吊住了,而且一用就是好几个项目。
ZXing.net是Java开源条码库ZXing的.NET移植版本,官方NuGet包名就叫ZXing.Net。它支持二维码(QR Code)、Code 128、EAN-13、PDF417等二十多种码制,既能生成条码图片,也能从图片中识别条码内容,全部本地计算,不依赖任何外部服务。对.NET开发者来说,这是最常见的"开箱即用"条码类库之一,也是我实测后觉得最省心的一个。
如果你也是个被二维码需求临时找上门的开发者,或者正在纠结该不该引入这个类库,这篇文章把我在真实项目里验证过的生成流程、识别细节、踩坑记录都整理了出来,照着操作就能少走不少弯路。
1. 环境准备与第一个Demo:NuGet装完就能跑
先说结论:ZXing.net的接入成本低到几乎可以忽略。你不需要配置复杂的依赖,也不需要写一堆初始化代码,添加NuGet包引用之后,核心功能就全部可用了。
1.1 安装哪个包、什么版本
项目里右键"管理NuGet程序包",搜索ZXing.Net,安装即可。有一点要留意:官方包和连字符大小写有几种变体,比如ZXing.Net和ZXing.NET,尽量认准作者为Michael Jahn的ZXing.Net,这是目前维护最活跃、兼容性最好的版本。
如果你用的是.NET Framework 4.5以上的老项目,或者.NET Core 3.1 / .NET 6 / .NET 8这类新项目,都可以直接安装。我实测过 .NET Framework 4.7.2 和 .NET 8 两个环境,基础生成识别功能完全一致,没有遇到兼容性报错。唯一的区别是跨平台部署时,.NET 8 下位图操作依赖本机图形库,Windows和Linux上表现略有差异,这点后面会专门说。
1.2 最简生成代码:十行以内出图
安装完成之后,生成一张二维码最简单的方式是用BarcodeWriter类:
using ZXing; using ZXing.Common; using System.Drawing; using System.Drawing.Imaging; var writer = new BarcodeWriter<Bitmap> { Format = BarcodeFormat.QR_CODE, Options = new EncodingOptions { Width = 300, Height = 300, Margin = 1 } }; using (var bitmap = writer.Write("hello zxing.net")) { bitmap.Save("qrcode.png", ImageFormat.Png); }这段代码生成的内容是纯文本"hello zxing.net",保存为300x300像素的PNG图片。很多第一次接触ZXing.net的人会误以为需要先了解条码编码规则才能动手,其实完全不用,Write方法内部已经处理好了编码和绘制逻辑,你只需要关心的就是格式、尺寸和要写入的内容。
1.3 最简识别代码:从图片里读出内容
识别也没有想象中复杂,推荐使用BarcodeReader类:
using ZXing; using System.Drawing; var reader = new BarcodeReader<Bitmap> { AutoRotate = true, TryInverted = true, Options = new DecodingOptions { TryHarder = true, PossibleFormats = new List<BarcodeFormat> { BarcodeFormat.QR_CODE, BarcodeFormat.CODE_128 } } }; using (var bitmap = new Bitmap("qrcode.png")) { var result = reader.Decode(bitmap); if (result != null) { Console.WriteLine($"识别结果: {result.Text}"); } }需要注意,识别结果result可能为null,说明这张图里没有解析出条码信息。我在实际测试中最常遇到的错误是直接拿保存后的微信截图去识别,由于截图比例、压缩率和干扰元素的影响,默认参数经常识别失败,设置TryHarder和AutoRotate之后成功率会明显上升。
2. 生成二维码的核心实操:中文乱码、尺寸清晰度、纠错级别一次讲清
跑通最简示例之后,真正的业务需求才开始暴露出来。这一节我把生成二维码时最常被问到的几个问题集中讲一下。
2.1 中文内容不能直接塞?编码处理最关键
ZXing.net对纯数字和纯英文的支持很友好,但如果你直接往Write方法里传入一段中文,比如"设备编号:A-2024-001",一部分环境会出现乱码或者生成后扫描不出来。
原因在于条码的内容不只是字符串,它最终会被编码成一串字节流。QR Code支持多种编码模式,默认情况下对非ASCII字符的处理依赖系统默认字符集,不同操作系统可能给出一致的结果。稳妥的做法是自己先按UTF-8转码,再交给条码写入器:
var content = "设备编号:A-2024-001"; var writer = new BarcodeWriter<Bitmap> { Format = BarcodeFormat.QR_CODE, Options = new EncodingOptions { Width = 400, Height = 400, Margin = 1, PureBarcode = false } }; var bitmap = writer.Write(content);这里需要说明,ZXing.net在较新的版本中默认使用UTF-8处理字符串,大部分情况下直接传中文也能正常工作。但如果你在项目里混用了老版本,或者目标机器是非中文操作系统,建议手动指定字符集:
var hints = new Dictionary<EncodeHintType, object> { { EncodeHintType.CHARACTER_SET, "UTF-8" } }; writer.Options.Hints = hints;设置之后生成的二维码扫出来就不会出现中文乱码。这个细节在我第一次做内网设备管理系统时没注意到,导致现场扫码出来的文字全部是问号,排查了整整一个下午。
2.2 白边(Margin)和尺寸的关系
ZXing.net生成二维码默认会保留一定宽度的白边,帮助扫码器精准定位。Margin的单位不是像素,而是模块数(module),也就是二维码最小单元的倍数。设置Margin=4表示四个模块宽的白边,通常扫描兼容性最好。
如果你的二维码要贴在很小的设备标签上,单纯缩小图片会连带缩小模块,到一定程度二维码会糊成一团。正确的做法是固定模块尺寸、控制图片尺寸,或者生成后保留合理的白边。一个实用经验:内容少的二维码模块数量少,同样尺寸下单个模块大,更容易被识别;内容塞得越多,模块越密集,识别难度越高。所以在满足业务需求的前提下,二维码里的信息越短越好。
2.3 纠错级别:不是越高越好
ZXing.net的EncodingOptions没有直接暴露纠错级别属性,需要通过Hints设置:
var hints = new Dictionary<EncodeHintType, object> { { EncodeHintType.ERROR_CORRECTION, "H" } };纠错级别从L(低,约7%)到M、Q、H(高,约30%)。级别越高,二维码被遮挡、污损后仍然能识别的概率越大,但代价是同样内容下模块数增多、图案更密。
我自己的经验是:打印在纸张或标签上的二维码,用M或者Q级别;要印在金属、塑料这种容易反光或者表面可能被磨损的材质上,直接上H。如果是电脑屏幕上展示、手机扫一扫的场景,用L或者M就够,没必要白白增加图案复杂度。
2.4 生成带Logo的二维码:业务需求里的高频操作
很多内部系统需要在二维码中间嵌入公司Logo,虽然ZXing.net本身不提供这个功能,但可以直接在生成的Bitmap上做二次绘制。原理很简单:二维码的纠错级别足够高时,中间区域被遮挡不影响识别,我们正好利用这一点来放Logo。
实现方式是用System.Drawing的Graphics对象,把Logo图片绘制到二维码中央,同时保留一定比例的白边过渡区域,避免Logo边缘污染码眼。有一个值得注意的参数组合:Logo嵌入前,将生成二维码的纠错级别设置为H,尺寸建议不低于300x300,否则Logo会把有效模块遮挡太多,扫码成功率会明显下降。
3. 识别环节的隐形门槛:方向旋转、反色图片、模糊图像处理
生成只是前半场,识别才是项目里真正让人头疼的部分。很多人在生成环节一切顺利,一到识别就各种失败,下面把我验证过的处理方法列出来。
3.1 旋转和反色:AutoRotate与TryInverted
手机拍照上传的图片,经常存在旋转角度的问题。ZXing.net的BarcodeReader内置了AutoRotate选项,开启后会尝试旋转90度、180度、270度再识别。虽然会额外消耗一些性能,但对业务场景而言,这个开销完全可以接受。
更隐蔽的是反色问题。有些设备或软件导出的图片是反色二维码,也就是原本黑色模块变成白色、白色区域变成黑色。直接用常规参数识别会失败,开启TryInverted后,类库会尝试反色识别,我实测过确实能成功解析。
3.2 模糊和噪点:TryHarder是一把双刃剑
识别不清晰的图片时,TryHarder选项可以提高成功率,它会让解码器在定位失败后重试更多条路径。但代价是耗时明显增加,而且误识别率也可能升高。
实际项目中我一般这样配置:如果是单张图片、对耗时没有要求,比如后台解析用户上传的截图,直接TryHarder=true;如果是视频流连续扫码、对实时性要求高,比如扫码枪连拍场景,不建议开启,否则会严重影响帧率。这里没有绝对正确的配置,完全取决于业务场景对"速度"和"成功率"的取舍。
3.3 多码制识别:不要盲目开启所有格式
ZXing.net支持几十种码制,但识别时不需要把所有格式都打开。开启的格式越多,解码器花的匹配时间越长,出现误识别(比如把某段图案识别成另一码制的合法编码)的概率也越高。
我习惯在PossibleFormats里只列当前业务可能遇到的格式。比如扫码枪场景通常只是二维码,就只保留QR_CODE;混合了条码场景的,再加CODE_128、EAN_13即可。传入的图片未知时,可以考虑先用默认全格式试一次,如果失败再用白名单格式试一次,双保险既保证了成功率,也控制了误识别风险。
4. 一维条码与二维码的差异化处理:ZXing.net里容易被忽略的设计
不少新人以为ZXing.net只是"二维码库",其实它的一维条码生成和识别同样是核心能力。我在这里单独讲一维条码,是因为它的处理和二维码差异很大,理解这些差异能避免很多低级失误。
4.1 一维条的生成参数和二维码完全不一样
一维条码(比如Code 128)是有方向性的,宽度通常远大于高度,而且内容只能包含约定的字符集。生成时不再使用正方形尺寸,而是设置一个比较宽的Width和相对小一些的Height:
var writer = new BarcodeWriter<Bitmap> { Format = BarcodeFormat.CODE_128, Options = new EncodingOptions { Width = 260, Height = 80, Margin = 1, PureBarcode = false } }; using (var bitmap = writer.Write("ABC-123456")) { bitmap.Save("code128.png", ImageFormat.Png); }Code 128支持数字、大写字母和部分符号,不支持中文。如果你硬要塞中文进去,生成阶段可能不报错,但扫码枪读出来就是乱码,或者根本无法识别。这一点和一维码的编码标准有关系:Code 128的字符集设计时压根没有中文字符的位置。
4.2 一维条码的识别注意事项
一维条的识别依赖清晰的条和空对比度。如果打印的条码过细、过密,扫描设备容易把相邻的条合并成一个条,导致解码失败。在ZXing.net中,识别一维码建议把TryHarder打开,因为一维条的定位时间远小于二维码,性能开销影响不大。
另外,一维条码对倾斜非常敏感。AutoRotate对一维条码的帮助比较大,因为即使图片旋转了,只要条码的条线方向和水平方向保持着合理的角度,解码器也有机会恢复。但如果是整体旋转了90度,竖着的一维条码识别难度会大幅上升,这类图片提前做一个角度校正会稳妥很多。
5. 亲测后才明白的坑:Bitmap锁定、跨平台图形差异、批量性能
这一节全部来自我在真实项目里踩过的坑,每一个都花了时间排查,写出来希望能帮你省下这些时间。
5.1 Bitmap被占用导致无法保存或识别失败
使用BarcodeReader时,很多人习惯复用同一个Bitmap对象,解码后不释放,然后下一轮又往这个对象里面塞新图片。这个问题在Windows的GDI+体系下很常见:位图被解码器引用后,没有及时释放会导致文件句柄被占用,后续保存图片或再次读取时抛异常。
我的习惯写法是:每个识别周期都新建Bitmap对象,用using包裹,解码完成后立即释放:
using (var bitmap = new Bitmap(filePath)) { var result = reader.Decode(bitmap); // 这里result.Text已经是解析结果 }5.2 Linux下生成图片会白屏?图形库依赖问题
如果项目部署在Linux容器里,使用System.Drawing相关类型时,需要安装系统级的图形库依赖。容器基础镜像如果没有libgdiplus,生成的图片很可能是空白或者抛DllNotFoundException。
解决方案有两个。一是改用ZXing.NET的跨平台渲染包ZXing.Net.Bindings.SkiaSharp,它使用SkiaSharp绘图,天然跨平台,不依赖Windows GDI+。二是保持System.Drawing方案,但在Dockerfile中安装libgdiplus基础包。我个人更推荐第一种,因为它能规避整个图形库兼容性问题,而且SkiaSharp在现代.NET生态里支持度更好。
5.3 批量生成二维码的性能优化
批量生成几千张二维码时,每张都单独new一个BarcodeWriter是没问题的,核心瓶颈通常在图片保存和文件IO上。如果每张二维码都要写文件,建议用并行流或者Task并行,同时把图片格式统一为PNG,避免频繁的格式转换。
另外一个容易被忽略的优化点:同一个业务场景下,writer可以复用,因为Format和Options不会变,每次只变化写入内容。虽然BarcodeWriter不是线程安全的,但你可以为每个线程创建独立实例,实测在8核机器上批量生成5000张二维码,耗时从串行的十几秒降到两秒多。对于大多数内部系统需求,把writer定义为静态实例或者按线程池缓存,性能提升非常明显。
5.4 识别结果为空时的补充排查手段
当你确认图片内容没问题但识别结果仍然为null时,先别急着怀疑类库。我遇到过的几种真实原因包括:图片保存时被二次压缩导致过度失真、二维码区域在整张图片里占比过小、图片本身带有强反光或阴影干扰。最简单的排查办法是用截图工具把条码区域单独截出来再识别一次,如果单独区域能识别而整图不能,优先考虑裁剪和预处理,而不是继续调整解码参数。
6. 关于选型的一些个人体会
如果你正在评估要不要使用ZXing.net,我的建议是可以直接纳入备选。它最大的优势在于完全离线、支持码制多、API简单、社区成熟,大多数场景下几分钟就能跑通。和付费商业库相比,它在复杂图像识别抗干扰能力和技术支持上确实有差距,但对常规业务系统来说,这些差距基本感知不到。
我也见过一些人坚持自己画二维码、自己写解码逻辑,结果一个项目耗尽几周时间还没稳定。条码领域的水比想象中深,专业的事交给成熟的类库去做,把精力留给业务本身,这才是工程效率的正解。
最后再分享一条经验:引入ZXing.net后,建议在项目最初的冒烟测试阶段就覆盖"生成-打印-扫描-识别"全链路,排查出环境差异和打印分辨率问题。很多问题不在代码层面,而在图片从生成到被扫描的中间链路里。这个环节验证通过之后,后续项目的复用就非常顺畅了。
本文还有配套的精品资源,点击获取