简介:面向具备一定C++基础的开发者,这份资源提供可在VS2005环境下直接打开编译的podofo 0.9.7开源PDF读写库完整工程,用于解决PDF解析、生成与文档加密等文件处理需求。压缩包共1382个文件、约42.38MB,以.h头文件和.c源文件为主,辅以cpp实现、vcproj工程文件以及obj、lib、dll等预编译产物;工程内已整合freetype、libjpeg、libpng、libtiff、zlib、openssl、cppunit等依赖库,省去开发时逐个链接静态库的繁琐操作。已有572人学习下载。工程默认关闭PODOFO_HAVE_OPENSSL宏,如需文档加密可自行启用,并补充相应dll与lib;涉及Linux的两个用例处于禁用状态,便于在Windows下稳定构建。下载后即可编译成功,适合研究PDF底层结构、二次封装阅读器或做格式转换的开发者参考使用。
1. 一个不许升级的老工程:VS2005 编 podofo 0.9.7 的第一次翻车
接手一个 C++ 文件解析的老工程,解决方案是 VS2005 建的,界面、报表、打印全挂在里面,升级工具链的申请打上去就被驳回了。这轮要在里面加 PDF 读写,选型绕了一圈落到 podofo 0.9.7——源码干净、读和写都能干、不需要额外运行时。结果第一次编译,报错刷了三屏:nullptr未声明、auto不认、enum class语法错误。这不是装错版本,是 podofo 0.9.7 的代码基线早就跑到 C++17 去了,而 VS2005 停在 C++03。凡是给老系统做 C++ PDF 解析、又要留在 VS 老工具集里做 VS 编程的人,这一关躲不掉,下面是我把这条路走通的全过程。
2. 环境清点:VS2005 加 podofo 0.9.7 先在纸上对齐版本和依赖
动手之前先把三件事写清楚:编译器提供什么能力、poDoFo 要什么、中间差的那部分靠什么补。这一步省下来的时间,后面会以“少刷两屏报错”的形式还给你。我见过太多人直接解压源码就往工程里加,加到一半发现连std::unique_ptr都没有,回头推倒重来。
2.1 编译器能力清单:VC8 到底能给什么
VS2005 对应的是 VC8 工具集,cl.exe版本 14.00.50727,CRT 是 8.0。它能认的语言标准是 C++03,long long、nullptr、auto类型推导、范围 for、enum class、移动语义、lambda、std::unordered_map、std::mutex一律没有。TR1 那一套(std::tr1::shared_ptr)是后面版本才带进来的,别指望。
有一样东西要特别留意:VS2005 的<memory>里还有std::auto_ptr,虽然语义恶心(拷贝即转移所有权),但在没有移动语义的年代,它是唯一能表达“独占所有权”的标准设施。poDoFo 里那些std::unique_ptr回退时,简单地方用裸指针加手动delete,所有权跨函数传递的地方用auto_ptr顶一下,是个能落地的折中。
另一个容易被忽略的差异是wchar_t。VS2005 开始把wchar_t当内置类型,老代码里把它当typedef unsigned short用的写法会撞车。poDoFo 本身对wchar_t依赖不重,但如果你还要跟更老的其他库混编,工程属性里那个“将 wchar_t 视为内置类型”的开关就得统一。
2.2 依赖清单:哪些必须编,哪些可以省
poDoFo 0.9.7 的依赖通过podofo-config.h里一组PODOFO_HAVE_*宏控制。手工编译没有 CMake 帮你探测,这些开关得自己拍板。我的取舍是这样:
| 依赖库 | 是否必需 | 对应宏 | 不编的后果 |
|---|---|---|---|
| zlib | 必需 | 直接链接 | FlateDecode 解不开,绝大多数正常 PDF 解析出来是空页 |
| FreeType | 强烈建议 | PODOFO_HAVE_FREETYPE | 字体嵌入与子集化失效,写出的 PDF 在中文字体上显示异常 |
| libjpeg | 建议 | PODOFO_HAVE_JPEG_LIB | DCTDecode 图像抽不出来,图片页只剩空白 |
| OpenSSL | 可选 | PODOFO_HAVE_OPENSSL | 加密 PDF(AES / RC4)打不开,普通文件无影响 |
| libpng | 不需要 | 无 | poDoFo 不直接吃 PNG,用了只会白编 |
| fontconfig | 不需要 | PODOFO_HAVE_FONTCONFIG | 这是类 Unix 桌面环境的字体查找设施,Win32 上没必要 |
第一轮建议把 OpenSSL 关掉。它的头文件对老编译器的要求比 poDoFo 本身还苛刻,先跑通明文 PDF 的读写,加密需求后面单独加。别一上来全开,报错会混在一起,分不清是谁的锅。
2.3 目录规划:一份源码,两个输出
依赖库和 poDoFo 都按静态库走,运行库统一/MT,把 CRT 静态链进去。这样最终交付给现场的是一个不依赖额外 DLL 的 exe,客户机器上不用装任何运行时,这是我在老项目里踩过坑之后固定下来的做法。
目录我习惯这么铺:
D:\sdk\ ├─ src\ 原始压缩包解压位置,只读不改 │ ├─ zlib-1.2.x\ │ ├─ freetype-2.x\ │ ├─ jpeg-9x\ │ └─ podofo-0.9.7\ ├─ include\ 所有对外头文件,编译时只需这一个 -I │ ├─ zlib.h zconf.h │ ├─ ft2build.h freetype\ │ └─ jpeglib.h jconfig.h jmorecfg.h ├─ lib\ x86 与 x64 分开放,别混 │ ├─ x86\ │ └─ x64\ └─ build\ 中间产物,随时可删include这一层是刻意做平的。poDoFo 的源码里引用 freetype 的写法五花八门,有的地方写#include <ft2build.h>,有的地方走FT_FREETYPE_H宏,把include目录直接指到ft2build.h所在的层级,能省掉一批包含路径问题。x86 和 x64 一定分目录,混在一起迟早出现“链接器挑错架构”的玄学报错。
3. 依赖先编:zlib、FreeType、libjpeg 的静态库怎么在 VS2005 下产出
顺序不能乱:zlib 最先,FreeType 和 libjpeg 次之,poDoFo 最后。原因很直白——前三个是 poDoFo 的输入,它们没产出,poDoFo 编不过。每一个都要在 VS2005 自己的命令行环境里做,别用系统里另一个版本的nmake,混用编译器版本后期排查成本极高。
3.1 zlib:先编出唯一一个必选依赖
打开 VS2005 命令提示符(开始菜单里有那一项,它会自动调vcvars32.bat把环境变量配好),然后:
cd /d D:\sdk\src\zlib-1.2.x nmake -f win32\Makefile.msc zlib.lib copy zlib.lib D:\sdk\lib\x86\ copy zlib.h D:\sdk\include\ copy zconf.h D:\sdk\include\-f win32\Makefile.msc指定的是 zlib 自带的 Win32 构建脚本,zlib.lib是目标名,静态库形式。执行完你会得到一个zlib.lib和两个头文件。zconf.h必须一起拷走,它是zlib.h依赖的配置头,只拷一个的话编译期一定报 C1083。
如果nmake报fatal error U1077: "cl.exe" : 返回代码 0x2,八成是环境变量没生效,关掉窗口重新从 VS2005 命令提示符进。如果报的是zconf.h里一堆重复定义,说明系统 PATH 上有另一个版本的 zlib 头文件抢先被找到了,把include路径的优先级调到最前,或者干脆用/I显式指定。
3.2 FreeType:挑一个还带 vc2005 工程目录的版本
FreeType 老一点的版本里,builds\win32\vc2005\freetype.sln是官方提供的,能直接用 VS2005 打开编译。这省掉了手搓工程的全部麻烦。编译前把解决方案配置切成Release Multithreaded,这个配置对应/MT,跟我们的运行库约定一致;切成Release那个单线程配置会得到一个使用/ML的库,跟/MT链接时LNK2005能让你怀疑人生。
手上的版本如果已经没有这个目录了,就手工建一个 Win32 静态库工程,把这些源目录里的.c全部加进去:
src\base src\sfnt src\truetype src\cff src\type1 src\psaux src\psnames src\pshinter src\raster src\smooth src\autofit src\gzip预处理器里必须定义FT2_BUILD_LIBRARY和WIN32,附加包含目录加上include以及src下各子目录(FreeType 内部头文件是相对路径互相引用的,只加include不够)。FT2_BUILD_LIBRARY的作用是把freetype.h里的导出宏从dllimport切成dllexport/空,不加这个宏,编出来的库符号是对外的 dllimport,链接阶段会集体找不到。
输出后把include整个目录拷到D:\sdk\include\freetype,把ft2build.h拷到D:\sdk\include\根上。这一步的位置关系不能错,FreeType 的头文件是靠#include <freetype/config/ftheader.h>这种相对结构定位的。
3.3 libjpeg:先改配置头,再谈编译
libjpeg 最容易翻车的不是编译,是它要求你手动把jconfig.vc复制成jconfig.h:
cd /d D:\sdk\src\jpeg-9x copy jconfig.vc jconfig.h nmake -f makefile.vc libjpeg.lib copy libjpeg.lib D:\sdk\lib\x86\ copy jpeglib.h jconfig.h jmorecfg.h D:\sdk\include\忘了复制那一步的典型现象是:fatal error C1083: 无法打开包含文件:"jconfig.h",或者更隐蔽一点,某些头文件里boolean、INT32类型没定义,报一堆语法错误。jconfig.h就是告诉 libjpeg“你面对的是哪个编译器、有哪些类型可用”的那张说明书,必须有。
nmake -f makefile.vc出来的是libjpeg.lib,三份头文件一起拷走。jmorecfg.h常被漏掉,而jpeglib.h里第一行就包含它。
三个依赖都产出之后,D:\sdk\include下应该同时能看到zlib.h、ft2build.h、jpeglib.h,D:\sdk\lib\x86下有三个.lib。少一个都别急着往下走,poDoFo 阶段会把依赖缺失伪装成各种奇怪的语法错误,排查成本翻几倍。
提示:三个依赖的编译配置(Debug / Release、/MT / /MD、x86 / x64)必须跟最终 poDoFo 工程完全一致。静态库混用运行库导致的链接错误,报错信息通常指向
_malloc、_free、_CrtDbgReport这类底层符号,跟你的业务代码毫无关系,新手很容易往代码上找原因。
4. 源码回退:把 podofo 0.9.7 的 C++17 写法拉回 VS2005 认的 C++03
这一章是整个安装过程里最耗时间的一段,也是最没法跳过的。poDoFo 0.9.7 的构建脚本要求 C++17 标准,VS2005 给不了,所以要么换更老的分支(API 差异大,迁移成本转移到业务代码上),要么把 0.9.7 的源码回退。我选后者:业务代码不用动,改动集中在库内部,而且回退的规则是可枚举的。
4.1 先手写 podofo-config.h,把 PODOFO_HAVE_* 定死
正常流程里这个文件由 CMake 从podofo-config.h.cmake生成,手工编译没有这一步,得自己写一份。放在D:\sdk\src\podofo-0.9.7\include\podofo\podofo-config.h:
/* VS2005 手工版 podofo-config.h —— 由构建脚本生成的那份替代品 */ #ifndef PODOFO_CONFIG_H #define PODOFO_CONFIG_H #define PODOFO_VERSION_MAJOR 0 #define PODOFO_VERSION_MINOR 9 #define PODOFO_VERSION_PATCH 7 #define PODOFO_VERSION_STRING "0.9.7" /* 依赖开关:编了哪个就开哪个,没编的一律注释掉,不要留空定义 */ #define PODOFO_HAVE_ZLIB #define PODOFO_HAVE_FREETYPE #define PODOFO_HAVE_JPEG_LIB /* #define PODOFO_HAVE_OPENSSL */ /* 第一轮关掉,少一大票头文件依赖 */ /* 平台与导出方式:静态库必须走 PODOFO_STATIC 这条分支 */ #if defined(PODOFO_STATIC) # define PODOFO_API #elif defined(PODOFO_EXPORTS) # define PODOFO_API __declspec(dllexport) #else # define PODOFO_API __declspec(dllimport) #endif #endif /* PODOFO_CONFIG_H */这段的关键在PODOFO_API的三分支。CMake 生成的原版会按构建类型自动填,手写版必须自己兜住:编静态库时如果把PODOFO_STATIC忘了,PODOFO_API会展开成__declspec(dllimport),然后你会在链接阶段看到一整屏LNK2019: 无法解析的外部符号 __imp_?...。看到__imp_前缀就说明是这个问题,不用往别处想。
PODOFO_HAVE_OPENSSL注释掉之后,poDoFo 里跟加密相关的实现会被条件编译排除,头文件依赖也跟着断掉。这是减少第一轮报错量最有效的一刀。
4.2 机械替换与手工修改的分界线
语法回退分两类。第一类能靠脚本批量做:关键字就是关键字,上下文无关。第二类必须逐处看:auto要推导出具体类型、范围 for 要改写成下标循环、lambda 要拆成仿函数或静态函数,这些正则表达式判断不了。
能批量做的那一类,我写了个脚本跑第一遍,跑完一定diff一遍再提交:
# revert_cxx.py —— 只做上下文无关的机械替换,跑完必须人工 diff import pathlib, re RULES = [ (r"\bnullptr\b", "NULL"), # C++11 空指针字面量 -> C++03 的 NULL (r"\boverride\b", ""), # 继承修饰符,直接删 (r"\bfinal\b", ""), # 同上,注意别误伤变量名,跑完 diff (r"\blong long\b", "__int64"), # VC8 只认 __int64 (r"\bstd::to_string\b", "podofo_ts"), # C++11 才有,换成自己写的辅助函数 ] root = pathlib.Path("src") for p in root.rglob("*"): if p.suffix.lower() not in (".h", ".cpp", ".cxx"): continue s = p.read_text(encoding="utf-8", errors="ignore") o = s for pat, rep in RULES: s = re.sub(pat, rep, s) if s != o: p.with_suffix(p.suffix + ".bak").write_text(o, encoding="utf-8") # 留后悔药 p.write_text(s, encoding="utf-8") print("patched", p)p.with_suffix(p.suffix + ".bak")这行是留后悔药,替换错了还能从备份捞回来。errors="ignore"是因为 poDoFo 里有几个源文件带非 UTF-8 字符,强行按 UTF-8 读会抛异常中断整个脚本。\b词边界不能省,否则nullptr会被误匹配到标识符内部。
final这条规则有风险,源码里如果有叫final的变量或参数,删掉就成了语法错误。所以脚本只管“提出候选”,真正提交前一定过一遍 diff。我一般会把final这条规则干脆注释掉,改成手工 grep 确认后逐个删。
不能机械做的部分,对照表贴在这儿,遇到一条改一条:
| 0.9.7 里的写法 | VS2005 不认的原因 | 回退写法 |
|---|---|---|
std::unique_ptr<T> | C++11 独占所有权 | T*手动delete,跨函数传递时用std::auto_ptr<T> |
auto it = v.begin() | 类型推导 | 显式写std::vector<PdfObject*>::iterator |
for (auto& x : v) | 范围 for | 下标循环或迭代器循环 |
enum class E { A }; | 强类型枚举 | enum E { A };,使用处补作用域前缀 |
= default/= delete | 显式默认/删除 | 手写空函数体 / 声明为 private 且不实现 |
std::unordered_map | 哈希容器 | std::map,接口基本兼容 |
std::mutex+lock_guard | 线程库 | 单线程模式下直接去掉,多线程用CRITICAL_SECTION |
static_assert(a, "msg") | 静态断言 | typedef char check[(a) ? 1 : -1]; |
v.emplace_back(args) | 就地构造 | v.push_back(T(args)) |
std::vector<std::vector<int>> | 连续两个> | 写成> >,中间必须有空格 |
最后那条看着傻,但它是 C++03 词法分析的硬规定,>>会被当成右移运算符。poDoFo 里模板嵌套不深,撞上的地方不多,撞上一次报错信息极其迷惑:error C2143: 语法错误 : 缺少“;”(在“>>”的前面),指的位置还经常离真正出错行好几行。
4.3 补上 Win32 缺的类型和 POSIX 函数
poDoFo 的代码里散落着ssize_t、snprintf、strcasecmp这些 POSIX 世界的东西。VS2005 的 CRT 一个都不给。对策是写一个兼容头,然后用/FI(强制包含)在编译每个源文件时自动带上:
/* podofo-compat.h —— VS2005 缺失的类型与函数,统一收口 */ #ifndef PODOFO_COMPAT_H #define PODOFO_COMPAT_H #if defined(_MSC_VER) && _MSC_VER < 1600 /* 1600 = VS2010,之前都没有 stdint.h */ #include <windows.h> #include <cstdio> #include <cstring> typedef __int64 podofo_int64_t; typedef unsigned __int64 podofo_uint64_t; typedef int ssize_t; /* Win32 下未定义,poDoFo 用它做长度返回值 */ #define snprintf _snprintf /* 注意:_snprintf 不保证补 '\0' */ #define vsnprintf _vsnprintf #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif /* _MSC_VER < 1600 */ #endif /* PODOFO_COMPAT_H */_snprintf那个坑值得单独说:缓冲不够时它不写结尾的\0,返回值还是负数。poDoFo 里有几处用返回值算长度的代码,直接换过去会读到越界内存,运气好是乱码,运气差就是崩溃。稳妥做法是包一层自己的函数,截断后强制buf[n - 1] = '\0'。
ssize_t的 typedef 要留意顺序。如果先包含了 winsock2 的头,某些 SDK 版本会定义同名类型,撞上就是error C2371: 重定义。把兼容头放在所有系统头之前包含,或者干脆用/FI"podofo-compat.h"强制它永远排第一。
4.4 生成 vs2005 工程:先试探 CMake,不行就手工 vcproj
第一步先试探,看看手上的 CMake 还认不认 2005 生成器:
cd /d D:\sdk\build cmake -G "Visual Studio 8 2005" ^ -DPODOFO_BUILD_LIB_ONLY=ON ^ -DPODOFO_BUILD_STATIC=ON ^ -DPODOFO_BUILD_SHARED=OFF ^ -DCMAKE_INCLUDE_PATH=D:\sdk\include ^ -DCMAKE_LIBRARY_PATH=D:\sdk\lib\x86 ^ ..\src\podofo-0.9.7PODOFO_BUILD_LIB_ONLY=ON这条最有用,它只生成库、不生成tools下那堆命令行小工具,省掉一批额外依赖。CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH是给FindXXX模块的搜索提示,让它去我们手工编出来的目录里找静态库,而不是去系统路径乱找。
如果这一步报Generator ... not found,或者配置阶段直接提示 CMake 版本要求不满足,说明手上的 CMake 已经把 2005 生成器删了。别在这上面耗,直接手工建工程,反而更快。手工vcproj的要点就这么几项:
| 配置项 | 值 | 为什么 |
|---|---|---|
| 附加包含目录 | D:\sdk\include;D:\sdk\src\podofo-0.9.7\include | 依赖头和 poDoFo 自己的头 |
| 预处理器定义 | WIN32;_LIB;PODOFO_STATIC;PODOFO_HAVE_ZLIB;PODOFO_HAVE_FREETYPE;PODOFO_HAVE_JPEG_LIB;_CRT_SECURE_NO_DEPRECATE | 缺PODOFO_STATIC必出__imp_链接错误 |
| 强制包含文件 | podofo-compat.h | 每个 .cpp 都自动补上 POSIX 类型 |
| 运行时库 | 多线程(/MT) | 与三个依赖库保持一致 |
| 禁用特定警告 | 4996 | 老的 CRT 函数被标不安全,刷屏掩盖真错误 |
工程里要加的源文件覆盖src\podofo\base、src\podofo\main、src\podofo\auxiliary三个目录。tools和test目录一律不加,它们依赖额外的命令行解析代码,会把问题复杂化。
5. 避坑排查:podofo 0.9.7 在 VS2005 上最常翻车的五个地方
报错刷屏的时候最忌讳从上往下一条条改,因为第一个错误的成因往往会派生出后面几十条。我的一般做法是先按“编译期 / 链接与运行期”分开看,编译期的错从第一条开始改,链接期的错先看符号前缀再定位。
5.1 编译期:报错刷屏时先改第一条
现象:error C2065: “nullptr”: 未声明的标识符,一次几十条,分布在不同文件。原因:回退脚本只扫了src目录,include下的公共头没扫到,而那些头被到处包含,一处没改就是成片的报错。解决:先用findstr /s /n "nullptr" *.h *.cpp在源码根目录全量扫一遍,确认覆盖范围,再重跑脚本。同类还有auto,这个不能靠脚本,得grep -rn "auto "拉出清单人工过。
现象:fatal error C1083: 无法打开包含文件:“podofo/podofo-config.h”。原因:工程里的包含路径还指向build目录,那是 CMake 生成配置头的地方,手工编译根本没生成。解决:把手写的podofo-config.h放进src\podofo-0.9.7\include\podofo\下,同时把那个目录加进附加包含目录。别把原版的 `podofo-config.h.c
本文还有配套的精品资源,点击获取