2017-02-10 27 views
2

我正在使用LibreOffice執行郵件合併的C#應用​​程序。
我可以執行郵件合併並將結果保存爲pdf,但在撥打xDesktop.terminate()後發生崩潰,並且在下次打開LibreOffice時出現崩潰報告。在C#中執行郵件合併時LibreOffice崩潰

每次我使用com.sun.star.text.MailMerge服務並關閉LibreOffice時,用作郵件合併基礎的模型都不會從臨時文件夾中刪除。
例如文件:
%TEMP%\lu97964g78o.tmp\lu97964g78v.tmp
%TEMP%\lu97964g78o.tmp\SwMM0.odt

看來,我沒有正確關閉郵件合併服務。


最少的代碼來重現作家崩潰:

// 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 86 (還測試了5.2.4.2和5.2.5.1 x86)
LibreOffice快速啓動:禁用
Crashreport

Complete Visual Studio project在GitHub上。

非常感謝任何能告訴我我錯在哪裏的人。

編輯:更新代碼並提交錯誤報告。

編輯2:希望做一些有用的事情,我發佈了一個解決上述問題的解決方法。

基本上,我通過傳遞一個參數來創建一個新的用戶配置文件來啓動LibreOffice進程。
我還將僅用於LibreOffice進程的tmp環境變量的路徑更改爲指向上一個目錄。

當我完成工作時,我刪除了該目錄,其中包含由LibreOffice API錯誤創建的崩潰報告和臨時文件。

的Program.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 
    } 
} 
+0

它看起來像代碼缺少關閉文檔的命令。例如'xCloseable.close(true);'如下所示:https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Closing_Documents。 –

+0

@JimK謝謝,但我已經看到了這個鏈接,我已經在使用xCloseable關閉郵件合併創建的文檔。 我在github上創建了一個存儲庫,並提供了我使用的更完整的代碼示例。 郵件合併的作品,但總是發生我提到的崩潰。 – Simone

回答

0

我不能讓它不崩潰工作,根據this discussion,其他人也遇到了同樣的問題。

但應該可以多次關閉並重新打開文檔(而不是LibreOffice應用程序本身)而不會崩潰。

因此,首先手動打開LibreOffice或使用諸如PowerShell等shell腳本。然後運行你的程序。執行多個郵件合併,但不要撥打xDesktop.terminate()。應用程序完成後,手動關閉LibreOffice或使用shell腳本關閉它。

結果:沒有崩潰! :)

+0

我試着照你說的做,但在臨時文件夾中仍然沒有刪除用作郵件合併基礎的模型。 – Simone