资讯详情

C#用DocX库免装Office编辑Word转PDF,源码+Demo全解析

📅 2026/10/19 5:41:12 | 华诺云谱 👁 阅读
C#用DocX库免装Office编辑Word转PDF,源码+Demo全解析
简介C# DocX 是一套用于操作 Word 文档的 .NET 组件资源面向需要在服务端或客户端程序中自动生成、修改 Word 内容并完成 PDF 转换的开发者。使用它不需要安装微软办公软件可通过编程方式控制段落、表格、图片、样式、页眉页脚等文档元素特别适合报表生成、合同处理、在线文档编辑与数据批量导出等应用场景。整个压缩包约 4.93MB共 244 个文件包含 7 个核心动态库、83 个源码文件、6 个工程文件以及 86 个 Word 示例文档还配有图片、配置和说明文档目录结构清晰便于按模块阅读与二次开发。随包演示程序覆盖了新建文档、内容替换、表格调整、PDF 转换等典型用法源码注释详细能够帮助初中级 .NET 开发者快速掌握接口调用方式其中动态库可直接引用使用免去额外配置 Office COM 组件的麻烦也可作为小型自动化办公项目的基础参考。目前已有 277 人学习下载适合希望快速上手 Word 文档编程处理的开发者。1. DocX 源码 DemoC# 编辑 Word 和转 PDF 的最省心方案很多老项目的“导出报表”需求第一反应都是挂 Microsoft.Office.Interop.Word但换一台没装 Office 的服务器就当场翻车。我拆的这个包里带的是 DocXXceed.Document.NET 与 Xceed.Words.NET 两枚 DLL它走 OpenXML 路线不依赖 Office能在内存里直接建文档、插表格、配样式再搭配 LibreOffice 或本机 Word 服务把 PDF 一并产出。源码 demo 都齐适合做进销存报表、合同批量生成、OA 公文导出的 C# 开发者拿来就能跑坑我已经替你们踩过一轮了。2. 拆开源码包两枚 DLL 的职责、工程结构与引用方式解压资源包之后第一眼很容易被一堆源码文件搞懵。别着急打开 sln先按文件类别把家当摸清再决定用哪种姿势引用。2.1 源码目录长什么样三块内容各管一摊大多数 DocX 源码包解开后是这种结构根目录放解决方案文件下面有 DocX 源码工程、Demo 工程、bin 目录和文档。第一次拿到手先看三块地方bin 目录放着两个编译好的 DLLXceed.Document.NET.dll 和 Xceed.Words.NET.dll。不想深究源码的直接引这两个就够了。源码工程DocX 类库的全部源代码适合想跟踪内部实现、想改默认行为的开发者。Demo 工程一个可直接运行的控制台或 WinForm 项目里面写了从建文档到保存的全流程用它验证环境最快。文件清单可以用这张表概括文件/目录是什么使用时怎么处置Xceed.Document.NET.dll底层 OpenXML 文档包结构与序列化必须引用Xceed.Words.NET.dll对外主 APIDocX/Paragraph/Table 等类型必须引用DocX 源码工程库的源代码需要调试库内部时加入解决方案Demo 工程可直接运行的示例第一次跑建议直接运行它说明文档使用说明与版本记录部署前先看版本约束注意包里的两个 DLL 是编译好的二进制版本已经固定。写代码时以包内版本为准别用 NuGet 最新版硬套后面第 5 章会讲为什么。2.2 两枚 DLL 的分工谁在管磁盘谁在管对象这个库早期开源版叫 DocX后来代码被整合进 Xceed.Words.NET 这条线程序集拆成两个。两者的边界很清晰Xceed.Document.NET.dll 管的是“文档在磁盘上的长相”OpenXML 包结构、WordprocessingDocument、序列化和解压缩相当于把内存对象翻译成 .docx 文件的翻译器。Xceed.Words.NET.dll 管的是“你在代码里摸到的对象”DocX、Paragraph、Table、Image、Footer相当于模具。写代码时大多数情况会这样开头using Xceed.Words.NET; using Xceed.Document.NET;两个 using 都写上不是手误因为部分类型与枚举分散在两个命名空间里。只写一个也能编过一多半但一用到 TableDesign、Alignment 这些枚举另一个找不到又会报错。源码 Demo 里的写法就是两个一起 using照抄最省事。这里有个常见误用有人嫌麻烦只引 Xceed.Words.NET.dll结果 InsertParagraph 能过InsertTable 就报类型不存在。原因是 Table 类型的完整定义牵扯到 Xceed.Document.NET 程序集单引一个 DLL 无法解析全部依赖。所以不管是直接引 DLL 还是走 NuGet两个程序集都必须能在输出目录找到。提示如果编译出现“无法解析类型 Xceed.Document.NET.Table”这类错误第一时间看 bin 目录下两个 DLL 是否都在优先排查引用丢失而不是去改代码。2.3 把 DLL 放进项目的三种接法接法一直接引用源码包里的 DLL。在 VS 里右键项目 → 添加 → 引用 → 浏览选中 bin 下两个 DLL。随后马上检查引用属性里的“复制本地”ItemGroup Reference IncludeXceed.Document.NET HintPath..\lib\Xceed.Document.NET.dll/HintPath Privatetrue/Private /Reference Reference IncludeXceed.Words.NET HintPath..\lib\Xceed.Words.NET.dll/HintPath Privatetrue/Private /Reference /ItemGroup这段是 csproj 里的引用配置。HintPath 是 DLL 的路径Privatetrue 表示“复制本地”部署时 DLL 会跟着 exe 走。如果不设 Private运行时大概率抛 FileNotFoundException那种“编译过了但一跑就崩”的怪问题多半来自这里。接法二用 NuGet 安装。包管理器里搜 Xceed.Words.NET 直接装Install-Package Xceed.Words.NET包管理器会自动把 Xceed.Document.NET 依赖一起带上。但注意NuGet 拉下来的是新版API 和源码包里的老版本可能有出入尤其是枚举名和方法重载。想稳定复现这个资源包的行为优先用包内自带的 DLL。接法三把源码工程加入当前解决方案。右键解决方案 → 添加 → 现有项目选 DocX 工程再把项目引用从“程序集引用”改成“工程引用”。好处是调试时能 F11 跳进库内部看它到底怎么解析段落代价是库源码也要参与编译目标框架必须对齐否则编译报一堆杂七杂八的类型错误。2.4 一张空文档验证环境三行代码决生死引用配好后我习惯先建一个最小控制台项目验证环境通不通using System; using Xceed.Words.NET; class Program { static void Main(string[] args) { using (DocX doc DocX.Create(env_check.docx)) { doc.InsertParagraph(环境正常); doc.Save(); } Console.WriteLine(生成完成); } }逻辑说明DocX.Create 在内存里新建空白文档不落盘InsertParagraph 插入一段文本Save 才写入工作目录下的 env_check.docx。using 保证对象及时释放这个库持有文件句柄忘了释放后面删文件会报“正由另一进程使用”。参数说明Create 的参数是输出文件路径相对路径会落到程序运行目录WinForm 调试时通常在 bin\Debug 下。如果这段能编译能运行说明两个 DLL 引用和 .NET 框架版本都没问题。2.5 源码包与 NuGet 版本不一致时的处理原则这个包里面有现成的 DLL而 NuGet 上又有新版本两者 API 不完全一致这是个经常被问到的点。我的建议是如果只是做报表导出、合同生成这些常规操作用包里带的老版本 DLL 完全够没必要追新。如果要用新版才有的功能先把 Demo 工程切到新包跑一遍回归确认原来能用的大功能没变化再升级。记录一下 DLL 的文件版本和大小出问题排查时可以对照避免“我明明改的是这套代码跑起来怎么行为不一样”这种黑匣子问题。把版本差异当成固定变量处理能省掉很多排查时间。3. 跑通 Demo从空文档到一份带样式、表格、图片的完整 Word环境通了之后把最小示例扩展成能交付的报表骨架。这一章按“段落 → 表格 → 图片 → 页脚分页”的顺序推进每一步都可直接抄。3.1 最小创建流程Create → Insert → Save把第 2 章的 env_check 扩展成正式点的报表开头using System; using System.Drawing; using Xceed.Words.NET; class Program { static void Main(string[] args) { using (DocX doc DocX.Create(report.docx)) { Paragraph title doc.InsertParagraph(月度销售报表, false) .FontSize(20).Bold().Color(Color.DarkBlue); title.Alignment Alignment.center; doc.InsertParagraph(统计时间2024-05-01 至 2024-05-31) .FontSize(11) .SpacingAfter(8); doc.Save(); } Console.WriteLine(报表骨架已生成); } }逻辑说明InsertParagraph(string text, bool trackChanges) 的第二个参数是“是否开启修订跟踪”业务生成一般传 false。FontSize、Bold、Color 都返回 Paragraph 本身所以能一路链式调用。Alignment 是枚举属性不能放进链式调用里只能单独赋值。参数说明FontSize 单位是磅pt正文常用 10.5五号或 12小四标题可以到 20 左右。SpacingAfter 是段后间距单位也是磅控制段与段之间的留白。Color 用的是 System.Drawing.Color可以写 Color.DarkBlue 这种预设色也能用 Color.FromArgb(51, 102, 204) 自定义。这个骨架能适配任意业务把标题替换成“合同编号xxx”就是合同导出的雏形。3.2 表格从二维数据生成带边框的表格报表离不开表格DocX 的 Table 用法比较直接using Xceed.Document.NET; // 生成 4 行 4 列 Table table doc.AddTable(4, 4); table.Design TableDesign.TableGrid; table.AutoFit AutoFit.Window; string[,] data new string[,] { { 商品, 数量, 单价, 金额 }, { 键盘, 20, 299, 5980 }, { 鼠标, 35, 89, 3115 }, { 显示器, 8, 1299, 10392 } }; for (int r 0; r table.Rows.Count; r) { for (int c 0; c table.Columns.Count; c) { table.Rows[r].Cells[c].Paragraphs[0].InsertText(data[r, c]); } } doc.InsertTable(table);逻辑说明AddTable 先建空表壳Design 决定整套边框和底纹风格AutoFit.Window 让表格宽度铺满页面。两层循环把数据填进每个单元格里的 Paragraph最后 InsertTable 把表格挂到文档正文尾部。参数说明TableDesign 有很多预设TableDesign.TableGrid 是最接近普通报表的“田字格”细黑边框LightListAccent1 是带浅色底纹的列表风格。AutoFit 有两个常用值Window 是按页面宽度铺满Contents 是按内容收缩。建议固定写 TableGrid Window最稳。提示很多新手漏写 Design默认样式下表格线可能很淡甚至完全看不到。这里先记住“建表必设 Design”第 5 章会单独展开这个坑。3.3 插入图片从文件到内存再挂到段落带 logo、带签章的公文导出需要往文档里插图片。DocX 的操作分两步Image logo doc.AddImage(logo.png); Picture pic logo.CreatePicture(); pic.Width 120; // 像素 pic.Height 40; // 像素 Paragraph logoPara doc.InsertParagraph(string.Empty); logoPara.Alignment Alignment.center; logoPara.InsertPicture(pic);逻辑说明AddImage 把图片文件读进内存并建立映射不立即显示CreatePicture 创建图片对象Width/Height 控制显示尺寸InsertPicture 把图片挂到某个段落里。图片本身要占一行所以先建一个空段落顺手把对齐方式设成居中。参数说明Width/Height 的单位是像素原图很大时不设尺寸就会按原始像素显示直接超出页面宽度。建议固定宽高同时保持宽高比不然会变形。AddImage 的参数还能接字节数组或 MemoryStream从数据库读图片时可以先转成 MemoryStream 再传入不需要落盘临时文件。3.4 页脚与分页让文档更像正式交付物// 在页脚插入固定文本 Footer footer doc.Footers.Odd; footer.InsertParagraph(内部资料注意保密).Alignment Alignment.center; // 在正文末尾分页续写第二页 doc.InsertParagraph(本页为签署页).InsertPageBreakAfterSelf();逻辑说明Footers.Odd 是“奇数页页脚”整篇默认用同一个页脚时直接操作它。InsertPageBreakAfterSelf 会在当前段落后面插入分页符适合把签署栏单独放到新的一页。参数说明如果文档设置了“首页不同”首页页脚要单独写 doc.Footers.first。没有特殊需求时用 Odd 就能把整篇统一搞定。这里能跑通说明你已经具备生成一份完整 Word 交付物的能力了。4. 把 Word 转成 PDF两条路线、参数与并发边界DocX 本身的重心是 docx 的读写PDF 渲染不是它的核心职责。所以转 PDF 的常见做法是二选一本机装了 Office 就走 Word COM 精确还原服务器不想装 Office就用 LibreOffice 的 headless 模式批量转换。4.1 路线一本机装了 Word用 COM 转出“像素级”PDF这个方式适合内网、桌面端、Office 环境本来就存在的场景。先生成 docx再用互操作打开另存为 PDFusing System; using System.Runtime.InteropServices; using Word Microsoft.Office.Interop.Word; public static void ConvertWithWord(string docxPath, string pdfPath) { Word.Application app new Word.Application { Visible false }; Word.Document doc null; try { doc app.Documents.Open(docxPath); doc.ExportAsFixedFormat(pdfPath, Word.WdExportFormat.wdExportFormatPDF); } finally { if (doc ! null) doc.Close(false); app.Quit(); Marshal.ReleaseComObject(doc); Marshal.ReleaseComObject(app); } }逻辑说明先启动 Word 应用Open 打开文件ExportAsFixedFormat 是 Word“另存为 PDF”的底层接口。finally 里 Close 文档、Quit 应用、再用 Marshal.ReleaseComObject 把 COM 引用计数降下去三步缺一步都会让 WINWORD.EXE 进程留在后台。参数说明wdExportFormatPDF 指定 PDF 导出格式Close(false) 表示不保存对 docx 的改动保持源文件干净。app.Visible false 让转换在后台跑不弹窗口。如果转换过程中抛“拒绝访问”去看一下 Office 是否允许服务账户以交互方式运行。这条路排版还原度最高Word 渲染 PDF 天然兼容 Word 自身的字体和分页规则。代价是两个硬约束服务器必须装 Office授权方式得允许服务调用并发转换要控制数量几十个线程同时启动 Word 进程内存会先撑不住。4.2 路线二不装 Office用 LibreOffice headless 批量转Linux 服务器、Docker 容器、不想碰 Office 授权时改用 LibreOffice 更稳。它打开 docx 后另存为 PDF渲染引擎独立不占 Office 许可。先确认 soffice 可执行文件的位置Windows 典型路径是C:\Program Files\LibreOffice\program\soffice.exe然后写一个 C# 包装方法using System.Diagnostics; public static int ConvertWithLibreOffice(string sofficePath, string inputDir, string outputDir) { ProcessStartInfo psi new ProcessStartInfo { FileName sofficePath, Arguments $--headless --convert-to pdf --outdir \{outputDir}\ \{inputDir}\, UseShellExecute false, CreateNoWindow true }; using (Process p Process.Start(psi)) { // 等待最多 60 秒 if (p.WaitForExit(60000)) { return p.ExitCode; } p.Kill(); return -1; } }逻辑说明headless 让 LibreOffice 不带界面运行--convert-to pdf 指定目标格式--outdir 是输出目录最后一个参数是待转换文件或目录。Process 对象等待退出超时则 Kill 并返回 -1调用方要把这个返回值当失败处理。参数说明--outdir 必须提前创建好否则 LibreOffice 会静默失败返回非 0如果传的是目录LibreOffice 会把目录下所有可转换文件都转一遍业务上要按单文件处理时尽量传具体文件路径。首次运行会初始化用户配置目录可能多花几秒之后会快很多。这条路要注意中文字体转换结果依赖系统安装的中文字体容器里如果没装PDF 里中文全是方块具体排查在第 5 章 5.3。4.3 验证转换结果文件大小、页数、内容转换完别急着上线我一般做三步验证。第一步确认 PDF 存在且大小不是 0第二步读页数看是否匹配第三步把 PDF 再转成文本搜关键词。using System; using System.IO; static void ValidatePdf(string pdfPath) { FileInfo fi new FileInfo(pdfPath); if (!fi.Exists) { throw new Exception(PDF 未生成); } if (fi.Length 1024) { throw new Exception(PDF 大小异常可能为空文件); } Console.WriteLine($PDF 存在大小 {fi.Length} 字节); }逻辑说明这是一个最小校验函数文件存在和大小非零只能证明转换过程没失败不能证明内容正确。要验证内容可以把 PDF 再转成 txt在纯文本里搜“报表编号”“合计金额”这类必然出现的关键词搜到才算成功。参数说明1024 字节这个阈值需要根据实际模板调整。纯文字模板可能很小带 logo 的可能几百 KB建议先用最小模板实测一次再定阈值别直接抄固定值。两条转换路线对比如下方案环境要求排版还原度并发能力维护成本Word COM要装 Office 授权最高差需限制并发高补丁和授权要跟着管LibreOffice headless免授权高字体需对齐好多进程可控低独立于办公软件5. 避坑与常见问题五个翻车点与对应排查源码和 demo 能跑通只是第一步上生产的坑大部分在环境差异。这一章列五个最常见问题每条都是“现象 → 原因 → 解决”的结构。5.1 现象两个 DLL 都引了编译仍报找不到类型某开发者把 DLL 拖进引用列表代码里也写了 using但编译时 Xceed.Document.NET 命名空间下的类型时有时无。原因是工程目标框架过低。DocX 面向 .NET Framework 4.5 及以上如果工程是 3.5编译器加载程序集后识别不了部分元数据表现就是“这个类型存在但就是解析不出来”。解决把目标框架切到 .NET Framework 4.6.2 或更高。如果是 .NET Core / NET 5 项目直接引用 Framework 编译的 DLL 有兼容风险改用 NuGet 里对应新框架的包或者单独建一个 .NET Framework 4.6.2 的辅助工程负责生成 docx主程序通过文件或消息队列调用它。5.2 现象保存后 Word 打开提示“文档内容有问题”代码没报错docx 也生成了但 Word 打开时提示文件损坏需要修复打开后内容还可能是空的。最常见的原因是 Save 之后又继续往 DocX 对象里写内容或者把 using 放在 Save 前面导致文档包结构没写完整就释放句柄。DocX 的对象模型很“记仇”保存动作之后继续操作状态就乱了。解决严格按“创建 → 编辑 → Save → 离开 using”的顺序写。Save 尽量放到 using 块的最后一步保存后不要再用 InsertParagraph 或 AddTable。如果同时用 FileStream 读写同一个文件也要先关 FileStream 再动文档避免两把锁抢同一个文件。5.3 现象PDF 中文全部变成方块用 LibreOffice 转出来的 PDF英文数字正常中文全是方框。原因是转换环境缺少中文字体。LibreOffice 找不到字体就用默认字体兜底默认字体不支持中文字形渲染结果就是方框。这既不是 DocX 的问题也不是代码能直接解决的。解决给服务器或容器安装中文字体常见做法是装 fonts-noto-cjk或者直接装系统自带的宋体、微软雅黑。装完字体后注意清理字体缓存不然新字体不生效。容器场景里这个最玄学建议在镜像构建时就装好字体并重建缓存别等到运行时再补。5.4 现象表格没有边框线打印出来一片空白格表里有数据导出后表格线看不到打印预览更明显整张表像隐形了一样。原因是 AddTable 默认的表格样式本身不包含明显边框。很多人以为表格天然带框实际上 OpenXML 里默认样式很可能就是不画线的。解决建表后必须显式指定 Design。前面示例中的 TableDesign.TableGrid 就是干这个的如果用了自定义设计逐单元格检查边框属性。这个坑坑过不少人我一般建表三件套固定写AddTable → Design TableDesign.TableGrid → AutoFit Window。5.5 现象大批量生成时内存只增不减循环生成 1000 个合同内存一路涨到 1GBGC 回收好像也不管事。原因是 DocX 对象和 COM 对象没有被及时释放。尤其和 Word COM 混用时Process 或 COM 实例的引用还挂在托管对象上.NET 垃圾回收管不了这些非托管资源内存自然只升不降。解决每轮循环必须用 using 包住 DocXCOM 转换流程记得 Marshal.ReleaseComObject。LibreOffice 路径每次转换是独立进程好一些但还是要用信号量把并发控制在 2 到 3 个以内。机器内存吃紧时把大循环拆成每批 100 个处理完强制重启一次辅助进程内存就回到基线了。不建议在循环里频繁 GC.Collect那属于治标不治本。6. 进阶模板占位符批量生成合同把格式和数据解耦DocX 另一个高频用法是模板填充。做法是先准备一个 docx 模板所有要替换的位置放 {姓名}、{编号}、{金额} 这样的占位符代码里加载模板做替换。格式调整只改模板和数据代码互不牵连这是大批量文档生成里维护成本最低的路子using Xceed.Words.NET; var records GetRecords(); // 假设从数据库读出要生成的记录 foreach (var row in records) { string outp $D:\output\{row.No}.docx; using (DocX doc DocX.Load(contract_template.docx)) { doc.ReplaceText({编号}, row.No); doc.ReplaceText({姓名}, row.Name); doc.ReplaceText({金额}, row.Amount.ToString(N2)); doc.ReplaceText({日期}, row.Date.ToString(yyyy-MM-dd)); doc.SaveAs(outp); } }逻辑说明DocX.Load 打开模板文件ReplaceText 把占位符逐段替换成真实数据SaveAs 另存到输出目录。模板本身始终不被改动第二次循环加载的永远是干净的模板可以反复使用。参数说明ReplaceText 不区分大小写给占位符加上大括号可以避免把正文里的普通词语误替换SaveAs 路径的扩展名必须是 .docx。如果模板里有几十个占位符也可以用正则批量替换但占位符越简单越好维护。输出目录要先创建SaveAs 不会自动建目录。配合文本校验批量生成后我习惯再加一道保险用 LibreOffice 把每个结果 PDF 转成文本搜“总金额”等关键词统计命中数和输入记录条数对比数目对得上说明没有一条因为占位符写错而静默漏值。我吃过一次亏某次批量通知导出数据里有一个人的姓名恰好和系统占位符长得很像结果被正则替换错位文档内容两处串了。那次之后我定了规矩占位符统一带大括号替换前先用正则预检模板确保占位符一一对应齐全。从那以后每次做文档导出都强制走一遍“模板预检 → 批量生成 → PDF 回读校验”三条流程这个习惯救了我不少次。希望这份源码和踩坑清单帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑