簡體   English   中英

在C#中執行郵件合並時,LibreOffice崩潰

[英]LibreOffice crash when perform a Mail Merge in C#

我正在使用使用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 64bit and Windows 7 32bit
LibreOffice和SDK版本:5.3.0.3 x86(也已測試5.2.4.2和5.2.5.1 x86)
LibreOffice快速入門:已禁用
Crashreport

在GitHub上完成Visual Studio項目

非常感謝任何可以告訴我我錯在哪里的人。

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

編輯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
  }
}

沒有崩潰我無法使它正常工作,根據這次討論 ,其他人也遇到了同樣的問題。

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

因此,首先手動或使用Shell腳本(如PowerShell)打開LibreOffice。 然后運行您的應用程序。 執行多個郵件合並,但不要調用xDesktop.terminate() 應用程序完成后,手動關閉LibreOffice或使用Shell腳本將其關閉。

結果:沒有崩潰! :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM