简介PoDoFo 是一个用于解析、修改和创建 PDF 文档的 C 类库。这份 VS2010 工程来自作者在 Windows 10 x64 环境下的实际构建尝试因 CMake 生成解决方案多次失败转而手工创建工程并成功编译仅收录核心 src 部分不含 Samples 等附加内容适合需要直接在 Visual Studio 2010 中编译使用 PoDoFo 的 C 开发者。压缩包内共有 370 个文件以 263 个头文件与 93 个 C 源文件为主体并包含预先编译好的 freetype2、zlib 静态库和工程配置项整体大小约 2.43MB目录精简源码组织清晰。目前已有 749 人浏览学习工程覆盖 PDF 解析、绘制、加密等核心模块编译后可直接产出 DLL 与 LIB 库文件同时作者将构建时生成的必要配置头文件完整保留免去使用者自行配置的步骤也避开了依赖库版本匹配的常见坑。对于希望在旧版 VS 中集成 PDF 功能、或想研读 PoDoFo 核心源码的开发者这份现成工程可显著缩短环境准备时间快速进入开发或学习状态。1. 在VS2010里编译podofo-0.9.4前先弄懂这三件事做PDF解析和生成在VS2010里搭podofo-0.9.4工程是个典型的老环境补课问题。podofo 是一个开源 C PDF 库0.9.4 这个版本被很多老系统固化了新需求往往只能在它上面补功能而不是换库重来而 VS2010VC10虽然老但不少工业上位机、嵌入式工具链到现在还用它能编译出来承认。这篇文章想把从源码编译、CMake 生成、VS 工程配置到链接运行的完整过程捋一遍特别是那些一看就头大的 C1189、LNK2038 和运行时崩溃。适合手里有老工程要维护、或被迫把 PDF 功能塞进 2010 环境的工程师。2. 准备编译环境依赖库、工具链和目录结构2.1 下载podofo-0.9.4源码先核对两个关键文件我拿到 podofo-0.9.4 源码后第一步不是急着点 CMake而是先解压看两个东西根目录的CMakeLists.txt和cmake/Modules目录里的Find*.cmake。因为这个版本没有现成的 VS2010 工程所有.sln都是 CMake 现场生成的你后面用哪些选项、会触发哪些依赖检查全看这两个文件。打开CMakeLists.txt重点找option(PODOFO_HAVE_JPEG Use LibJPEG ...)类似的行。这些option()就是后面 CMake 命令行里-D参数的开关。默认情况下podofo 会把能开的依赖都开着libjpeg、libpng、libtiff、OpenSSL、FreeType、Lua。如果你照默认直接编VS2010 会疯狂找新版第三方库而这些库多半是用 VS2015 或 VS2019 编出来的链接阶段必然报LNK2038运行时库不匹配。所以我的习惯是第一次先全部关掉把核心 PDF 库编出来确认“无依赖也能跑”然后再按需一个个开。这也是老工程最稳妥的推进方式别想一口吃成胖子。另一个关键文件是src/podofo.h或podofoConfig.h.cmake。它决定了你用静态库还是动态库时要不要定义PODOFO_STATIC。这一步踩坑的人特别多后面第 4 章会专门说。现在你只需要确认这个版本支持静态编译并且知道自己要编的是.lib还是.dll因为 VS2010 的 Debug 和 Release 运行库配置会影响整个工程的属性继承。2.2 VS2010的工程生成方式用CMake还是自带工程podofo 官方从不提供“podofo-0.9.4-vs2010.sln”这种现成工程你必须用 CMake 生成。这里最大的坑是 CMake 版本。VS2010 的 generator 在 CMake 里叫Visual Studio 10 2010CMake 3.20 之前还保留着之后才慢慢从cmake --help里消失。所以我机器上常年留着 CMake 3.18 和一个 CMake 2.8.12 备用。如果你只有新版 CMake可以试试但生成失败时别死磕换老版本是常态。生成方式有两种命令行和 CMake GUI。GUI 的好处是能用勾选直观看到所有PODOFO_HAVE_*选项坏处是第一次设置CMAKE_PREFIX_PATH很麻烦特别是依赖库分散在不同目录时。我一般用命令行把选项写成一个 bat 脚本这样以后重新生成工程时不用去 GUI 里翻配置。还要决定 32 位还是 64 位。VS2010 的 64 位编译器并不是默认安装的很多精简安装都只有 x86。如果你的上位机是 32 位进程直接生成 Win32 即可如果必须 64 位得提前确认 VS2010 已装 x64 编译组件否则 CMake 检查 generator 会失败。2.3 手动准备依赖zlib、libjpeg、openssl等以及32位/64位选择这是最容易让新手放弃的一步。podofo 0.9.4 的可选依赖和用途如下依赖库作用关闭时的功能损失zlibPDF 流过滤 (FlateDecode)几乎必须但 podofo 源码内置了 mini zlib 吗实际上 0.9.4 仍要求外部 zlib除非改配置。libjpegJPEG 图片解码/编码无法处理 JPEG 图像libpngPNG 图像同上libtiffTIFF 图像无法嵌入 TIFFFreeType字体渲染/度量无法做文本布局测量OpenSSLAES/RSA 加密无法创建加密 PDFLua脚本功能一般用不到我的建议如果只是读写不加密的 PDF 文本、合并页面、加水印那么全关也没关系。FlateDecode 压缩在 0.9.4 里其实依赖 zlib绕不开所以 zlib 还是得准备。好消息是 zlib 很小用 VS2010 从源码编一个很轻松。你可以在 zlib 目录里打开projects/vc10工程直接生成zlibstat.lib注意配置成“Release /MD”或“Debug /MD”和后面的 podofo 工程保持一致。具体做法先建一个干净的第三方目录比如D:\thirdparty。分别把 zlib 源码解压到D:\thirdparty\zlib把 podofo 源码解压到D:\podofo-0.9.4。不要放中文路径也不要有空格。VS2010 对路径里的空格处理虽然没有太大问题但有些旧版 CMake 模块会把路径拼错所以洁癖一点。3. 用CMake生成VS2010工程并配置静态库/动态库3.1 编写CMake命令行生成.sln打开 VS2010 自带的“Visual Studio 命令提示符”或者普通 cmd 后确认cmake在 PATH 里。我习惯在源码目录外建独立构建目录这样源码不会被污染。先执行mkdir build-vs2010 cd build-vs2010 cmake -G Visual Studio 10 2010 -A Win32 ^ -DPODOFO_BUILD_SHAREDOFF ^ -DPODOFO_HAVE_JPEGOFF ^ -DPODOFO_HAVE_LIBPNGOFF ^ -DPODOFO_HAVE_TIFFOFF ^ -DPODOFO_HAVE_OPENSSLOFF ^ -DPODOFO_HAVE_FREETYPEOFF ^ -DPODOFO_HAVE_LUAOFF ^ -DPODOFO_HAVE_ZLIBON ^ -DZLIB_LIBRARYD:/thirdparty/zlib/build/zlibstat.lib ^ -DZLIB_INCLUDE_DIRD:/thirdparty/zlib ^ ..参数说明-A Win32指定平台架构只有 CMake 3.1 以上才支持-A参数。如果你用老 CMake 2.8就得去掉-A生成之后再在 VS 里改“目标平台”。如果生成失败检查 CMake 版本。-DPODOFO_BUILD_SHAREDOFF表示编静态库。静态库链接简单单一.lib文件不需要部署 DLL。但注意要用时定义PODOFO_STATIC。-DPODOFO_HAVE_ZLIBON保留 zlib同时手动指定 zlib 的库和头文件路径免得 CMake 去系统路径里瞎找。-DZLIB_LIBRARY要指到你刚才用 VS2010 编译出来的 zlib 静态库。如果 zlib 是 32 位podofo 也得是 32 位。如果这一步提示Could NOT find ZLIB多半是路径不对或 zlib 没有先编好。可以先把-DZLIB_LIBRARY和-DZLIB_INCLUDE_DIR去掉让 CMake 自己找系统路径但那样容易找到 VS2015 版 zlib后面照样 LNK2038。3.2 打开后调整运行库和字符集CMake 生成完毕后进入build-vs2010目录双击打开podofo.sln。不要急着按 F7 编译先做两件预防动作。第一检查 CMake 默认生成的运行时库设置。对于Visual Studio 10 2010生成器CMake 默认会根据 Debug/Release 选择/MDd或/MD。如果你 zlib 编的是/MD动态 CRT但 podofo 工程里某些项目被 CMake 设成了/MT链接时一样报 LNK2038。我一般会在项目属性 - C/C - 代码生成 - 运行库里把所有要用的项目都统一成“多线程调试 DLL /MDd”或“多线程 DLL /MD”。这个统一必须包含所有依赖库不然就是给自己挖坑。第二字符集改成“使用多字节字符集”。VS2010 默认新工程使用 Unicode 字符集而 podofo 内部大量使用char*和 ANSI 字符串。虽然 CMake 生成的工程不一定会强设 Unicode但你自己再建的调用工程必须注意否则后面调用 API 时参数不匹配出现PdfError全是乱码。这个坑留到第 4 章讲。3.3 编译顺序和输出文件位置在解决方案管理器里能看到多个项目核心的是podofo主项目还有其他测试项目。我可以只选中podofo项目右键“生成”。CMake 会自动处理项目依赖但要是你改了开关建议先生成一次整个解决方案确保所有由 CMake 生成的辅助项目先编好。编译成功后静态库输出通常在build-vs2010\src\Release\podofo.libDebug 则输出在build-vs2010\src\Debug\podofo.lib。头文件在源码根目录的src文件夹里但注意podofoConfig.h是由 CMake 在build-vs2010\src下生成的不是源码自带的。这个文件定义了当前配置比如是否启用 OpenSSL、是否共享库。你后面在自己的 VS2010 工程里加包含目录时必须同时包含D:\podofo-0.9.4\srcD:\build-vs2010\src少了第二个编译会出现podofoConfig.h not found。这是最容易被漏掉的生成头文件。4. 把podofo接进你的VS2010工程链接、头文件与最小示例4.1 新建VS2010控制台工程设置包含目录和库目录现在源库已经编好我们来建一个实际使用的 VS2010 工程。打开 VS2010新建一个 Win32 控制台应用程序工程名随便但最好放在和 thirdparty 同级目录避免将来路径迁移。在解决方案资源管理器中右键工程选“属性”。在VC目录下把“包含目录”加上两个路径D:\podofo-0.9.4\src和D:\build-vs2010\src。“库目录”加上D:\build-vs2010\src\Release或者 Debug 目录。然后在链接器 - 输入 - 附加依赖项里加上podofo.lib zlibstat.libzlibstat.lib是 zlib 静态库的文件名如果你用zlib.lib或zdll.lib就是动态版需要把对应 DLL 放到运行目录。对于 VS2010 老环境我强烈建议全部静态链接最后拷走一个 exe 就行。这里的关键是“运行库”必须和 podofo 编译时一致。如果 podofo 是/MDRelease你的调用工程也必须是/MDRelease否则第 5 章 LNK2038 会来找你。你可以把调用工程设置为“多线程 DLL”或“多线程调试 DLL”然后在“预处理器定义”里加上_CRT_SECURE_NO_WARNINGS这能屏蔽掉fopen等函数的安全错误提示。4.2 一个读PDF页面的最小C代码在上一节配置基础上我写过一个最简示例打开一个 PDF打印页数。因为 podofo 0.9.4 的 API 命名和现代版不太一样我用下面的代码验证基本链路通不通。#include podofo/podofo.h #include iostream using namespace PoDoFo; int main() { PdfMemDocument doc; try { doc.Load(D:\\test.pdf); std::cout PDF pages: doc.GetPageCount() std::endl; } catch (PdfError e) { std::cerr Error: e.what() std::endl; return 1; } return 0; }逻辑说明PdfMemDocument是 podofo 最常用的内存文档对象0.9.4 里加载文件用Load()函数。doc.Load如果失败会抛异常所以必须用try/catch包住。不包也行但错误提示极难定位。这里由于using namespace PoDoFo;PdfError直接可用。你可能想直接把连接矩阵放在这里但注意一个细节如果 podofo 是静态库必须确认是否定义了PODOFO_STATIC。在 4.3 节说。编译时如果报error C1189: #error : PODOFO_STATIC must be defined ...说明你没定义宏。进入项目属性——C/C - 预处理器 - 预处理器定义加上PODOFO_STATIC。这个宏会让头文件里的导入导出dllimport变成普通声明否则链接时会找不到一堆外部符号。另一个问题是 VS2010 默认的“Unicode 字符集”会让doc.Load字符串参数变成宽字符类型。PdfMemDocument::Load接收的是const char*你用 L... 就编译报错。所以正确做法是把工程字符集设成“使用多字节字符集”或者把宽字符串转成 UTF-8。我建议前者省事且没有运行时转换开销。4.3 参数说明链接库名、宏定义、预处理我把这组配置列成一张表方便你对照检查项目设置值说明包含目录D:\podofo-0.9.4\src;D:\build-vs2010\src第二个是 CMake 生成头文件所在目录库目录D:\build-vs2010\src\Release取决于你编译的配置附加依赖项podofo.lib;zlibstat.lib静态库方式运行库/MD或/MDd与 podofo 全局统一预处理器PODOFO_STATIC;_CRT_SECURE_NO_WARNINGS前者必须后者抑制 fopen 安全告警字符集使用多字节字符集匹配 podofo 的 ANSI API如果你只做读取到这一步就够了。但如果你想生成 PDF会发现PdfPainter相关 API 里隐藏更多的坑我们放到最后一章再写。这里额外提一个“黑匣子”问题podofo 的异常信息有时非常简略只返回e.what()像Error 0x...。我见过有人加载失败后完全摸不着头脑。建议在catch里多打一个e.GetErrorCode()转成字符串比如(int)e.GetErrorCode()然后对照podofo/src/PdfError.cpp里的错误码表。这条血泪经验能省你大量排查时间。5. 避坑podofo 0.9.4在VS2010下常见的5个编译/链接问题5.1 现象fatal error C1083: Cannot open include file: zlib.h现象编译 podofo 或你的调用工程时头文件阶段直接报 zlib.h 找不到。原因CMake 生成时确实指定了ZLIB_INCLUDE_DIR但该路径下没有 zlib.h或者你把 zlib 和 zlib 头文件放在了不同目录。还有一种情况是用了#include zlib.h但D:\thirdparty\zlib里只有zlib.h.in没有zlib.h——因为 zlib 源码需要先运行configure或 CMake 生成头文件。解决如果是 zlib 源码目录通常zlib.h就在根目录。如果找不到去zlib 源码\build目录找。确认你的ZLIB_INCLUDE_DIR指向了包含zlib.h的那一层而不是src。另外VS2010 的“包含目录”是全局继承的不要在“源文件目录”里写相对路径“../thirdparty/zlib”否则项目迁移后必然挂。5.2 现象LNK2038: mismatch detected for RuntimeLibrary: value MD_DynamicRelease doesnt match value MT_StaticRelease现象链接时出现 LNK2038通常发生在 podofo.lib 和你调用工程或 zlib 之间。原因podofo 编译时运行库是/MD动态 CRT而你的调用工程默认可能是/MT静态 CRT。在 VS2010 中这两者不能混用因为内存分配、静态变量所属的 CRT 实例不同链接器直接拒绝。解决打开所有相关工程——zlib 工程、podofo 工程、你的调用工程统一“运行库”选项。最省心的是都用/MD。如果你坚持用/MT那么 zlib 和 podofo 都必须用/MT重新编一遍。别想着用/MT的 podofo.lib 配/MD的调用工程那是浪费时间。5.3 现象LNK2019: unresolved external symbol class PoDoFo::PdfError __cdecl PoDoFo::PodofoSetError(...)或大量_imp_符号找不到现象编译通过但链接时报一堆外部符号未解析全是_imp_前缀或 podofo 内部类。原因最常见于把动态库DLL的导入库当成静态库用。如果 CMake 里PODOFO_BUILD_SHAREDON生成的是podofo.dll它的.lib只是导入符号。你链接了它但没有把podofo.dll放到 PATH 或 exe 目录或你在代码里定义了PODOFO_STATIC但实际链接的是 DLL 导入库头文件里的dllimport和dllexport对不上号。解决确定你到底想用静态还是动态。静态库方式重新编译时PODOFO_BUILD_SHAREDOFF并定义PODOFO_STATIC。动态方式去掉PODOFO_STATIC宏链接podofo.lib指导入库并把podofo.dll复制到 exe 所在目录。如果必须混用报这个错不用奇怪这是 VS2010 下最常见的“库形态”混淆。5.4 现象error C4996: fopen: This function or variable may be unsafe. Consider using _fsopen instead.现象编译你的代码时只要用到标准文件函数VS2010 就把 C4996 视为错误。原因VS2010 的 CRT 默认启用安全警告把fopen、strcpy、sscanf等列为 deprecated。podofo 内部大量用这些函数所以它的源码头文件也会触发这个警告。如果你在“预处理器定义”里没有加_CRT_SECURE_NO_WARNINGS警告会升级为错误。解决在你的调用工程里加入_CRT_SECURE_NO_WARNINGS。如果你在编译 podofo 自带的工程也遇到这个错误可以在 podofo 工程的“预处理器定义”里也加上它。这个宏没有副作用只是告诉 CRT 不报这些安全警告。如果你追求安全可以替换成_s版本但在 podofo 源码里到处改不现实。我的血泪经验是老工程直接用宏别折腾源码替换。5.5 现象运行时崩溃Unhandled exception at 0x...或PdfError内部断言的SetError(0x0001... )现象读某些 PDF 文件时崩溃生成 PDF 时也会在Write阶段突然抛异常。关闭所有可选依赖后调试器在PdfFilter或PdfTokenizer里中断。原因大概率是 zlib 版本和 podofo 0.9.4 不兼容。podofo 走 FlateDecode 时依赖 zlib 的inflateInit和inflate如果你用的是 zlib 1.2.12 以后的高版本某些结构体大小和旧版不同而 podofo 0.9.4 是按旧 zlib 编译的运行时在 DLL 边界上换结构体会导致内存越界。或者你用了 VS2015 编的 zlib内部 CRT 调用的栈和 VS2010 不一致。解决用 VS2010 从源码重编 zlib并且固定一个和 podofo 0.9.4 同年代的主流版本比如 zlib 1.2.8。不要贪新。同时PODOFO_HAVE_OPENSSL关闭后如果你调用了加密相关 API也会因为加密后端为空而崩溃。检查自己是否使用了PdfEncrypt如果用了必须把 OpenSSL 打开否则这种崩溃是无解的。6. 让podofo为你生成PDF迷你写入示例与后续扩展读通了之后生成 PDF 就是顺理成章的事。这里给一个我验证过的写入示例它只输出一行文字到 A4 页面没有花哨功能但能帮你确认整条编译链没问题。#include podofo/podofo.h #include iostream using namespace PoDoFo; int main() { try { PdfMemDocument doc; PdfPage* pPage doc.CreatePage(PdfPage::CreateStandardPageSize(PdfPageSize::A4)); if (!pPage) { std::cerr Failed to create page std::endl; return 1; } PdfPainter painter; painter.SetPage(pPage); PdfFont* pFont doc.CreateFont(Helvetica); if (!pFont) { std::cerr Failed to create font std::endl; return 1; } painter.SetFont(pFont); painter.DrawMultiLineText(Hello from PoDoFo 0.9.4 VS2010, 50, 50); painter.FinishPage(); doc.Write(output.pdf); std::cout PDF written successfully std::endl; } catch (PdfError e) { std::cerr PdfError: e.what() code static_castint(e.GetErrorCode()) std::endl; return 1; } return 0; }代码说明PdfPage::CreateStandardPageSize(PdfPageSize::A4)生成标准 A4 尺寸。如果想自定义页面可以用PdfPage的SetSize但 0.9.4 的这组 API 要求传入PdfRect对象参数顺序极容易记错建议第一次就照示例写。painter.DrawMultiLineText是绘制文本的函数坐标原点在页面左下角50, 50表示距左下角 50 个单位。VS2010 的double和 podofo 的double没有差异但注意如果页面旋转过坐标映射会变。doc.Write(output.pdf)会覆盖已存在文件。如果文件正在被另一个程序占用会抛异常这个异常需要捕获。编译这个示例时记得把PODOFO_STATIC和字符集设置照 4.3 节核对一遍。运行后检查生成的output.pdf用 Adobe Reader 或浏览器能正常打开。如果打不开多半是 zlib 版本问题回到 5.5 条。往后扩展时我最常用的几个方向是功能podofo 0.9.4 的入口备注嵌入图片PdfImagePdfPainter::DrawImage需要打开 JPEG/PNG 依赖加密PdfEncrypt需要打开 OpenSSL 并设置密钥读取表单字段PdfAcroForm老 API 和新版本差异较大合并/拆分页面PdfMemDocument::Append0.9.4 对大型文件内存开销大顺带说一个习惯我每次拿到老库都会先留一个“干净工程”不含业务代码只保留编译配置和最小示例。等哪天换电脑或交接时不用重新折腾 CMake 参数下午茶时间就能重新跑起来。这和用什么高深的技巧无关纯粹是吃过翻车的亏想留一颗后悔药。希望帮到你。本文还有配套的精品资源点击获取