2017-02-10 4 views
2

Je travaille sur une application C# qui effectue une opération de fusion et publipostage à l'aide de LibreOffice.
Je peux effectuer le publipostage et enregistrer le résultat en tant que pdf, mais un plantage se produit après l'appel xDesktop.terminate() et le rapport d'erreur s'affiche la prochaine fois que LibreOffice est ouvert.Crash LibreOffice lors de l'exécution d'une fusion et publipostage en C#

Chaque fois que j'utilise le service com.sun.star.text.MailMerge et que vous fermez LibreOffice, les modèles utilisés comme base de la fusion et publipostage ne sont pas supprimés du dossier temporaire.
Par exemple, les fichiers:
%TEMP%\lu97964g78o.tmp\lu97964g78v.tmp
%TEMP%\lu97964g78o.tmp\SwMM0.odt

Il semble que je ne ferme pas correctement le service MailMerge.


code minimal pour reproduire accident 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(); 
     } 
    } 
    } 
} 


OS: Windows 10 et 64 bits de Windows 7 32bit
la version LibreOffice et SDK: 5.3.0.3 x86 (également testé 5.2.4.2 et 5.2.5.1 x86)
LibreOffice QuickStart: désactivé
Crashreport

Complete Visual Studio project sur GitHub.

Un grand merci à tous ceux qui peuvent me dire où je me trompe.

EDIT: Mettre à jour le code et envoyer un rapport de bogue.

EDIT 2: En espérant faire quelque chose d'utile, je publie une solution de contournement pour le problème décrit ci-dessus.

Fondamentalement, je démarre le processus LibreOffice en passant en paramètre un répertoire dans lequel créer un nouveau profil utilisateur.
Je modifie également le chemin de l'environnement variablile tmp pour que seul le processus LibreOffice pointe vers le répertoire précédent.

Lorsque j'ai terminé le travail, je supprime ce répertoire avec des rapports d'erreur et des fichiers temporaires créés par le bogue de l'API LibreOffice.

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

Il semble qu'il manque au code une commande pour fermer le document. Par exemple 'xCloseable.close (true);' comme ici: https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Closing_Documents. –

+0

@JimK Merci, mais j'avais déjà vu ce lien et j'utilise déjà xCloseable pour fermer le document créé par la fusion et publipostage. J'ai créé un dépôt sur github avec un exemple plus complet du code que j'utilise. La fusion et le publipostage fonctionnent mais se produit toujours le plantage que j'ai mentionné. – Simone

Répondre

0

Je ne peux pas le faire fonctionner sans l'accident, et selon this discussion, d'autres ont connu le même problème.

Cependant, il devrait être possible de fermer et de rouvrir des documents (et non l'application LibreOffice elle-même) plusieurs fois sans se bloquer.

Commencez donc par ouvrir LibreOffice manuellement ou avec un script shell tel que PowerShell. Ensuite, lancez votre application. Effectuez plusieurs fusions, mais n'appelez pas xDesktop.terminate(). Une fois l'application terminée, fermez manuellement LibreOffice ou fermez-le avec le script shell. Le résultat: Pas de pannes! :)

+0

J'ai essayé de faire comme vous l'avez dit mais dans le dossier temporaire ne sont toujours pas supprimés les modèles utilisés comme base de la fusion et publipostage. – Simone