2017-02-10 40 views
2

Я работаю над приложением C#, которое выполняет слияние с помощью LibreOffice.
Я могу выполнить слияние и сохранить результат в формате pdf, но произошел сбой после вызова xDesktop.terminate(), и отчет о сбое появляется в следующий раз, когда открывается LibreOffice.Ошибка LibreOffice при выполнении слияния в C#

Каждый раз, когда я пользуюсь услугой 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 64bit и Windows 7 32bit
LibreOffice и SDK версии: 5.3.0.3 x86 (также проверены 5.2.4.2 и 5.2.5.1 x86)
LibreOffice быстрый запуск: отключено
Crashreport

Complete Visual Studio project на GitHub.

Большое спасибо всем, кто может сказать мне, где я ошибаюсь.

EDIT: Обновить код и отправить отчет об ошибке.

EDIT 2: В надежде сделать что-то полезное, я опубликую обходное решение проблемы, описанной выше.

В принципе, я запускаю процесс LibreOffice, передавая в качестве параметра каталог, в котором создается новый профиль пользователя.
Я также изменяю путь переменной окружения tmp только для процесса LibreOffice, чтобы указать на предыдущий каталог.

Когда я заканчиваю работу, я удаляю этот каталог с отчетами о сбоях и временными файлами, созданными в результате ошибки 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

Похоже, что в коде отсутствует команда для закрытия документа. Например, 'xCloseable.close (true);' как здесь: https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Closing_Documents. –

+0

@JimK Спасибо, но я уже видел эту ссылку, и я уже использую xCloseable, чтобы закрыть документ, созданный слиянием. Я создал репозиторий на github с более полным примером кода, который я использую. Слияние работает, но всегда происходит авария, о которой я упоминал. – Simone

ответ

0

Я не могу заставить его работать без сбоя, и в соответствии с this discussion, другие испытали ту же проблему.

Однако может быть возможно закрыть и повторно открыть документы (а не приложение LibreOffice) несколько раз без сбоев.

Итак, сначала откройте LibreOffice вручную или с помощью сценария оболочки, такого как PowerShell. Затем запустите приложение. Выполняйте множественные слияния почты, но не вызывайте xDesktop.terminate(). После завершения работы приложения вручную закройте LibreOffice или закройте его сценарием оболочки.

Результат: Без сбоев! :)

+0

Я пытался сделать то, что вы сказали, но во временной папке все еще не удаляются модели, используемые в качестве базы слияния. – Simone

 Смежные вопросы

  • Нет связанных вопросов^_^