VS2005编译podofo 0.9.7:依赖链、补丁与链接避坑
简介:podofo 0.9.7 的 VS2005 编译版本,面向具备一定 C++ 基础、需要在 Windows 下进行 PDF 读写与解析的开发者,省去自行整合多套第三方库的麻烦。压缩包共 1382 个文件、约 42.38MB,以 h 头文件、c 源文件、obj 中间文件、cpp 实现和 vcproj 工程文件为主,并含 mk 构建脚本、html 文档及少量 dll/lib,完整保留编译产物与示例工程。工程已将 freetype、libjpeg、libpng、libtiff、zlib、openssl、lua、cppunit 等依赖整理为 lib,下载后可直接用 VS 打开编译,降低逐个链接静态库的配置成本;文档加密相关的 OpenSSL 宏、DLL 与 lib 链接方式也留有说明,便于按需启用或裁剪。两个依赖 Linux 库的用例处于禁用状态,可减少无关报错对主线调试的干扰。已有 573 人学习下载,适合研究 PDF 对象结构、内容流解析与加密文档处理的开发者参考。
1. 老编译器配老库:VS2005 编 podofo 0.9.7 这件事到底卡在哪
前阵子要在一台只能装 VS2005 的开发机上做 PDF 读写,绕不开的一个选项就是 podofo。它是 C++ 里少见的、不拖 Qt、不拖 Java、不拖 Poppler 那一大串依赖的 PDF 解析与生成库,0.9.7 这一版正好处在 API 相对稳定、功能又够用的位置。真正让人头疼的地方不在 podofo 本身,而在 VC8 这个编译器:它没有stdint.h、没有snprintf、新版本 CMake 也不再为它生成工程文件。做 VS 编程的人都知道,编译不过反而是好事,最怕的是编译过了、跑起来才崩。下面按依赖链、补丁层、链接期、运行期四段往下推,适合手上维护老 C++ 工程、又必须离线处理 PDF 的从业者照着走一遍。
2. 把依赖先钉死:VC8 能链接的 zlib / libjpeg / freetype 怎么编
podofo 0.9.7 不是那种改两行就能编的独立库,它对第三方依赖的版本和编译参数相当敏感。VC8 时代的东西放到今天,依赖的源码版本、CMake 版本、运行时库设置这三样必须一次性对齐,任何一项错位都会在最后的链接阶段以 LNK2005 或者运行期"应用程序配置不正确"的形式报复你。这一章先把依赖链钉死,后面 podofo 本体才有得谈。
2.1 三个硬依赖和三个可选依赖,先做减法
podofo 0.9.7 的CMakeLists.txt里挂了一堆PODOFO_HAVE_*开关,但真正绕不过去的是 zlib 和 freetype:zlib 负责 FlateDecode 解压,PDF 里绝大多数内容流都靠它;freetype 负责字体度量与嵌入字体解析,没有它连文本宽度都算不出来。libjpeg 处理 DCTDecode,也就是常见的 JPEG 图像对象,虽然理论上能关,但关掉之后带图的 PDF 基本没法看,实操里建议直接打开。libtiff、libpng、openssl、fontconfig 这四个属于可选,而且每个都会额外拉一串依赖进来,老工程上能关就关。先做减法的好处是,编译失败时排查面从十几个库压缩到三个库,这在 VC8 上是实打实的时间成本。
顺序上我一般按 zlib → libjpeg → freetype 走。freetype 自己可以选依赖 zlib,所以放最后编,编的时候把FT_WITH_ZLIB打开,让它复用同一份 zlib 头文件和 lib,避免两份 zlib 打进一个进程。libjpeg 用 IJG 的原版jpeg-9a更省事,它没有 CMakeLists,靠jconfig.vc加一份手写工程反而比 CMake 干净,libjpeg-turbo 那条路需要 nasm 汇编器,在 VS2005 环境里属于自找麻烦。
2.2 生成 VS2005 工程:CMake 版本卡在 2.8.12 附近
新版 CMake 早就不输出 VS2005 工程了,手上得常备一份 2.8.12 或 3.0 附近的版本,装在独立目录里,别和系统 PATH 里的新版打架。生成器名字是固定的Visual Studio 8 2005,注意不是VS2005也不是Visual Studio 2005,写错会直接报 unknown generator。zlib 和 freetype 都自带 CMakeLists,可以直接走这条路。
:: 用 2.8.12 的 cmake.exe,别用 PATH 里的新版 set CMAKE_OLD=C:\tools\cmake-2.8.12\bin\cmake.exe set ROOT=D:\build\pdfdep mkdir %ROOT%\zlib && cd %ROOT%\zlib :: 生成 VS2005 解决方案,安装前缀指向统一目录 %CMAKE_OLD% -G "Visual Studio 8 2005" ^ -DCMAKE_INSTALL_PREFIX=%ROOT%\install ^ -DCMAKE_C_FLAGS="/D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE" ^ %ROOT%\src\zlib-1.2.8这段命令的关键在三处:生成器字符串必须一字不差;CMAKE_INSTALL_PREFIX把三个依赖的产物收拢到同一个install目录,后面配 podofo 时只需要写一条 include 路径;CMAKE_C_FLAGS里那两个宏是 VS2005 时代对付strcpy、fopen这类 CRT 安全警告的标准做法,VS2005 认_CRT_SECURE_NO_DEPRECATE,_CRT_SECURE_NO_WARNINGS是 VS2008 才引入的,两个都加上最省心。生成完打开.sln,只编 Release 和 Debug 两个配置,别用 Debug 版去链 Release 版的 podofo。
2.3 三个依赖的编译参数必须写成同一套
依赖编译最容易翻车的不是编不过,而是编过了之后 CRT 不一致。zlib 默认/MD、freetype 默认/MD、libjpeg 的手写工程默认/MT,三者混在一起,podofo 链接时就会看到一堆_malloc、_free的 LNK2005。我一般把三个依赖的运行时库统一成/MD(Release)和/MDd(Debug),理由是最后交付的 podofo 如果做成 DLL,多份 CRT 一定会出问题;如果做成静态库,/MD也比/MT更容易和第三方组件共存。
freetype 的 CMake 参数要显式关掉几个开关,不然它会去找 harfbuzz、bzip2、png,找到一个就多一份链接依赖:
%CMAKE_OLD% -G "Visual Studio 8 2005" ^ -DCMAKE_INSTALL_PREFIX=%ROOT%\install ^ -DFT_WITH_ZLIB=ON ^ -DFT_WITH_PNG=OFF ^ -DFT_WITH_BZIP2=OFF ^ -DFT_WITH_HARFBUZZ=OFF ^ -DCMAKE_DEBUG_POSTFIX=d ^ %ROOT%\src\freetype-2.6.5CMAKE_DEBUG_POSTFIX=d这个参数值得单独说:老工程的习惯是 Debug 版加d后缀,这样 Debug 和 Release 的 lib 能在同一个目录里共存,配 podofo 时用debug和optimized两个关键字分别指定,链接器就不会拿错。FT_WITH_ZLIB=ON之后 freetype 的头文件里会引用zlib.h,所以 zlib 的 include 路径必须在 freetype 的包含路径前面,否则 freetype 编得过、podofo 编不过。
libjpeg 走的是手写路线。把jconfig.vc复制成jconfig.h,新建一个空的 Win32 静态库工程,把j*.c全加进去,预处理器定义加_CRT_SECURE_NO_DEPRECATE,运行时库选/MD。jconfig.vc里已经定义了boolean为unsigned char并#define HAVE_BOOLEAN,这一步很关键,Windows SDK 的rpcndr.h里也有一份boolean定义,不提前用HAVE_BOOLEAN挡住,编译期就会撞名。
2.4 产物扁平化:一条 include、一条 lib
三个依赖编完,install目录里会是一堆include/zlib.h、include/freetype2/ft2build.h、lib/zlib.lib这样的结构,头文件层级深浅不一。freetype 尤其容易踩坑:它的入口头文件是ft2build.h,但这个文件在include/freetype2/下面,而ft2build.h里面又用#include <freetype/config/ftheader.h>去引别的头。所以 podofo 的包含路径必须同时写install/include和install/include/freetype2,只写一条必然报ft2build.h找不到。我把这一步做成了个固定的目录约定,后面所有工程都按这个约定配:
| 路径 | 内容 | 用途 |
|---|---|---|
install/include | zlib.h、jpeglib.h、jconfig.h | 直接包含 |
install/include/freetype2 | ft2build.h、freetype/ | 二级包含根 |
install/lib/release | zlib.lib、jpeg.lib、freetype.lib | Release 链接 |
install/lib/debug | zlibd.lib、jpegd.lib、freetyped.lib | Debug 链接 |
整理成这张表之后,podofo 的 CMake 配置参数就只剩寥寥几行,出问题的时候也好定位到底是哪个库的路径错了。这一步多花十分钟,后面能省两小时。
3. 让 podofo 0.9.7 通过 VC8 编译器:补头、换函数、关开关
依赖就位之后,podofo 本体才是真正的主战场。0.9.7 的源码主要面向 VS2008 及以上和 GCC 写的,里面有不少 C99 和后来才标准化的东西,VC8 一个都不给。这一章的处理思路是"能补的补、能改的改、能关的关",尽量不动 vendored 源码,把改动集中在几个补丁头文件和配置宏上,方便以后复现和回滚。
3.1 补齐 stdint.h 和 inttypes.h
VC8 没有stdint.h,inttypes.h更是要到 VS2013 才有。podofo 里pdf_uint8、pdf_uint32这类类型定义和不少#include <stdint.h>都会在这里断掉。最省事的做法是自己写一份放到install/include下,让它优先于任何系统路径。VS2005 的编译器其实支持__int64,也支持long long作为扩展,用__int64更保险,因为后面配printf的时候%I64d直接对得上。
/* stdint.h —— 放在依赖 include 目录里,供 VC8 使用 */ #ifndef PODofo_COMPAT_STDINT_H #define PODofo_COMPAT_STDINT_H typedef signed char int8_t; typedef short int16_t; typedef int int32_t; typedef __int64 int64_t; /* 用 __int64 而不是 long long,配 %I64d */ typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned __int64 uint64_t; /* 指针宽度类型,PDF 解析里偶尔会用到 */ typedef int intptr_t; typedef unsigned int uintptr_t; #define INT32_MAX 2147483647 #define UINT32_MAX 4294967295U #define INT64_MAX 9223372036854775807i64 #endif这份补丁的要点在于不能重复定义:如果某个 SDK 头文件已经定义了int8_t,重定义会报 C2371。稳妥的做法是在stdint.h外面加一层#ifndef _STDINT_H之类的守卫,或者在工程预处理器里加一个PODOFO_COMPAT_STDINT开关,只有在 VC8 环境下才引入这个文件。inttypes.h更简单,直接#include <stdint.h>加几个空的PRId32宏即可,podofo 里真正用到PRIu64的地方不多。
3.2 snprintf 与格式化占位符
VC8 的 CRT 里没有snprintf,只有_snprintf。很多人图省事直接#define snprintf _snprintf,能编过,但埋了个隐患:_snprintf在缓冲区不够时不会在末尾写\0,返回值也不同(snprintf返回"本应写入的长度",_snprintf返回负数)。podofo 里生成 PDF 交叉引用表和对象偏移量时大量用到snprintf,一旦截断没终止,写出去的 PDF 就是坏文件,而且这种坏文件用阅读器打开时只是偶尔报错,极难复现。
/* podofo_compat.h —— 在 podofo 的预编译头里最先包含 */ #ifndef PODofo_COMPAT_CRT_H #define PODofo_COMPAT_CRT_H #include <cstdio> #include <cstdarg> #include <cstddef> /* VC8 没有 snprintf,用 _vsnprintf 包一层,保证终止且返回值语义正确 */ static int podofo_snprintf(char* buf, size_t n, const char* fmt, ...) { va_list ap; int need; if (n == 0) return 0; va_start(ap, fmt); need = _vsnprintf(buf, n, fmt, ap); /* 截断时返回 -1 */ va_end(ap); buf[n - 1] = '\0'; /* 强制终止,兜住 _vsnprintf 的坑 */ return need; } #define snprintf podofo_snprintf /* VC8 不支持 C99 的 %zu,size_t 要走 %Iu */ #ifdef _WIN64 # define PODOFO_SIZET_FMT "%I64u" #else # define PODOFO_SIZET_FMT "%Iu" #endif #endif这里buf[n-1] = '\0'那行是血泪经验:_vsnprintf在恰好填满缓冲区和不填满两种情况下行为不一致,官方文档里写得很含糊,实际测试下来只有强制结尾才能保证字符串安全。返回need为负数时上层代码通常当错误处理,不会继续用这个缓冲区,所以不影响正确性。另外%zu是 C99 的东西,podofo 里如果有snprintf(..., "%zu", size)的写法,在 VC8 里会输出字面的zu,排查起来非常玄学,把这类格式化串统一换成PODOFO_SIZET_FMT宏最稳。
3.3 podofo_config.h 里必须手改的几行
podofo 用 CMake 的configure_file生成podofo/base/podofo_config.h,但自动探测在 Windows 上经常误判,尤其是unistd.h。VC8 环境里绝对没有unistd.h,但 CMake 的CheckIncludeFile可能因为某些路径污染而返回真,结果编到一半报cannot open include file: 'unistd.h'。我的做法是 CMake 生成完之后手工复核一遍,下面这几个宏必须落到位:
/* podofo/base/podofo_config.h —— 手工复核后的关键项 */ #define PODOFO_HAVE_UNISTD_H 0 /* Windows 上必须为 0,否则引 <unistd.h> */ #define PODOFO_HAVE_OPENSSL 0 /* 老版本 openssl 在 VC8 上编不过,先关 */ #define PODOFO_HAVE_FONTCONFIG 0 /* fontconfig 是 Linux 侧的东西 */ #define PODOFO_HAVE_TIFF_LIB 0 /* 不开 tiff,少一个依赖 */ #define PODOFO_HAVE_PNG_LIB 0 /* 不开 png,libpng 1.6 需要 pnglibconf.h */ #define PODOFO_HAVE_JPEG_LIB 1 /* DCTDecode 靠它 */PODOFO_HAVE_PNG_LIB单独说一下:libpng 1.6 之后引入了pnglibconf.h,这个文件在老版本里是构建时生成的,手动编的时候很容易漏,表现是png.h里#include "pnglibconf.h"直接失败。podofo 对 PNG 图像的支持本来就是可选的,关掉它不影响绝大多数 PDF 的解析,能省一整轮折腾。PODOFO_HAVE_OPENSSL关掉之后加密 PDF 就没法解密了,但 0.9.7 时代它依赖的 openssl 接口在 VC8 上编起来极其痛苦,如果不是必须处理加密文档,直接关。
除了 config 头,工程级的预处理器定义也要统一:_CRT_SECURE_NO_DEPRECATE、_CRT_NONSTDC_NO_DEPRECATE、_SCL_SECURE_NO_DEPRECATE三个都加上,WIN32、_LIB(静态库时)也要有。字符集一项保持"未设置"即多字节,不要切 Unicode,podofo 内部大量用char*和std::string,切了 Unicode 反而会引入一堆LPCWSTR转换的麻烦。
3.4 分批编译:先 base 后 main
podofo 的源码目录大致分base、main、doc、tools几块,base里是PdfString、PdfName、PdfVariant这类基础设施,main里才是PdfDocument、PdfPage、PdfImage这些面向用户的类。一次性全编的话,几百个错误刷屏,根本看不出根因。我一般先在解决方案里把main、doc、tools三个工程全部卸载,只留base,把它编到零错误零警告,再逐个加回来。
base编过是个很强的信号:说明stdint.h、snprintf、podofo_config.h这三处补丁都生效了,因为base里同时用到了整数类型、字符串格式化和配置宏。main加回来之后新出现的错误基本集中在两处,一是 C++ 语法(比如>>嵌套模板在 VC8 里需要加空格),二是第三方头文件的包含顺序(比如jpeglib.h必须在podofo_config.h之后包含,否则HAVE_BOOLEAN没定义就撞名)。tools目录里是命令行小工具,依赖base和main,编不编都行,交付的是库本身。
注意:分批编译时不要在解决方案级别点"生成解决方案",那样会把卸载的工程一起跳过的同时也跳过依赖关系检查,容易编出一个看起来成功、实际缺符号的 lib。正确的是右键单个工程点"仅生成"。
4. 链接期与运行期:编译通过之后的三个关卡
编译通过那一刻是最容易松懈的时候,但 VC8 上的老库恰恰是链接期和运行期问题最多。静态库、动态库、CRT 版本、清单文件这四样东西任意两个不匹配,表现都是"编译成功、程序起不来"。这一章按链接错误、最小验证、异常处理、编码边界四步走,把黑匣子拆开。
4.1 LNK2005 / LNK2019 的定位顺序
LNK2005 的意思是同一个符号被定义了两次,在 VC8 配合老库的场景里九成是 CRT 不一致。_malloc、_free、_operator new、_CrtDbgReport这几个符号反复出现,就去查每个参与链接的 lib 到底用的是/MT还是/MD。VS2005 没法从.lib文件名看出 CRT 版本,得用dumpbin看它引用了哪个 CRT。
:: 看一个 lib 到底依赖哪种 CRT dumpbin /directives zlib.lib | findstr /i "DEFAULTLIB" :: /MD 输出里会有 MSVCRT :: /MT 输出里会有 LIBCMT :: 混用会同时出现 MSVCRT 和 LIBCMT,这就是 LNK2005 的根因 :: 看 podofo.lib 里某个符号到底有没有被导出 dumpbin /symbols podofo.lib | findstr "PdfMemDocument"第一条命令的输出决定了要不要回头重编依赖。如果zlib.lib里同时出现MSVCRT和LIBCMT,说明编 zlib 的时候两个配置的 obj 混进了一个 lib,只能删掉重来。第二条命令用来对付 LNK2019(符号未解析),典型场景是 podofo 编成了 DLL 但工程里没有链接podofo.lib对应的导入库,或者反过来,podofo 编成了静态库但工程里定义了PODOFO_IMPORT。静态库要用PODOFO_STATIC,动态库用PODOFO_IMPORT,这两个宏定义错了,符号修饰名就对不上。
还有一种隐蔽的 LNK2019 是 freetype 造成的,FT_Init_FreeType找不到时,先确认freetype.lib真的被加进了链接器输入,再确认 freetype 的FT2_BUILD_LIBRARY宏在你引用它的工程里没有被误定义。这个宏是 freetype 自己编 lib 时用的,外部工程定义了它会把__declspec(dllimport)变成dllexport,符号方向反了自然找不到。
4.2 最小验证:打开 PDF、读页数、写一条线
链接通过之后,第一件事不是跑业务代码,而是写一个三十行的最小程序把"加载—读结构—写回"这条链路走通。这个 Demo 的验证价值很高:它能同时验证 zlib(内容流解压)、freetype(字体初始化)、CRT(内存分配)三条链路是否真的通了。
#include <podofo/podofo.h> #include <iostream> using namespace PoDoFo; int main(int argc, char** argv) { if (argc < 2) { std::cerr << "usage: demo input.pdf\n"; return 1; } try { PdfMemDocument doc; doc.Load(argv[1]); // 整份读进内存,便于改结构 std::cout << "pages = " << doc.GetPageCount() << std::endl; PdfPage* page = doc.GetPage(0); // 0 基下标 PdfRect size = page->GetMediaBox(); // 单位是 PDF 点,1/72 英寸 std::cout << size.GetWidth() << " x " << size.GetHeight() << std::endl; // 往首页画一条红线,验证内容流写入 PdfPainter painter; painter.SetPage(page); painter.SetStrokeColor(1.0, 0.0, 0.0); painter.DrawLine(0, 0, size.GetWidth(), size.GetHeight()); painter.FinishPage(); doc.Save("out.pdf"); // 带增量标记,不破坏原对象表 std::cout << "ok" << std::endl; } catch (PdfError& e) { e.PrintErrorMsg(); // 打印错误码与描述 return e.GetCode(); } return 0; }几个参数要说明清楚。doc.Load()和doc.Save()配的是PdfMemDocument,适合要改结构的场景;只读的话用PdfStreamedDocument更省内存,但老版本在 Windows 上的流式读取对文件的独占锁要求更严,读的过程中不能有其他句柄。GetMediaBox()返回的是 PDF 点,A4 是 595×842,这个值可以直接判断页面尺寸读取有没有出错。PdfPainter必须显式FinishPage(),不调的话内容流不会真正写回页面对象,保存出来的 PDF 看着有页、但画笔的内容全丢,这种问题不报错,只能靠对比输出文件大小发现。Save()默认走增量更新,会保留原对象表,对已经加密或者有签名的文档更友好。
4.3 异常处理:PdfError 不能只 catch 不打印
podofo 的错误处理走的是异常,所有解析失败都抛PdfError,错误码定义在PdfError.h里。VC8 上有个额外的坑:如果异常跨 DLL 边界抛出,而 DLL 和调用方用的 CRT 不一致,catch可能抓不到,程序直接终止。这就是为什么前面死磕/MD统一——只要 CRT 一致,跨模块异常传播基本可靠。
PdfError::PrintErrorMsg()输出的是错误码加上一段描述,比如Error: 13对应的是文档结构损坏。实际排查时我一般不用它,而是自己把e.GetCode()打到日志里,因为错误码是稳定的,描述文本会随版本变化。常见的几个码值得记住:PdfError::ePdfError_InvalidXRef(交叉引用表坏了)、ePdfError_UnsupportedFilter(不认识的压缩过滤器,通常是关了 zlib 或者遇到新格式)、ePdfError_InvalidHandle(文件不存在或者权限问题)。把catch分支写成返回e.GetCode(),上层再按码分支处理,比打印一堆字符串有用得多。
还有一点容易被忽略:PdfError里有ePdfError_OutOfMemory这种码,VC8 的 CRT 在内存耗尽时行为比新版本更粗暴,new可能直接返回空指针而不抛异常。所以 podofo 的调用点外面,最好再加一层__try/__except兜底,尤其是在批量处理成千上万份 PDF 的服务里,单份文档把进程搞崩是绝对不行的。
4.4 字体与编码:中文 PDF 的真实边界
中文 PDF 在 podofo 0.9.7 上的表现取决于两条链路:解析和生成。解析侧,只要 PDF 里嵌入了字体子集,freetype 能加载就能拿到字形度量,PdfPage::ExtractTextTo能抽出文本,但抽出来的编码是 PDF 内部编码,ToUnicode 映射不完整的文档抽出来就是乱码,这不是 podofo 的问题,换任何库都一样。生成侧更麻烦,往页面写中文必须自己指定字体和编码,PdfFont用PdfEncoding创建,不指定编码默认走 WinAnsi,中文会全部变成问号或者直接抛异常。
实际项目里的做法是,生成中文 PDF 时从系统目录加载一份 CID 字体,用PdfFontCache缓存起来复用,避免每写一段文字就新建一个字体对象。VS2005 环境下还有个wchar_t宽度的问题:VC8 的wchar_t是 2 字节,podofo 内部用的是 UTF-8 的pdf_utf8,从std::wstring转 UTF-8 得自己写转换函数,WideCharToMultiByte加CP_UTF8是最省事的路径,别忘了最后补\0。
提示:处理中文 PDF 时先把
PODOFO_HAVE_FONTCONFIG关掉确认,fontconfig 打开后 podofo 会走一套 Linux 风格的字体查找逻辑,在 Windows 上反而找不到字体,表现是中文全部丢字。
5. 避坑:VS2005 编 podofo 0.9.7 最常见的 5 次翻车
这一章记录的是我在复现过程中真的踩过、而且每次重搭环境都可能再踩一遍的问题,每一条都按"现象 → 原因 → 解决"写,方便对号入座。有些坑看起来是 podofo 的问题,实际根因在 VC8 或者某个依赖上,只盯着 podofo 源码改是改不出来的。
5.1 C4819 警告刷屏,编译到一半中断
现象:编译 podofo 的源文件时,每个.cpp都报 C4819,提示当前代码页不能表示某个字符,紧接着是几个莫名其妙的语法错误,行号指向一段注释。
原因:podofo 源码里有作者写的非 ASCII 注释,文件是 UTF-8 无 BOM 保存的。VC8 在没有任何编码线索时会按系统本地代码页(简体中文环境是 GBK)解析源文件,UTF-8 的多字节序列被拆成两个独立的 GBK 字符,其中一个可能正好等于\或者引号,把注释提前截断,后面的代码就被当成了语句。
解决:把这些源文件批量转成"UTF-8 带 BOM",VS2005 看到 BOM 就知道按 UTF-8 解析。没有/utf-8编译开关可用(那是 VS2015 之后才有的),所以 BOM 是唯一可靠信号。转完之后 C4819 消失,那些"语法错误"也会一起消失。如果不想动源码,另一个办法是把文件转成 GB2312,但源码里如果同时有日文、德文注释就会丢字符,BOM 更稳。
5.2 jpeglib.h 报 boolean 重定义
现象:jmorecfg.h里typedef unsigned char boolean;报 C2371 重定义,前面已经有一个boolean的定义。
原因:Windows SDK 的rpcndr.h里也定义了boolean,而且这个头文件经常通过windows.h被间接引入。libjpeg 官方早就知道这事,在jmorecfg.h里留了HAVE_BOOLEAN的开关,定义了它就跳过自己的typedef。
解决:在工程的预处理器定义里加HAVE_BOOLEAN。注意不是改jconfig.h,因为 podofo 的某些源文件会先包含windows.h再包含jpeglib.h,顺序不受控,只有全局宏能保证在任何包含顺序下都生效。加完之后如果还报boolean相关错误,检查是不是某个第三方头文件里自己写了一份typedef int boolean,那种情况只能改包含顺序。
5.3 编译链接全过,双击 exe 报"应用程序配置不正确"
现象:Debug 版在本机 VS 里能跑,拷到测试机上双击就弹出"由于应用程序的配置不正确,应用程序未能启动",事件查看器里能看到 SideBySide 错误,说找不到某个版本的 CRT 程序集。
原因:VS2005 的/MDd依赖Microsoft.VC80.DebugCRT,这个调试版 CRT 不允许再分发,测试机上没有。而 Release 版虽然依赖Microsoft.VC80.CRT,但如果编译时把清单嵌错了,或者 exe 和 dll 的清单版本号不一致,同样起不来。
解决:交付一律用 Release 版/MD,并且把Microsoft.VC80.CRT作为可再发行组件一起部署,或者干脆改成/MT静态链接 CRT。后者要注意所有依赖库也得是/MT,包括前面的 zlib、freetype、libjpeg,只要有一个是/MD,链接期就会撞 LNK2005。另一种情况是 exe 和 podofo.dll 用了不同的 CRT 版本号(8.0.50608 和 8.0.50727),清单里的assemblyIdentity对不上,也会报同样的错,用mt.exe检查两边的清单版本。
5.4 加载 PDF 抛 PdfError 13,但文件用阅读器能正常打开
现象:同一个 PDF,Acrobat 和浏览器都能打开,podofo 的Load()抛PdfError,错误码 13,描述是交叉引用表损坏。
原因:0.9.7 的 xref 修复逻辑比较保守。很多 PDF 生成器(尤其是某些报表系统)写出来的 xref 偏移量是错的,但有经验的阅读器会自动扫描全文件重建 xref 表,podofo 也支持这个能力,但要显式开启,或者传入一个容错参数。
解决:PdfMemDocument::Load()有个重载接受PdfArchive,或者用LoadFromBuffer之后调SetPdfVersion之类的方式绕不过去,真正有效的是给PdfParser设容错。0.9.7 里可以在 load 前调用doc.GetParser()->SetStrictParsing(false)(具体 API 名以头文件为准),关掉严格模式后它会扫描obj ... endobj重建对象表。如果还不行,就是文件里用了不支持的过滤器,检查ePdfError_UnsupportedFilter对应的对象是不是 LZW 或 JPX 编码,这两种 0.9.7 支持得不好。
5.5 freetype 头文件找得到,链接时 FT_Init_FreeType 找不到
现象:编译期一切正常,#include <ft2build.h>能过,链接期报unresolved external symbol _FT_Init_FreeType。
原因:有两种。一是 freetype 编成了 DLL,但工程里没有定义FT2_BUILD_LIBRARY之外对应的导入宏,freetype 的头文件在没有FT2_BUILD_LIBRARY也没有FT_DLL的情况下会把函数声明成普通外部函数,符号名和 DLL 里的修饰名不一致。二是 freetype 编的是 Release 版,工程链接路径指向了 Debug 目录,文件名不匹配但链接器没报错,因为路径里恰好有一个同名的旧文件。
解决:先确认 freetype 是静态库还是动态库。静态库要在引用工程里定义FT2_BUILD_LIBRARY的反面——也就是不定义,让FT_EXPORT展开成空,同时定义FT_STATIC_LIBRARY(部分版本需要)。动态库则要确保 freetype 的头文件是按 DLL 模式编出来的。最稳的办法是直接编成静态库,FT_DISABLE_DLL一类的开关不用管,链接时把freetype.lib放进输入列表,符号一定找得到。至于路径,我一般把 Debug 和 Release 的 lib 名字用CMAKE_DEBUG_POSTFIX区分开,物理上就不会拿错。
6. 进阶:把这次编译固化成可复用的工程模板
单次编过不算本事,能在一个新机器上二十分钟内重来一遍才算。我的做法是把整个流程固化成一个属性表加一个批处理脚本:属性表podofo-vc8.props里写死 include 路径、lib 路径、预处理器定义、运行时库,批处理里按顺序调三个依赖的 CMake 加 podofo 的 CMake,全部用-DCMAKE_INSTALL_PREFIX指向同一个根目录。新机器上只要把源码包和 CMake 2.8.12 拷过去,跑一遍脚本,产物就是一致的。
属性表里最值得写死的是这几项:AdditionalIncludeDirectories用$(PodoFoRoot)\install\include;$(PodoFoRoot)\install\include\freetype2,AdditionalLibraryDirectories用 Release 和 Debug 两条,AdditionalDependencies用podofo.lib;freetype.lib;jpeg.lib;zlib.lib,其中 Debug 版用podofo.lib;freetyped.lib;jpegd.lib;zlibd.lib。预处理器定义固定成WIN32;_LIB;PODOFO_STATIC;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;HAVE_BOOLEAN。这几行抄过去,新工程就自动具备和 podofo 一致的编译环境,不用每次手工配。
验证环节我加了一个自检脚本,编译完立即跑一遍最小 Demo,把输入输出对一遍:
:: 编完立刻验,别等业务代码写完才发现库是坏的 build_demo.bat demo.exe sample.pdf || goto :fail if not exist out.pdf goto :fail :: 用 podofo 自己的工具反读一遍,确认写出的文件结构合法 pdfinfo.exe out.pdf > out.txt 2>&1 findstr /c:"pages = 1" out.txt || goto :fail这个脚本的价值在于把"编译成功"和"功能可用"分开验证。demo.exe走的是加载加写回,pdfinfo走的是纯解析,两条路径覆盖的代码不同。任何一条挂了,说明问题不在业务代码而在库本身,直接回退重编,不用浪费时间调业务。goto :fail那几行是刻意的,批处理的||对某些退出码不敏感,用标签跳转更可靠。
版本核对表也建议一起维护,我习惯把它放在工程根目录的toolchain.txt里,每次换机器先对一遍:CMake 必须是 2.8.12 附近;zlib 用 1.2.8;libjpeg 用jpeg-9a原版;freetype 用 2.6.5;podofo 固定 0.9.7 的 tag,不要拿 master。这几项里最容易被"顺手升级"的是 freetype,升到 2.9 之后 CMake 最低版本要求就到 3.1 了,VS2005 生成器直接消失,整个流程从第一步就断掉。
从那以后我每次搭这类老编译链,都会先把toolchain.txt和属性表一起建好、再动一行源码,宁可前期多花半小时,也不愿意在链接错误里泡一整天。希望帮到你。
本文还有配套的精品资源,点击获取