源码编译QRencode:C/C++项目集成二维码生成完整指南
简介一份基于QRencode最新版2019年3月发布的4.02的二维码生成源码编译实例面向需要在Qt环境中快速集成二维码功能的开发者尤其适合具备基本C知识、想缩短开发周期的初中级程序员。压缩包共51个文件仅446KB其中12个头文件与9个C源文件实现核心编码逻辑两个C文件、一个工程文件和一个界面文件组成Qt程序框架configure、CMakeLists.txt等脚本便于跨平台编译配置整体结构清晰可快速定位关键模块。已有404人学习说明这个小巧实例对同类需求具有实际参考价值。用QTCreator打开即可运行省去从零搭建的繁琐QRencode 4.02作为当时较新版本可参考其接口移植到其他平台或封装成库复用完整代码与编译细节能帮助开发者落地二维码生成模块。整个工程简洁规整既能直接上手使用也可作为二维码编码原理的学习范本减少重复造轮子成本。 搞源码编译这阵子真是停不下来前面刚把 Linux 下的 Neo4j 源码编译安装跑通又有人在群里问 Win10 VS2019 编译 Qt 5.15 的坑其实思路都是一套先理依赖再配工具链最后构建。我这边正好有个老项目要在纯 C 环境里集成二维码生成功能干脆把 QRencodelibqrencode也从头编译了一遍写了个命令行小工具直接出图。这篇就把整个流程记录下来从拉源码到编译、再到写出能跑的二维码生成代码一步不落代码我放到文里直接拿去用就行。适合需要在 C/C 项目里生成二维码、但不想为这个功能引入 Java 或 Python 运行时依赖的朋友。1. 为什么从源码编译先把需求和工具链理清1.1 QRencode 能做什么QRencode 是一个用 C 写的开源二维码编码库GitHub 上主要维护在 fukuchi/libqrencode 仓库当前稳定版本到了 4.1.x。它做的事情很纯粹把一段字符串、二进制数据或者 URL 编码成符合 ISO/IEC 18004 标准的二维码点阵数据。它支持四种纠错级别L/M/Q/H支持数字、字母数字、字节、汉字等多种编码模式还内置了 Reed-Solomon 纠错编码和掩码处理。我选择它的核心原因就两个字轻。整个库编完之后静态库体积很小没有额外的运行时环境头文件加一个库文件就能用对嵌入式设备、服务端工具、桌面小工具都非常友好。对比同样是二维码方案的 ZXingQRencode 的定位是纯编码端不承担解码任务代码路径短集成成本低。如果你的场景只需要“把内容变成二维码图片”用它非常合适。1.2 直接用包管理器不行吗很多人第一反应是apt install libqrencode-dev 不就行了确实Linux 上直接装包能省不少事但源码编译的价值通常在一些特定场合体现出来。比如你需要给老系统编译一个指定版本需要裁剪掉默认开启的 PNG 输出模块需要静态链接进现有程序避免目标机器上缺依赖或者你本身就有一批代码要从源码构建就像前面提到的 bit7z、libssh 源码编译一样那库也得跟着源码走。还有一个容易忽略的点包管理器里的库版本往往滞后而且编译参数未必符合你的需求。比如官方包默认开启了 libpng会自动把 PNG 写文件模块也编进去可你根本用不到 PNG只想拿原始点阵数据自己渲染那源码编译时直接关掉 PNG 依赖出来的库更干净链接也更简单。这也是我这次选择源码编译的直接原因。1.3 选 QRencode 而不是其他库市面上生成二维码的库很多C 语言这一层主要就是 libqrencode 和 libqr。libqr 的问题在于维护不活跃接口风格也比较老。libqrencode 接口设计清晰QRcode_encodeString()一行就能拿到点阵数据后续绘制成位图、SVG、终端字符画都很方便。另外它支持QRcode_encodeData()处理非字符串二进制数据灵活性更高。对嵌入式内存受限的场景也可以自己控制版本号甚至能直接拿到编码后的模块矩阵省去中间转换开销。注意QRencode 只负责“生成”不负责“识别”。如果项目里还要扫码就得配合 OpenCV 的 QRCodeDetector 或者 ZBar 来做解码端。我这边是纯生成需求所以不需要考虑解码。2. 编译前的环境准备Linux 和 Windows 两条路2.1 Linux 依赖清单Linux 下编译依赖比较少核心工具链只要 gcc、make、pkg-config 和 autoconf 系列。如果需要输出 PNG 图片还要装 libpng 和 zlib 的开发包。Ubuntu/Debian 系统执行下面的命令就能把环境补齐sudo apt install build-essential autoconf automake libtool pkg-config sudo apt install libpng-dev zlib1g-dev这里有一个很容易踩的坑有些机器上已经能正常打开 PNG 图片就以为系统里有 libpng结果 configure 阶段一直报PNG library not found。原因往往是你只装了运行时库没装-dev后缀的开发包头文件和libpng符号链接都不存在。所以编译类问题先别怀疑系统坏没坏第一反应应该是检查开发包齐不齐。2.2 Windows VS2019 依赖清单Windows 上建议直接用 CMake 配合 Visual Studio 2019 生成工程不需要手动维护.vcxproj文件。你需要确保 VS2019 里装了“使用 C 的桌面开发”工作负载并且包含 CMake 工具。如果你只是为了生成点阵数据自己画图可以不装 libpng稍后编译时用-DWITH_PNGOFF关掉这个模块少一层依赖。zlib 在 Windows 下同理如果编译脚本检测不到直接关掉即可。只有一种情况你必须补齐这些依赖真的需要库本身就输出 PNG 文件。如果只是拿到QRcode结构体之后自己渲染那 PNG 模块完全是个冗余项。2.3 拉取源码和确认版本源码直接 clone 官方仓库git clone https://github.com/fukuchi/libqrencode.git cd libqrencode确认版本我用的是 tag 列表里的最新稳定版git tag -l git checkout v4.1.1锁定版本很重要。开发版可能带新特性但 API 可能有微调和生产环境依赖的接口不一定一致。我这边项目要稳定所以 checkout 到 v4.1.1。源码目录里的qrencode.h是唯一的对外头文件整个库的核心 API 都声明在这里后续写代码主要就是围绕它来做。3. 完整编译过程从源码到库文件的每一步3.1 Linux 下最简编译流程QRencode 用的也是标准的 autotools 构建流程四步走./autogen.sh ./configure --without-tools --without-png make -j$(nproc) sudo make install sudo ldconfig--without-tools是关掉自带的命令行工具编译只留下库本体--without-png就是刚才说的跳过 libpng 检查编译库时不再依赖图片库。编译结束之后头文件会装到/usr/local/include静态库libqrencode.a和动态库libqrencode.so出现在/usr/local/lib。如果你之前没给/usr/local/lib配置过动态库搜索路径记得执行sudo ldconfig否则后面编译好的测试程序运行时可能报cannot open shared object file。3.2 Windows 下用 CMake 生成 VS2019 工程Windows 下最好用 CMake 直接生成工程命令如下cmake -S . -B build -G Visual Studio 16 2019 -A x64 -DWITH_PNGOFF cmake --build build --config Release第一行负责生成 VS2019 的工程文件和解决方案-DWITH_PNGOFF还是用来关闭 PNG 依赖。第二行直接执行 Release 构建。编完以后库文件在build/Release/目录下一个是qrencode.lib导入库一个是qrencode.dll动态库静态库则是qrencode_static.lib。把自己项目里的头文件路径指到源码根目录的qrencode.h链接时加上qrencode.lib再把 DLL 复制到 exe 旁边就能正常跑了。3.3 编译日志里藏着什么信息不要忽略编译输出里的关键行。configure 结束时会有一段 summary明确写着 PNG 支持是否开启、工具是否编译。比如你想验证 PNG 到底关没关掉直接看这段输出就行Configuration: Encoding tool: no PNG output: no ...如果这里明明写了 no但编译过程中还在找 libpng那基本可以确定是缓存问题删掉 build 目录重来一遍就好。我在 CMake 里因为改过好几次选项遇到过一次类似情况最后把 build 目录整个删掉重新生成就干净了。4. 直接能用的二维码生成代码4.1 QRcode 结构体究竟怎么用编译安装好库之后核心就是一个结构体QRcode。它在qrencode.h里定义如下typedef struct { int version; /* 二维码版本号1-40 */ int width; /* 模块矩阵宽度宽度 x width 个模块 */ unsigned char *data; /* 点阵数据每个字节的最低位是 0/1 */ } QRcode;version决定二维码能塞多少数据容量越大版本越高。width是模块矩阵的边长不管实际内容多短它永远是一个正方形矩阵。data数组长度是width * width每个字节的最低位对应一个模块是黑还是白。你要做的所有绘制工作本质上就是遍历这个数组把data[i] 0x01翻译成一个像素点或者一个色块。这里取的是最低位别直接拿整个字节去判断我见过有人用 0x01判断导致颜色反掉的例子。4.2 最小可运行版输出 PBM 验证库是否正常为了先验证库本身没问题我写了一个最小版本把点阵输出成 PBM 格式。PBM 是一种极其简单的文本图片格式第一行写P1第二行写宽和高后面按行写 0/1 像素。这个工具不依赖任何图片库逻辑直白非常适合做链路验证。#include stdio.h #include stdlib.h #include qrencode.h int main(int argc, char *argv[]) { if (argc 2) { fprintf(stderr, usage: %s text\n, argv[0]); return 1; } QRcode *qr QRcode_encodeString(argv[1], 0, QR_ECLEVEL_M, QR_MODE_8, 1); if (!qr) { fprintf(stderr, encode failed\n); return 1; } printf(version: %d, width: %d\n, qr-version, qr-width); FILE *fp fopen(qrcode.pbm, wb); fprintf(fp, P1\n%d %d\n, qr-width, qr-width); for (int y 0; y qr-width; y) { for (int x 0; x qr-width; x) { int dark qr-data[y * qr-width x] 0x01; fputc(dark ? 1 : 0, fp); fputc( , fp); } fputc(\n, fp); } fclose(fp); QRcode_free(qr); return 0; }编译命令gcc -o qrgen qrgen.c -lqrencode ./qrgen Hello, QRencode!运行完以后目录下会多出qrcode.pbm。这个格式文件 Windows 自带看图工具打开不了需要配一个看图软件或者转一下。PBM 在这个阶段定位就是“验证库链路没问题”真正交付到项目里我们还是用下一节增强版。4.3 升级版输出缩放后的 BMP 图片PBM 文件没法直接交付而且没有放大扫起来费劲。实际使用中我们需要输出一张带白边、带缩放因子的位图这样用户拿手机一扫就能识别。我选择 BMP 而不是 PNG就是因为它结构简单、不需要任何第三方库二维码识别器对图片格式不敏感大家都读像素点。#include stdio.h #include stdlib.h #include string.h #include qrencode.h static void write_bmp(const char *filename, const QRcode *qr, int scale, int margin) { int modules qr-width; int img_size (modules margin * 2) * scale; int row_size ((img_size * 3 3) / 4) * 4; int data_size row_size * img_size; int file_size 54 data_size; unsigned char header[54]; memset(header, 0, sizeof(header)); header[0] B; header[1] M; header[2] (unsigned char)(file_size 0xFF); header[3] (unsigned char)((file_size 8) 0xFF); header[4] (unsigned char)((file_size 16) 0xFF); header[5] (unsigned char)((file_size 24) 0xFF); header[10] 54; header[14] 40; header[18] (unsigned char)(img_size 0xFF); header[19] (unsigned char)((img_size 8) 0xFF); header[20] (unsigned char)((img_size 16) 0xFF); header[21] (unsigned char)((img_size 24) 0xFF); header[22] (unsigned char)(img_size 0xFF); header[23] (unsigned char)((img_size 8) 0xFF); header[24] (unsigned char)((img_size 16) 0xFF); header[25] (unsigned char)((img_size 24) 0xFF); header[26] 1; header[28] 24; FILE *fp fopen(filename, wb); if (!fp) return; fwrite(header, 1, 54, fp); unsigned char pixel[3]; for (int y img_size - 1; y 0; y--) { int bytes_in_row 0; for (int x 0; x img_size; x) { int qx (x / scale) - margin; int qy (y / scale) - margin; int dark 0; if (qx 0 qx modules qy 0 qy modules) { dark qr-data[qy * modules qx] 0x01; } if (dark) { pixel[0] pixel[1] pixel[2] 0; } else { pixel[0] pixel[1] pixel[2] 255; } fwrite(pixel, 1, 3, fp); bytes_in_row 3; } while (bytes_in_row % 4 ! 0) { fputc(0, fp); bytes_in_row; } } fclose(fp); } int main(int argc, char *argv[]) { if (argc 2) { fprintf(stderr, usage: %s text [scale] [margin]\n, argv[0]); return 1; } int scale argc 2 ? atoi(argv[2]) : 8; int margin argc 3 ? atoi(argv[3]) : 4; QRcode *qr QRcode_encodeString(argv[1], 0, QR_ECLEVEL_M, QR_MODE_8, 1); if (!qr) { fprintf(stderr, encode failed: maybe input too long?\n); return 1; } write_bmp(qrcode.bmp, qr, scale, margin); QRcode_free(qr); printf(saved to qrcode.bmp, width%d, scale%d, margin%d\n, qr-width, scale, margin); return 0; }几个关键点说明一下。BMP 的像素是从下往上存储的所以外层循环 y 要从图片底部开始倒着走。每一行字节数必须是 4 的倍数不够就补零。24 位 BMP 每个像素占 3 个字节颜色顺序是 BGR但这里黑和白三个分量都相同所以不需要纠结顺序。margin是静区标准要求二维码周围至少保留 4 个模块宽度的空白我默认传 4。scale是每个二维码模块对应几个像素默认 8这个尺寸在手机屏幕上识别非常轻松。编译方式gcc -o qrgen_bmp qrgen_bmp.c -lqrencode ./qrgen_bmp https://example.com?id123 8 4想在 Windows 下用这套代码新建一个控制台项目把源码里的qrencode.h路径加到附加包含目录把编译生成的 lib 路径加到附加库目录再把 DLL 放到 exe 目录即可。4.4 参数选择背后的逻辑版本号传 0意思是让库自动选择能装下当前数据的最小版本。这对我们这种通用工具最合理不用手工计算容量。纠错级别选QR_ECLEVEL_M它能恢复约 15% 的数据损坏日常放在海报、包装上足够了。如果想更抗污损可以升级到 Q25%或 H30%但容量会缩小信息密集度会更高。编码模式选QR_MODE_8是按字节编码对 URL、ASCII 文本、UTF-8 中文都通用是兼容性最好的选择。casesensitive1表示区分大小写字母数字混合内容时语义更准确。注意内容越长二维码版本越高模块矩阵越大。相同 scale 下高版本二维码的图会更大但单个模块尺寸不变所以远看更密集。如果内容里带了很长的 URL建议先用短链服务压一下二维码扫描识别的成功率会高很多。5. 编译和生成环节的常见坑5.1 configure 一直报依赖缺失第一种情况是没装 libpng-dev 和 zlib1g-dev这个装一下就好。第二种情况是系统里其实装了但 configure 的缓存路径没更新这个时候删掉源码目录下的config.cache重新 clean 再 configure。我用 CMake 遇到过更迷的情况CMakeCache.txt 里缓存了旧的 PNG 路径后来我把库卸了重新装反而报找不到。删除 build 目录全部重新生成问题直接消失。说到底构建系统缓存不可信改完依赖先清理再编译。5.2 生成图片扫不出来排在第一位的原因是静区不够。二维码标准要求四周至少留出 4 个模块的空白区域如果紧贴边缘打印很多扫码器直接拒识。另一个高频原因是每个模块的像素数太少当 scale1 或 2 时一张 29x29 的二维码实际只有 30-60 像素稍微有干扰点就废了。我建议 scale 最低开到 4常规用 8打印用途甚至可以开到 12 以上。还有一个反向问题有些二维码贴到网页或 UI 上会被 CSS 压缩几十像素看着能扫实际一压缩模块就糊了。这种情况要么保证渲染尺寸与模块数成倍数关系要么直接输出更高分辨率的图。5.3 中文内容乱码QRcode 库本身不管字符编码它只是把字节码进二维码。如果你的程序内部是 GBK 编码直接把字符串传进去手机扫码后按 UTF-8 解码就会得到乱码。解决办法是统一在调用 encode 之前把内容转成 UTF-8。Linux 下通常系统就是 UTF-8问题不大Windows 下建议用MultiByteToWideChar配合WideCharToMultiByte做一次 GBK 到 UTF-8 的转换或者代码文件直接保存成带 BOM 的 UTF-8再配合 VS 的/utf-8编译选项。核心原则是不要依赖默认代码页。5.4 内存泄漏与 NULL 返回值QRcode 结构体是 malloc 出来的用完之后必须调QRcode_free(qr)释放。我见过写工具链时只调 encode 不调 free连续生成几千张二维码后内存暴涨的例子。另外QRcode_encodeString返回 NULL 不代表是世界末日常见原因有三个数据超出当前版本容量、版本号传的太小、编码模式不支持内容类型。最省心的做法是 version 传 0 让库自动选如果还是 NULL再考虑把纠错级别从 H 降到 M或者精简内容。排查问题的时候我习惯列一张“现象-原因-解法”的速查表放这里正好用上现象常见原因处理方式configure/CMake 找不到 PNG缺 dev 包或缓存过期安装libpng-dev清理缓存重来链接报错找不到库函数头文件或库路径没配检查 include/lib 路径和-lqrencode图片模糊扫不出模块像素太少、无静区scale 调大到 8 以上margin 设 4中文乱码字符编码不一致统一成 UTF-8 后再编码encode 返回 NULL内容过长或版本受限version 传 0调低纠错级别DLL 缺失运行报错动态库没放对位置把 qrencode.dll 放到 exe 同级目录这套排查思路不仅适用 QRencode你折腾 bit7z、libssh 这类源码编译项目时也一样先清理缓存、再查依赖、最后看链接配置大部分问题都能在十分钟内解决。我在实际使用中的体会是源码编译这件事最难的往往不是编译本身而是对自己项目到底需要哪些依赖模块判断不清楚。QRencode 这种小库算很友好的能明确关掉可选模块、依赖也不深如果你遇到 Qt 5.15 那种超大工程思路更要前置先想清楚要哪些组件再动手配置否则一个晚上基本耗在等待编译和逐个排查报错上了。后来我在项目里就是基于这版 BMP 输出改的把白色像素改成透明通道直接喂给设备端的 UI 做叠加渲染完全不再需要动态链接 libpng整体体量又小了一圈。你如果也想在 C/C 嵌入二维码生成这个方案足够稳直接照着编译配置就能落地。本文还有配套的精品资源点击获取