2017-02-10 2 views
2

Ich arbeite an einer C# -Anwendung, die einen Seriendruck mit LibreOffice durchführt.
Ich kann den Seriendruck durchführen und das Ergebnis als PDF speichern, aber ein Absturz tritt nach dem Aufruf xDesktop.terminate() und die Absturzberichte erscheinen beim nächsten Öffnen von LibreOffice.LibreOffice abstürzen, wenn Sie eine Seriendruck in C ausführen #

Jedes Mal, wenn ich den Dienst com.sun.star.text.MailMerge benutze und LibreOffice schließe, werden die Modelle, die als Grundlage für den Seriendruck verwendet werden, nicht aus dem temporären Ordner gelöscht.
Zum Beispiel können die Dateien:
%TEMP%\lu97964g78o.tmp\lu97964g78v.tmp
%TEMP%\lu97964g78o.tmp\SwMM0.odt

Es scheint, dass ich nicht richtig den MailMerge Dienst schließen.


Minimal Code Writer Absturz zu reproduzieren:

// 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(); 
     } 
    } 
    } 
} 


OS: Windows 10 64-Bit und Windows 7 32-Bit-
Libreoffice und SDK-Version: 5.3.0.3 x86 (auch getestet 5.2.4.2 und 5.2.5.1 x86)
LibreOffice Schnellstart: deaktiviert
Crashreport

Complete Visual Studio project auf GitHub.

Vielen Dank an jeden, der mir sagen kann, wo ich falsch liege.

BEARBEITEN: Code aktualisieren und einen Fehlerbericht senden.

BEARBEITEN 2: In der Hoffnung, etwas Nützliches zu tun, veröffentliche ich eine Workaround für das oben beschriebene Problem.

Grundsätzlich starte ich den LibreOffice-Prozess, indem ich als Parameter ein Verzeichnis übergebe, in dem ein neues Benutzerprofil erstellt werden soll.
Ich ändere auch den Pfad der tmp Umgebung Variablile für nur LibreOffice-Prozess auf das vorherige Verzeichnis verweisen.

Wenn ich die Arbeit beendet habe, lösche ich dieses Verzeichnis mit Absturzberichten und temporären Dateien, die vom LibreOffice-API-Fehler erstellt wurden.

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

Es sieht aus wie der Code fehlt ein Befehl, um das Dokument zu schließen. Zum Beispiel 'xCloseable.close (true);' wie hier: https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Closing_Documents. –

+0

@JimK Danke, aber ich hatte diesen Link bereits gesehen und verwende bereits xCloseable, um das Dokument zu schließen, das durch den Seriendruck erstellt wurde. Ich habe ein Repository auf GitHub mit einem vollständigeren Beispiel des von mir verwendeten Codes erstellt. Der Seriendruck funktioniert, aber immer tritt der Absturz auf, den ich erwähnte. – Simone

Antwort

0

Ich kann es nicht bekommen, ohne den Absturz zu arbeiten, und nach this discussion haben andere das gleiche Problem erfahren.

Es sollte jedoch möglich sein, Dokumente (nicht die LibreOffice-Anwendung selbst) mehrmals ohne Absturz zu schließen und erneut zu öffnen.

Zuerst öffnen Sie LibreOffice entweder manuell oder mit einem Shell-Skript wie PowerShell. Dann führe deine Bewerbung aus. Führen Sie mehrere Serienbriefe aus, rufen Sie jedoch nicht xDesktop.terminate() auf. Schließen Sie nach Abschluss der Anwendung LibreOffice manuell oder schließen Sie es mit dem Shell-Skript.

Das Ergebnis: Keine Abstürze! :)

+0

Ich habe versucht, wie Sie gesagt haben, aber in den temporären Ordner noch nicht die Modelle als Basis für den Seriendruck gelöscht werden. – Simone

Verwandte Themen