65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 生成 PDF

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (37投票s)

2008年7月23日

CPOL

9分钟阅读

viewsIcon

560630

downloadIcon

11044

使用 OpenOffice 将不同文档类型转换为 PDF。

撰写文章时使用的是 OpenOffice 2.4.1。文章末尾有使代码与 OpenOffice 3 兼容的说明链接。

引言

我必须承认,我不是 PDF 的忠实粉丝。尽管如此,它不知何故设法渗透到我参与的几乎所有项目中——客户想发送文档,Word 绑定到 Windows,HTML 很糟糕,最终还是选择了 PDF。不幸的是,在过去的几年里,C# 处理 PDF 的情况并没有太大变化——如果没有新的、花哨的、收费的组件,我会认为它几乎和 .NET 1.1 时代一样——创建 PDF 简直是件痛苦的事。

对于那些能够访问能将流行格式转换为 PDF 的组件的人来说,这篇文章几乎没有用。但是,对于那些不想或根本无法花费 1000 美元以上来转换其他格式为 PDF 的人来说——我希望这个解决方案能成为一个有吸引力的选择。

想法

在我与我的朋友 Toni Ruža(他主要是一名 Python 开发者)讨论如何轻松地将一些 WordML 报告转换为 PDF 时,他向我推荐了 OpenOffice 的无界面模式。似乎它已经存在相当长一段时间了,但由于它主要面向 Java 开发者,所以 C# 社区对其没有大肆宣传也就不足为奇了。尽管如此,它承诺了很多——你安装 OpenOffice,以服务模式启动它,通过 API 发送命令,并可以使用它提供的任何功能。最重要的是,我的兴趣在于加载任何支持的格式到 OpenOffice 中,然后将其导出为 PDF。

需要说明的是,在本文中,我将讨论从其他文档创建 PDF,而不是从零开始。如果您正在寻找一种方法来做到这一点,我鼓励您首先查看我的 生成 Word 报表/文档 文章。遵循它,您将可以轻松地(像这里使用的文件一样)从数据库或 XML 创建 WordML 文件。

解决方案架构

我遗憾地发现 OpenOffice 的无界面模式只是最小化 GUI 操作,而不是完全避免它们。作为有过 Word.Application.Open()(通过以编程方式模仿用户操作来使用 Word 等交互式应用程序)的糟糕经历的人,我开始思考如何将 OpenOffice 隔离起来,并独立于主应用程序进程对其进行查询,从而实现松耦合和更稳定的环境。结果是一个 Windows 服务,它封装了 OpenOffice 进程,负责安全上下文和使用,同时通过 Remoting 提供所需的功能(我真是个面向服务的怪胎吗?:)。

这是一个展示过程中使用的类的图表

Figure 1 – Class diagram

图 1 – 类图

ConversionToPDF 是执行实际工作的核心类。它使用了 unoidl.com.sun 命名空间中的各种类来与 OpenOffice 通信,并模拟打开文件、将其导出为 PDF 等操作。它还使用了 OfficeController 类,该类负责 OpenOffice 进程的生命周期——它启动、监视,并在不使用时最终关闭 soffice.exe 以节省资源。

Receiver 是我的 Windows 服务注册以通过 Remoting 使用的类。它实现了 IReceiver 接口(所需的功能),并充当主应用程序和 OpenOffice 之间的桥梁。

最后,我创建了 GenericSender 类,以方便不熟悉 Remoting 的用户。它提供了 Init 方法,该方法接受 Windows 服务包装器监听的地址(默认为 tcp://:6543/OpenOfficeServiceReceiver)并初始化一个代理接收器(可通过属性访问)。从那时起,一切都很简单,就像 GenericSender.Receiver.ConvertToPDF(...) 一样。

如何在我的机器上启动一切?

让我们一步一步来

  1. 请在此处 下载 OpenOffice,然后执行标准安装。我使用 OO 版本 2.4 开发并测试了一个解决方案。如果您在 x64 计算机上进行设置,请确保将 OO 程序目录(默认:c:\Program Files (x86)\OpenOffice.org 2.4\program)添加到 PATH 环境变量中,如 此论坛帖子中所述。如果更改了环境变量,请务必重启计算机以提交并重新加载更改。
  2. 下载本文附带的源代码,并在 Visual Studio 2008 中使用 Release 配置生成解决方案。构建完成后,检查 OpenOfficeService/bin/Release 并运行 svc_inst.bat。之后,当您转到“控制面板”->“管理工具”->“服务”时,您应该会在服务列表中看到 OpenOffice Wrapper Service。右键单击它,选择“属性”,转到“登录”选项卡,然后选中“允许服务与桌面交互”。
  3. 在启动服务之前,您需要调整许可协议。由于包装器服务将以 LocalSystem 帐户运行,因此您需要以某种方式告诉 OpenOffice LocalSystem 用户“接受”使用条款。为了防止许可协议对话框弹出并阻止所有操作,您需要修改文件 %OOInstallPath%\share\registry\data\org\openoffice\Setup.xcu,找到以下部分
  4. <prop oor:name="ooSetupInstCompleted">
      <value>false</value>
    </prop>
    <prop oor:name="ooSetupShowIntro">
      <value>true</value>
    </prop>

    并将其替换为(请注意 LicenseAcceptDate 必须晚于 OpenOffice 安装时间)

    <prop oor:name="ooSetupInstCompleted" oor:type="xs:boolean">
     <value>true</value>
    </prop>
    <prop oor:name="LicenseAcceptDate" oor:type="xs:string">
     <value>2008-07-22T14:00:00</value>
    </prop>
    <prop oor:name="FirstStartWizardCompleted" oor:type="xs:boolean">
     <value>true</value>
    </prop>

    此步骤摘自 这里,我想感谢 Mirko Nasato 的精彩指南。

    请务必启动任何 OpenOffice 应用程序(例如,开始 -> 程序 -> OpenOffice.org -> OpenOffice.org Writer),并验证它是否能正常加载,以确保 OO 已正确安装和设置。

  5. 验证服务配置(下一章),启动 OpenOffice Wrapper Service,并使用它来转换文档。如果您下载了源代码,可以在解决方案资源管理器中右键单击 Default.aspx(测试应用程序 -> PDFWeb 项目)并选择“在浏览器中查看...”。这是一段使用 OpenOfficeService.Objects.dll 中的 GenericSender 执行转换的代码摘录
  6. protected void GiveMePDFButton_Click(object sender, EventArgs e)
    {
        // Initialize Receiver in GenericSender
        OpenOfficeService.Objects.GenericSender.Init(
            "tcp://:6543/OpenOfficeServiceReceiver");
    
        // Translate path and load up file in byte array, convert it
        string source = Server.MapPath("~/SomeWordML.xml");
        byte[] wordML = File.ReadAllBytes(source);
    
        byte[] result = 
          OpenOfficeService.Objects.GenericSender.Receiver.ConvertToPDF(wordML);
    
        // Write response to client
        Response.AddHeader("content-type", "application/pdf");
        Response.AddHeader("Content-Disposition", 
                           "attachment; filename=result.pdf");
    
        Response.BinaryWrite(result);
    }

    Figure 2 – Testing page

    图 2 – 测试页面

信不信由你,就这么简单!您现在拥有了一个功能齐全的 PDF 转换器,可以通过 Remoting 从 C# 进行查询。

在包装器实现过程中,我考虑了多线程,并(希望)使调用 ConvertToPDF 成为线程安全的。转换请求会被排队并逐一处理,因此 Open Office Wrapper Service 可以被一个以上的应用程序使用,甚至可以从多台机器上使用(运行在其他机器上的应用程序的通用发送者应该使用 tcp://%machineHostingService%:6543/OpenOfficeServiceReceiver 进行初始化)。

配置

目前,Open Office Wrapper Service 有以下设置

  • Port – 服务将在此端口监听请求。默认情况下为 6543。
  • ProcessName – OpenOffice 进程的名称(用于搜索进程列表以查看 OO 是否正在运行)。当您以无界面模式启动 OpenOffice 时,它是 soffice.bin(而不是 soffice.exe)。
  • PathToOpenOffice – 不言自明,对吧?如果您将 OpenOffice 安装在非默认路径,则应更改此设置(默认路径为 c:\Program Files\OpenOffice.org 2.4\program\soffice.exe;在 x64 机器上,在 Program Files 后面加上 (x86))。
  • SecondsIdleAllowed – 当提交转换请求时,OpenOfficeController 会检查 OO 是否在后台运行,如果未运行,则以无界面模式启动 soffice.exe。默认情况下,如果在 60 秒内没有收到新请求,OpenOffice 进程将被终止。
  • CheckIntervalInSeconds – 服务评估 OpenOffice 使用情况的时间间隔(与前一个设置相关)。默认情况下为 30 秒。
  • RequestTimeoutInSeconds – 预期从 OpenOffice 获得响应的时间。如果项目在队列中停留时间过长,或者 OpenOffice 的文件过大无法处理,将抛出 Timeout Exception。默认等待时间为 30 秒。

进程内运行?

我想再次强调,我编写的 Windows 服务仅用于提供安全上下文并充当 OpenOfficeWrapper.dll 的桥梁,该 DLL 在与 OpenOffice 通信方面实现了主要功能。如果您愿意,可以直接引用 OpenOfficeWrapper.dll 并进行进程内 PDF 转换,但您必须确保您的应用程序以足够的安全权限运行!在我的测试中,只有当我在属于 Administrator 组的帐户下运行应用程序时,转换才能成功。

另外,在尝试在 x64 版本的 Windows 上运行 OpenOfficeWrapper 时,您可能会遇到麻烦。我在 Windows 2003 x64 计算机上尝试让我的 Web 应用程序通过进程内使用 OpenOfficeWrapper 进行 PDF 转换时遇到了很多麻烦。因此,如果您确实不需要将所有内容都放在应用程序的进程中,请将封装 OpenOffice 的代码分开,并通过 Windows 服务使用它。

警告和感谢

对我来说,OpenOffice 的文档很糟糕。好吧,我可能只是又一个“准开发者”,觉得看示例比在大量 Wiki 页面、图表和论坛帖子中爬行来获取几行代码更方便,但对于我来说——在浪费了 MSDN 上大量无用信息、不相关链接和无效搜索之后——OO 开发者门户是我不希望看到的文档组织方式的又一个例子。从我所看到的来看,考虑到成本(0 美元),OpenOffice 是一个很棒的产品,我无法对其文档给出同样的评价,这很可惜。

另一方面,OOoForum 上的服务器用户帖子非常有帮助;我特别想感谢 LarsBtcediDannyBConvertToPDF.cs 中大部分转换代码都取自 LarsB 的 命令行 PDF 转换器;所以,谢谢你,伙计——我希望你能继续发布有用的代码片段。

结论

通过这篇文章,我旨在实现一个简单的目标——提供一个易于遵循、免费且通用的解决方案,使用 C# 将文档转换为 PDF。我知道存在技术上更健壮的解决方案,但我不知道其中有任何是免费的。如果您知道——请在评论区与您对本文的看法一起分享。

尽情享用!;)

参考文献

历史

  • 2009 年 1 月 8 日 - 添加了引用。
  • 2008 年 7 月 22 日 - 文章初始版本。
© . All rights reserved.