在 C# 中执行邮件合并时 LibreOffice 崩溃
Posted
技术标签:
【中文标题】在 C# 中执行邮件合并时 LibreOffice 崩溃【英文标题】:LibreOffice crash when perform a Mail Merge in C# 【发布时间】:2017-02-10 23:15:36 【问题描述】:我正在开发一个使用 LibreOffice 执行邮件合并的 C# 应用程序。
我可以执行邮件合并并将结果保存为 pdf,但在调用 xDesktop.terminate()
后会发生崩溃,并且下次打开 LibreOffice 时会出现崩溃报告。
每次我使用com.sun.star.text.MailMerge
服务并关闭LibreOffice时,作为邮件合并基础的模型都不会从临时文件夹中删除。
例如文件:%TEMP%\lu97964g78o.tmp\lu97964g78v.tmp
%TEMP%\lu97964g78o.tmp\SwMM0.odt
我似乎没有正确关闭 MailMerge 服务。
重现 Writer 崩溃的最少代码:
// Program.cs
using System;
using System.IO;
namespace LibreOffice_MailMerge
class Program
static void Main(string[] args)
// LibreOffice crash after calling xDesktop.terminate().
// The crash reporting appear when the second itaration begins.
int i;
for (i = 0; i < 2; i++)
//Minimal code to reproduce the crash.
using (var document = new TextDocument())
document.MailMerge();
// TextDocument.cs
using Microsoft.Win32;
using System;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
namespace LibreOffice_MailMerge
class TextDocument : IDisposable
private XComponentContext localContext;
private XMultiComponentFactory serviceManager;
private XDesktop xDesktop;
public TextDocument()
InitializeEnvironment(); // Add LibreOffice in PATH environment variable.
localContext = uno.util.Bootstrap.bootstrap();
serviceManager = localContext.getServiceManager();
xDesktop = (XDesktop)serviceManager.createInstanceWithArgumentsAndContext("com.sun.star.frame.Desktop", new uno.Any[] , localContext);
public void MailMerge()
// #############################################
// # No crash if these two lines are commented #
// #############################################
var oMailMerge = serviceManager.createInstanceWithArgumentsAndContext("com.sun.star.text.MailMerge", new uno.Any[] , localContext);
((XComponent)oMailMerge).dispose();
public void Dispose()
if (xDesktop != null)
xDesktop.terminate();
操作系统:Windows 10 64 位和 Windows 7 32 位 LibreOffice 和 SDK 版本:5.3.0.3 x86(还测试了 5.2.4.2 和 5.2.5.1 x86) LibreOffice 快速入门:已禁用Crashreport
Complete Visual Studio project 在 GitHub 上。
非常感谢任何能告诉我哪里错的人。
编辑:更新代码并提交错误报告。
编辑 2:希望做一些有用的事情,我针对上述问题发布了解决方法。
基本上,我通过传递一个目录作为参数来启动 LibreOffice 进程,在该目录中创建一个新的用户配置文件。
我还将 tmp
环境变量的路径更改为仅 LibreOffice 进程指向上一个目录。
完成工作后,我删除了这个目录,其中包含由 LibreOffice API 错误创建的崩溃报告和临时文件。
程序.cs:
using System;
using System.IO;
namespace LibreOffice_MailMerge
class Program
static void Main(string[] args)
// Example of mail merge.
using (var document = new WriterDocument())
var modelPath = Path.Combine(Environment.CurrentDirectory, "Files", "Test.odt");
var csvPath = Path.Combine(Environment.CurrentDirectory, "Files", "Test.csv");
var outputPath = Path.Combine(Path.GetTempPath(), "MailMerge.pdf");
document.MailMerge(modelPath, csvPath);
document.ExportToPdf(outputPath);
LibreOffice.cs:
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
namespace LibreOffice_MailMerge
class LibreOffice : IDisposable
// LibreOffice process.
private Process process;
// LibreOffice user profile directory.
public string UserProfilePath get; private set;
public XComponentContext Context get; private set;
public XMultiComponentFactory ServiceManager get; private set;
public XDesktop2 Desktop get; private set;
public LibreOffice()
const string name = "MyProjectName";
UserProfilePath = Path.Combine(Path.GetTempPath(), name);
CleanUserProfile();
InitializeEnvironment();
var arguments = $"-env:UserInstallation=new Uri(UserProfilePath) --accept=pipe,name=name;urp --headless --nodefault --nofirststartwizard --nologo --nolockcheck";
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "soffice";
process.StartInfo.Arguments = arguments;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.EnvironmentVariables["tmp"] = UserProfilePath;
process.Start();
var xLocalContext = uno.util.Bootstrap.defaultBootstrap_InitialComponentContext();
var xLocalServiceManager = xLocalContext.getServiceManager();
var xUnoUrlResolver = (XUnoUrlResolver)xLocalServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xLocalContext);
for (int i = 0; i <= 10; i++)
try
ServiceManager = (XMultiComponentFactory)xUnoUrlResolver.resolve($"uno:pipe,name=name;urp;StarOffice.ServiceManager");
break;
catch (unoidl.com.sun.star.connection.NoConnectException)
System.Threading.Thread.Sleep(1000);
if (Equals(i, 10))
throw;
Context = (XComponentContext)((XPropertySet)ServiceManager).getPropertyValue("DefaultContext").Value;
Desktop = (XDesktop2)ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", Context);
/// <summary>
/// Set up the environment variables for the process.
/// </summary>
private void InitializeEnvironment()
var nodes = new RegistryHive[] RegistryHive.CurrentUser, RegistryHive.LocalMachine ;
foreach (var node in nodes)
var key = RegistryKey.OpenBaseKey(node, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\LibreOffice\UNO\InstallPath");
if (key != null && key.ValueCount > 0)
var unoPath = key.GetValue(key.GetValueNames()[key.ValueCount - 1]).ToString();
Environment.SetEnvironmentVariable("PATH", $"unoPath;Environment.GetEnvironmentVariable("PATH")", EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("URE_BOOTSTRAP", new Uri(Path.Combine(unoPath, "fundamental.ini")).ToString(), EnvironmentVariableTarget.Process);
return;
throw new System.Exception("LibreOffice not found.");
/// <summary>
/// Delete LibreOffice user profile directory.
/// </summary>
private void CleanUserProfile()
if (Directory.Exists(UserProfilePath))
Directory.Delete(UserProfilePath, true);
#region IDisposable Support
private bool disposed = false;
protected virtual void Dispose(bool disposing)
if (!disposed)
if (disposing)
if (Desktop != null)
Desktop.terminate();
Desktop = null;
ServiceManager = null;
Context = null;
if (process != null)
// Wait LibreOffice process.
if (!process.WaitForExit(5000))
process.Kill();
process.Dispose();
CleanUserProfile();
disposed = true;
~LibreOffice()
Dispose(false);
public void Dispose()
Dispose(true);
GC.Collect();
GC.SuppressFinalize(this);
#endregion
WriterDocument.cs:
using System;
using System.IO;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.sdb;
using unoidl.com.sun.star.task;
using unoidl.com.sun.star.text;
using unoidl.com.sun.star.util;
namespace LibreOffice_MailMerge
class WriterDocument : LibreOffice
private XTextDocument xTextDocument = null;
private XDatabaseContext xDatabaseContext;
public WriterDocument()
xDatabaseContext = (XDatabaseContext)ServiceManager.createInstanceWithContext("com.sun.star.sdb.DatabaseContext", Context);
/// <summary>
/// Execute a mail merge.
/// </summary>
/// <param name="modelPath">Full path of model.</param>
/// <param name="csvPath">>Full path of CSV file.</param>
public void MailMerge(string modelPath, string csvPath)
const string dataSourceName = "Test";
var dataSourcePath = Path.Combine(UserProfilePath, $"dataSourceName.csv");
var databasePath = Path.Combine(UserProfilePath, $"dataSourceName.odb");
File.Copy(csvPath, dataSourcePath);
CreateDataSource(databasePath, dataSourceName, dataSourcePath);
// Set up the mail merge properties.
var oMailMerge = ServiceManager.createInstanceWithContext("com.sun.star.text.MailMerge", Context);
var properties = (XPropertySet)oMailMerge;
properties.setPropertyValue("DataSourceName", new uno.Any(typeof(string), dataSourceName));
properties.setPropertyValue("DocumentURL", new uno.Any(typeof(string), new Uri(modelPath).AbsoluteUri));
properties.setPropertyValue("Command", new uno.Any(typeof(string), dataSourceName));
properties.setPropertyValue("CommandType", new uno.Any(typeof(int), CommandType.TABLE));
properties.setPropertyValue("OutputType", new uno.Any(typeof(short), MailMergeType.SHELL));
properties.setPropertyValue("SaveAsSingleFile", new uno.Any(typeof(bool), true));
// Execute the mail merge.
var job = (XJob)oMailMerge;
xTextDocument = (XTextDocument)job.execute(new NamedValue[0]).Value;
var model = ((XPropertySet)oMailMerge).getPropertyValue("Model").Value;
CloseDocument(model);
DeleteDataSource(dataSourceName);
((XComponent)oMailMerge).dispose();
/// <summary>
/// Export the document as PDF.
/// </summary>
/// <param name="outputPath">Full path of the PDF file</param>
public void ExportToPdf(string outputPath)
if (xTextDocument == null)
throw new System.Exception("You must first perform a mail merge.");
var xStorable = (XStorable)xTextDocument;
var propertyValues = new PropertyValue[2];
propertyValues[0] = new PropertyValue() Name = "Overwrite", Value = new uno.Any(typeof(bool), true) ;
propertyValues[1] = new PropertyValue() Name = "FilterName", Value = new uno.Any(typeof(string), "writer_pdf_Export") ;
var pdfPath = new Uri(outputPath).AbsoluteUri;
xStorable.storeToURL(pdfPath, propertyValues);
private void CloseDocument(Object document)
if (document is XModel xModel && xModel != null)
((XModifiable)xModel).setModified(false);
if (xModel is XCloseable xCloseable && xCloseable != null)
try
xCloseable.close(true);
catch (CloseVetoException)
else
try
xModel.dispose();
catch (PropertyVetoException)
/// <summary>
/// Register a new data source.
/// </summary>
/// <param name="databasePath">Full path of database.</param>
/// <param name="datasourceName">The name by which register the database.</param>
/// <param name="dataSourcePath">Full path of CSV file.</param>
private void CreateDataSource(string databasePath, string dataSourceName, string dataSourcePath)
DeleteDataSource(dataSourceName);
var oDataSource = xDatabaseContext.createInstance();
var XPropertySet = (XPropertySet)oDataSource;
// http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1sdb_1_1XOfficeDatabaseDocument.html
var xOfficeDatabaseDocument = ((XDocumentDataSource)oDataSource).DatabaseDocument;
var xModel = (XModel)xOfficeDatabaseDocument;
var xStorable = (XStorable)xOfficeDatabaseDocument;
// Set up the datasource properties.
var properties = new PropertyValue[9];
properties[0] = new PropertyValue() Name = "Extension", Value = new uno.Any(typeof(string), "csv") ;
properties[1] = new PropertyValue() Name = "HeaderLine", Value = new uno.Any(typeof(bool), true) ;
properties[2] = new PropertyValue() Name = "FieldDelimiter", Value = new uno.Any(typeof(string), ";") ;
properties[3] = new PropertyValue() Name = "StringDelimiter", Value = new uno.Any(typeof(string), "\"") ;
properties[4] = new PropertyValue() Name = "DecimalDelimiter", Value = new uno.Any(typeof(string), ".") ;
properties[5] = new PropertyValue() Name = "ThousandDelimiter", Value = new uno.Any(typeof(string), "") ;
properties[6] = new PropertyValue() Name = "EnableSQL92Check", Value = new uno.Any(typeof(bool), false) ;
properties[7] = new PropertyValue() Name = "PreferDosLikeLineEnds", Value = new uno.Any(typeof(bool), true) ;
properties[8] = new PropertyValue() Name = "CharSet", Value = new uno.Any(typeof(string), "UTF-8") ;
var uri = Uri.EscapeUriString($"sdbc:flat:dataSourcePath".Replace(Path.DirectorySeparatorChar, '/'));
XPropertySet.setPropertyValue("URL", new uno.Any(typeof(string), uri));
XPropertySet.setPropertyValue("Info", new uno.Any(typeof(PropertyValue[]), properties));
// Save the database and register the datasource.
xStorable.storeAsURL(new Uri(databasePath).AbsoluteUri, xModel.getArgs());
xDatabaseContext.registerObject(dataSourceName, oDataSource);
CloseDocument(xOfficeDatabaseDocument);
((XComponent)oDataSource).dispose();
/// <summary>
/// Revoke datasource.
/// </summary>
/// <param name="datasourceName">The name of datasource.</param>
private void DeleteDataSource(string datasourceName)
if (xDatabaseContext.hasByName(datasourceName))
var xDocumentDataSource = (XDocumentDataSource)xDatabaseContext.getByName(datasourceName).Value;
xDatabaseContext.revokeDatabaseLocation(datasourceName);
CloseDocument(xDocumentDataSource);
((XComponent)xDocumentDataSource).dispose();
#region IDisposable Support
private bool disposed = false;
protected override void Dispose(bool disposing)
if (!disposed)
if (disposing)
if (xTextDocument != null)
CloseDocument(xTextDocument);
xTextDocument = null;
disposed = true;
base.Dispose(disposing);
#endregion
【问题讨论】:
代码中似乎缺少关闭文档的命令。例如xCloseable.close(true);
在这里:wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/…。
@JimK 谢谢,但我已经看到了那个链接,并且我已经在使用 xCloseable 来关闭由邮件合并创建的文档。我在 github 上创建了一个存储库,其中包含我使用的代码的更完整示例。邮件合并有效,但总是发生我提到的崩溃。
【参考方案1】:
没有崩溃我无法让它工作,根据this discussion,其他人也遇到了同样的问题。
但是,应该可以多次关闭和重新打开文档(而不是 LibreOffice 应用程序本身)而不会崩溃。
所以首先手动打开 LibreOffice,或者使用 shell 脚本(例如 PowerShell)打开 LibreOffice。然后运行您的应用程序。执行多个邮件合并,但不要调用xDesktop.terminate()
。应用程序完成后,手动关闭 LibreOffice 或使用 shell 脚本将其关闭。
结果:没有崩溃! :)
【讨论】:
我试着照你说的做,但在临时文件夹中仍然没有删除用作邮件合并基础的模型。以上是关于在 C# 中执行邮件合并时 LibreOffice 崩溃的主要内容,如果未能解决你的问题,请参考以下文章
用于创建连接到现有电子表格“数据库”的 Libreoffice 数据库文件的 BASH 脚本
LibreOffice 停止工作,同时使用 C# 应用程序将 XLS 转换为 XHTML 文件