简介:本资源为 PoDoFo 0.9.4 的 VS2010 工程文件,面向需要在 Windows 平台使用 C++ 操作 PDF 的开发者。PoDoFo 是用于解析、修改和创建 PDF 文档的 C++ 类库,此工程仅包含核心 src 部分,不含 Samples 等示例代码。作者因用 CMake 生成 sln 屡次失败,改为手动创建工程并纳入源文件,已在 Win10 X64 与 VS2010 下编译通过,可生成 dll 和 lib,依赖 freetype2 与 zlib 静态库并已放入对应目录。压缩包共 370 个文件,以 263 个 h 头文件、93 个 cpp 源文件为主,另含 3 个 lib 静态库及 vcxproj、sln 等工程配置,整体约 2.43MB。目前已有 749 人学习下载。读者可直接获得一套可编译的 PDF 库工程,省去 CMake 配置的排错成本,并参考其头文件与库目录组织方式,快速接入 freetype2、zlib 等依赖,为后续 PDF 解析、加密、字体与图像处理等模块开发提供基础。
1. podofo-0.9.4 配 VS2010:一个被低估的 PDF 处理组合
手里有一堆 PDF 要批量抽文本、改元数据、拆页合并,又不想引入体积庞大的商业库,podofo 往往是最先被想起的 C++ 方案。它轻、依赖少、API 直白,0.9.4 这个版本在 Windows 上的稳定性也经过了不少项目验证。但真正让人卡住的从来不是 podofo 本身,而是把它塞进 VS2010 这套老工具链里——VS2010 默认的 C++ 标准、字符集、第三方库链接方式,和 podofo 的构建脚本之间有一堆需要手动对齐的地方。这篇笔记就围绕「podofo-0.9.4 在 VS2010 下怎么编、怎么用、坑在哪」展开,适合还在维护 VS2010 工程、需要离线处理 PDF 的 C++ 开发者。下面从依赖准备一路讲到最小可运行示例和排错。
2. 编译前必须理清的依赖与工程结构
2.1 podofo 0.9.4 到底依赖什么
podofo 的核心能力是解析和生成 PDF,它本身不重复造轮子,而是把压缩、字体、加密这些事交给成熟的第三方库。0.9.4 在 Windows 下的典型依赖包括 zlib(流压缩)、libjpeg(JPEG 图像)、libtiff(TIFF 图像)、freetype(字体渲染)以及 openssl(加密与签名)。其中 zlib 和 freetype 是硬依赖,缺了直接编译不过;libjpeg、libtiff、openssl 可以通过 CMake 选项关掉,但关掉之后对应的 PDF 特性就没了。
这里有个容易忽略的点:podofo 0.9.4 的 CMake 脚本对依赖的查找方式是find_package加手动指定路径。VS2010 时代很多第三方库还是以「头文件 + lib + dll」三件套的形式散落在磁盘上,没有现代包管理器帮忙,所以你得提前把每个依赖的 include 目录和 lib 目录准备好。我一般会在磁盘上建一个third_party目录,按库名分子目录,每个子目录下再分include和lib,这样后面在 CMake 里填路径时不容易乱。
另一个现实问题是字符集。VS2010 默认工程用的是多字节字符集(MBCS),而 podofo 内部大量使用std::string和PdfString,对宽字符的支持需要额外配置。如果你打算处理中文路径或中文内容,建议在工程属性里把字符集改成「使用 Unicode 字符集」,否则后面读写文件时会出现路径乱码。这个改动要在编译 podofo 之前就定好,编完再改会导致 ABI 不一致。
2.2 用 CMake 生成 VS2010 工程的具体命令
podofo 0.9.4 官方推荐用 CMake 构建,而不是直接给一个现成的.sln。所以第一步是装一个和 VS2010 兼容的 CMake 版本。VS2010 对应的是 CMake 2.8.x 到 3.5 左右的生成器,太新的 CMake 已经移除了对 VS2010 的支持。我一般用 CMake 3.4.3,这个版本还能稳定生成Visual Studio 10 2010的工程文件。
假设 podofo 源码解压在D:\src\podofo-0.9.4,第三方库在D:\third_party,构建目录用D:\build\podofo。打开命令行,执行:
cd /d D:\build\podofo cmake -G "Visual Studio 10 2010" ^ -DCMAKE_INSTALL_PREFIX=D:/install/podofo ^ -DPODOFO_BUILD_SHARED=ON ^ -DPODOFO_BUILD_STATIC=OFF ^ -DZLIB_INCLUDE_DIR=D:/third_party/zlib/include ^ -DZLIB_LIBRARY=D:/third_party/zlib/lib/zlib.lib ^ -DFREETYPE_INCLUDE_DIRS=D:/third_party/freetype/include ^ -DFREETYPE_LIBRARY=D:/third_party/freetype/lib/freetype.lib ^ -DLIBJPEG_INCLUDE_DIR=D:/third_party/libjpeg/include ^ -DLIBJPEG_LIBRARY=D:/third_party/libjpeg/lib/jpeg.lib ^ D:/src/podofo-0.9.4这段命令里几个参数值得说明。-G "Visual Studio 10 2010"指定生成 VS2010 工程,这是整个流程的起点。PODOFO_BUILD_SHARED=ON表示生成动态库,这样你的主工程只需要链接podofo.lib并带上podofo.dll,编译速度比静态库快很多;如果要做单文件分发再考虑静态库。CMAKE_INSTALL_PREFIX是后面INSTALL目标安装头文件和库的位置,建议单独放一个干净目录,不要和源码混在一起。
执行完如果没有报错,D:\build\podofo下会出现podofo.sln。用 VS2010 打开,先选Release配置,然后依次编译ALL_BUILD和INSTALL。编译过程中最常见的失败是找不到某个依赖的头文件,这时候回到 CMake 输出里看它实际在哪个路径找,把对应变量补上重新生成即可。
2.3 生成后目录里哪些东西要带走
编译并安装成功后,D:/install/podofo下会有include、lib、bin三个目录。include里是 podofo 的全部头文件,lib里是podofo.lib(导入库),bin里是podofo.dll。你的业务工程需要的是:头文件目录加到「附加包含目录」,podofo.lib加到「附加依赖项」,podofo.dll放到可执行文件同目录或系统 PATH 能覆盖的位置。
这里有个血泪经验:VS2010 的「附加依赖项」里如果同时出现 Debug 和 Release 版本的 lib,链接器会按顺序取第一个匹配的,很容易在 Release 下链到 Debug 库导致运行时崩溃。所以建议在工程属性里用$(Configuration)宏区分,比如 Debug 配置写podofo_d.lib,Release 配置写podofo.lib。podofo 0.9.4 默认只生成 Release 版,如果你需要 Debug 版,得在 CMake 里把CMAKE_BUILD_TYPE或配置管理器里的 Debug 配置也编一遍。
3. 在 VS2010 工程里跑通第一个 PDF 读取示例
3.1 新建控制台工程并配置 podofo 路径
打开 VS2010,新建一个 Win32 控制台应用程序,选择空项目。然后在「项目属性」里做三件事。第一,C/C++→常规→附加包含目录,加入D:/install/podofo/include。第二,链接器→常规→附加库目录,加入D:/install/podofo/lib。第三,链接器→输入→附加依赖项,加入podofo.lib。如果前面编译时开了 libjpeg、freetype 等依赖,这些库的 lib 也要一并加进来,否则链接阶段会报一堆未解析的外部符号。
字符集方面,在配置属性→常规→字符集里选「使用 Unicode 字符集」。podofo 的PdfMemDocument在加载文件时接受const char*路径,但如果你用std::wstring存路径,需要自己转成 UTF-8 再传进去。这一点在中文路径下特别重要,直接传宽字符会编译不过。
3.2 最小可运行的 PDF 文本抽取代码
下面这段代码演示用 podofo 0.9.4 打开一个 PDF,遍历每一页,把页面内容里的文本抽出来打印到控制台。这是验证工程配置是否正确的标准动作。
#include <podofo/podofo.h> #include <iostream> #include <string> using namespace PoDoFo; int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "usage: pdfdump <file.pdf>" << std::endl; return 1; } try { // 加载 PDF 文档,PdfMemDocument 会把整个文件读进内存 PdfMemDocument doc; doc.Load(argv[1]); int pageCount = doc.GetPageCount(); std::cout << "pages: " << pageCount << std::endl; for (int i = 0; i < pageCount; ++i) { PdfPage* page = doc.GetPage(i); if (!page) continue; // PdfContents 负责解析页面内容流 PdfContents contents(page); PdfContentsTokenizer tokenizer(&contents); PdfVariant variant; EPdfContentsType type; const char* token = NULL; // 逐 token 读取,遇到文本操作符时输出字符串 while (tokenizer.ReadNext(token, type, variant)) { if (type == ePdfContentsType_Keyword) { std::string kw(token); if (kw == "Tj" || kw == "TJ") { if (variant.IsString()) { std::cout << variant.GetString().GetStringUtf8() << std::endl; } } } } } } catch (PdfError& e) { std::cerr << "podofo error: " << e.what() << std::endl; return 2; } return 0; }这段代码的逻辑分三层。第一层是PdfMemDocument::Load,它负责解析 PDF 的交叉引用表、对象流和元数据,把整个文档结构建到内存里。第二层是PdfContents和PdfContentsTokenizer,前者把页面的内容流解压出来,后者把内容流拆成一个个 token。第三层是判断 token 类型,当遇到Tj或TJ这类文本显示操作符时,把紧随其后的字符串取出来。
参数方面,doc.Load接受的是文件路径,内部按二进制读取,所以路径里不要有中文(除非你确认字符集配置正确)。GetPage(i)的索引从 0 开始,越界会返回 NULL。GetStringUtf8()返回的是 UTF-8 编码的std::string,在 VS2010 的控制台里直接输出中文可能显示为乱码,这是控制台代码页的问题,不是 podofo 的问题,可以先把结果写到文件再用支持 UTF-8 的编辑器查看。
3.3 编译链接时最容易报的三个错
第一个错是LNK2019: 无法解析的外部符号,后面跟着一堆PoDoFo::开头的函数名。这通常是因为podofo.lib没加进附加依赖项,或者加的是 Debug 版而当前是 Release 配置。检查「链接器 → 输入 → 附加依赖项」里是否有podofo.lib,以及「附加库目录」是否指向了正确的lib文件夹。
第二个错是C1083: 无法打开包括文件: "podofo/podofo.h"。这说明「附加包含目录」没配好,或者 podofo 安装时头文件没有正确复制到include目录。回到D:/install/podofo/include确认podofo子目录存在,里面能看到podofo.h。
第三个错是运行时弹出「找不到 podofo.dll」。podofo.dll必须和你的.exe在同一目录,或者放在系统 PATH 覆盖的目录里。VS2010 调试时工作目录默认是工程目录,把 dll 复制到工程目录下即可。如果不想每次手动复制,可以在「生成事件 → 后期生成事件」里加一条copy命令。
4. 避坑与排查:VS2010 下 podofo 的五个真实翻车点
4.1 现象:编译 podofo 时提示找不到 freetype 头文件
原因:CMake 的FREETYPE_INCLUDE_DIRS变量名在不同 CMake 版本里大小写敏感,有些版本要求写成FREETYPE_INCLUDE_DIR(单数),写错时 CMake 不会报错,而是静默使用空路径,到编译阶段才暴露。
解决:在 CMake 输出里搜索FREETYPE,看它实际解析到的路径是什么。如果为空,改用-DFREETYPE_INCLUDE_DIR=...重新生成。更稳妥的做法是在 CMakeLists 里直接写死路径,但那样会破坏可移植性,只适合本地临时调试。
4.2 现象:链接时报unresolved external symbol __imp__...
原因:podofo 依赖的 zlib 或 freetype 是动态库版本,但你的工程只链接了 podofo 的导入库,没有把依赖库的导入库也加进来。VS2010 不会自动传递依赖,必须手动补全。
解决:把zlib.lib、freetype.lib、jpeg.lib等全部加到「附加依赖项」里。如果这些库有对应的 dll,也要一并复制到可执行文件目录。可以用dumpbin /dependents podofo.dll查看 podofo 运行时到底依赖哪些 dll,按图索骥。
4.3 现象:程序运行到doc.Load时抛出PdfError,提示Invalid PDF header
原因:PDF 文件本身损坏,或者文件路径传错了。还有一种可能是文件被其他进程占用,Load打开失败但错误信息被包装成了格式错误。
解决:先用 PDF 阅读器确认文件能正常打开。然后在代码里把argv[1]打印出来,确认路径没有多余空格或转义字符。如果路径含中文,改成纯英文路径再试。podofo 0.9.4 对某些加密 PDF 的支持有限,如果文件有密码,Load也会失败,需要先用SetPassword提供密码。
4.4 现象:抽取出来的中文全是乱码
原因:PDF 里的文本编码可能是 CID 字体或自定义编码,podofo 的GetStringUtf8()只能处理标准编码。如果字体没有正确的 ToUnicode 映射,抽出来的就是字形索引而不是可读文本。
解决:这种情况没有通用解法,需要针对具体 PDF 的字体做映射。可以先用PdfFont接口检查字体的编码方式,如果是ePdfFont_Type0且没有 ToUnicode CMap,基本只能放弃文本抽取,改用渲染成图片再 OCR 的方案。这也是 podofo 相比商业库的一个明显短板。
4.5 现象:Release 下运行正常,Debug 下崩溃
原因:podofo 0.9.4 默认只编了 Release 版,Debug 工程链接 Release 库时,CRT(C 运行时)版本不一致,内存分配和释放跨了不同的堆,导致堆损坏。
解决:要么在 CMake 里把 Debug 配置也编一遍,生成podofo_d.lib和podofo_d.dll;要么在 Debug 工程里也使用 Release 版的 CRT(不推荐,会失去调试信息)。我一般选前者,虽然多花一次编译时间,但后面调试省心。
5. 进阶:把 podofo 封装成可复用的 PDF 处理模块
5.1 用 RAII 包装文档生命周期
直接在主流程里裸调PdfMemDocument容易漏掉异常处理,尤其是Load和Save之间如果抛异常,文档对象可能处于半初始化状态。我习惯写一个薄封装类,把加载、保存、异常转换都收进去。
class PdfDocument { public: PdfDocument() : doc_(new PdfMemDocument()) {} ~PdfDocument() { delete doc_; } bool Load(const std::string& path) { try { doc_->Load(path.c_str()); return true; } catch (PdfError& e) { lastError_ = e.what(); return false; } } bool Save(const std::string& path) { try { doc_->Save(path.c_str()); return true; } catch (PdfError& e) { lastError_ = e.what(); return false; } } int PageCount() const { return doc_->GetPageCount(); } const std::string& LastError() const { return lastError_; } private: PdfMemDocument* doc_; std::string lastError_; };这个封装的价值在于把PdfError异常转成布尔返回值,调用方不用到处写 try-catch。lastError_保存最后一次错误信息,方便日志记录。注意PdfMemDocument的析构函数会释放所有内部对象,所以封装类里用裸指针加 delete 是安全的,不需要额外引用计数。
5.2 批量处理时的内存与性能注意点
podofo 的PdfMemDocument会把整个 PDF 读进内存,处理几百 MB 的大文件时内存占用会很明显。如果只是做元数据读取或页数统计,可以用PdfParser直接解析文件尾部,避免全量加载。但 0.9.4 的PdfParser接口比较底层,需要自己处理交叉引用表,适合对 PDF 格式比较熟的场景。
批量处理时另一个坑是PdfMemDocument对象复用。同一个对象连续Load多个文件时,前一个文档的资源不一定会完全释放,建议每个文件新建一个PdfMemDocument,处理完立即析构。如果文件数量很大,可以在循环里定期调用malloc_trim或HeapCompact来缓解内存碎片,不过 VS2010 的 CRT 对这块支持有限,最稳妥的还是控制单次处理数量。
5.3 验证模块是否可用的三个检查点
第一个检查点是页数一致性。用 podofo 读出的页数和 Adobe Reader 显示的页数对比,如果差一页以上,说明交叉引用表解析有问题,通常是 PDF 用了增量更新或对象流。
第二个检查点是文本抽取的覆盖率。随便挑一页,把 podofo 抽出的文本和手动复制的文本对比,如果缺失超过 20%,说明字体编码映射不完整,需要针对该字体做特殊处理。
第三个检查点是保存后的文件能否被其他阅读器打开。podofo 的Save默认会重写整个文件结构,如果原 PDF 有数字签名,保存后签名会失效。这个不是 bug,是 PDF 格式的固有特性,做签名验证的场景要特别注意。
5.4 一个我踩过的字符集坑
VS2010 工程默认用 MBCS,而 podofo 内部很多字符串操作假设输入是 UTF-8。有一次我在 MBCS 工程里传入一个含中文的路径,Load返回成功但读出来全是空页。排查了半天才发现是路径在const char*转换时被截断了。后来统一在工程属性里改成 Unicode 字符集,并在传路径前用WideCharToMultiByte转成 UTF-8,问题才消失。这个习惯我一直保留到现在:只要涉及 podofo 的文件路径,一律先转 UTF-8,不依赖工程默认字符集。希望帮到你。
本文还有配套的精品资源,点击获取