it-swarm.com.ru

Не удается закрыть Excel.exe после процесса взаимодействия

У меня проблема с Excel Interop.

Excel.exe не закрывается, даже если я выпускаю экземпляры.

Вот мой код: 

using xl = Microsoft.Office.Interop.Excel;


xl.Application Excel = new xl.Application();
Excel.Visible = true;
Excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
   //typeExcel become a string of the document name
   string typeExcel = wordFile.ToString();
   xl.Workbook workbook = Excel.Workbooks.Open(typeExcel,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing);
   object outputFileName = null;
   if (wordFile.Contains(".xls"))
   {
     outputFileName = wordFile.Replace(".xls", ".pdf");
   }
   else if (wordFile.Contains(".csv"))
   {
     outputFileName = wordFile.Replace(".csv", ".pdf");
   }

   workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName, 
                                 XlFixedFormatQuality.xlQualityStandard, oMissing,
                                 oMissing, oMissing, oMissing, oMissing, oMissing);

   object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
   ((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);

   Marshal.ReleaseComObject(workbook);
   workbook = null;
}

Я видел, что с Marshal.RealeaseComObject должно работать, но ничего . Как это исправить?

Спасибо.

25
provençal le breton

Простое правило: избегайте использования выражений, вызывающих двойные точки, например:

var workbook = Excel.Workbooks.Open(/*params*/)

... потому что таким образом вы создаете RCW объекты не только для workbook, но и для Workbooks, и вы должны также освободить его (что невозможно, если ссылка на объект не поддерживается).

Итак, правильный путь будет:

var workbooks = Excel.Workbooks;
var workbook = workbooks.Open(/*params*/)

//business logic here

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(Excel);
60
Dzmitry Martavoi

Вот фрагмент кода, который я написал, потому что у меня была та же проблема, что и у вас. По сути, вам нужно закрыть книгу, выйти из приложения, а затем выпустить ВСЕ ваши COM-объекты (не только объект приложения Excel). Наконец, позвоните сборщику мусора для хорошей меры.

    /// <summary>
    /// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
    /// </summary>
    public void Dispose()
    {
        // Cleanup
        xWorkbook.Close(false);
        xApp.Quit();

        // Manual disposal because of COM
        while (Marshal.ReleaseComObject(xApp) != 0) { }
        while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
        while (Marshal.ReleaseComObject(xCharts) != 0) { }
        while (Marshal.ReleaseComObject(xMyChart) != 0) { }
        while (Marshal.ReleaseComObject(xGraph) != 0) { }
        while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
        while (Marshal.ReleaseComObject(xSeries) != 0) { }
        xApp = null;
        xWorkbook = null;
        xWorksheets = null;
        xWorksheet = null;
        xCharts = null;
        xMyChart = null;
        xGraph = null;
        xSeriesColl = null;
        xSeries = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
16
qJake

Правила - никогда не используйте не более одной точки 

- одна точка

var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);

- две или более точек

 (Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);

-- Пример

 using Microsoft.Office.Interop.Excel;

 Application xls = null;
 Workbooks workBooks = null;
 Workbook workBook = null;
 Sheets sheets = null;
 Worksheet workSheet1 = null;
 Worksheet workSheet2 = null;

 workBooks = xls.Workbooks;
 workBook = workBooks.Open(workSpaceFile);
 sheets = workBook.Worksheets;
 workSheet1 = (Worksheet)sheets[1];


// removing from Memory
 if (xls != null)
 {    
   foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
   {
      ReleaseObject(sheet);
   }

   ReleaseObject(sheets);
   workBook.Close();
   ReleaseObject(workBook);
   ReleaseObject(workBooks);

   xls.Application.Quit(); // THIS IS WHAT IS CAUSES Excel TO CLOSE
   xls.Quit();
   ReleaseObject(xls);

   sheets = null;
   workBook = null;
   workBooks = null;
   xls = null;

   GC.Collect();
   GC.WaitForPendingFinalizers();
   GC.Collect();
   GC.WaitForPendingFinalizers();
}
9
Lawrence Thurman

В вашем коде у вас есть:

Excel.Workbooks.Open(...)

Excel.Workbooks создает COM-объект. Затем вы вызываете функцию Open из этого COM-объекта. Однако вы не освобождаете COM-объект после завершения.

Это распространенная проблема при работе с COM-объектами. По сути, вы никогда не должны иметь более одной точки в своем выражении, потому что вам нужно будет очистить объекты COM, когда вы закончите.

Эта тема слишком велика, чтобы ее можно было полностью найти в ответе, но я думаю, что статья Джейка Джиннивана на эту тему окажется чрезвычайно полезной: VSTO и COM Interop

Если вы устали от всех этих вызовов ReleaseComObject, вам может пригодиться этот вопрос:
Как правильно очистить объект взаимодействия Excel в C #, выпуск 2012

7
JDB

Это сложно, чтобы избавиться от всех ссылок, так как вы должны догадаться, если вызовы, как:

var workbook = Excel.Workbooks.Open("")

Создает экземпляр Workbooks, на который у вас нет ссылки.

Даже ссылки, как:

targetRange.Columns.AutoFit()

Создаст экземпляр .Columns() без вашего ведома и не будет выпущен должным образом.

В итоге я написал класс, содержащий список ссылок на объекты, которые могут располагать все объекты в обратном порядке.

Класс имеет список объектов и функций Add() для всего, на что вы ссылаетесь, когда используете взаимодействие с Excel, которое возвращает сам объект: 

    public List<Object> _interopObjectList = new List<Object>();

    public Excel.Application add(Excel.Application obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Range add(Excel.Range obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Workbook add(Excel.Workbook obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheet add(Excel.Worksheet obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheets add(Excel.Worksheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Sheets add(Excel.Sheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }


    public Excel.Workbooks add(Excel.Workbooks obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

Затем для отмены регистрации объектов я использовал следующий код:

    //Release all registered interop objects in reverse order
    public void unregister()
    {
        //Loop object list in reverse order and release Office object
        for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
        { ReleaseComObject(_interopObjectList[i]); }

        //Clear object list
        _interopObjectList.Clear();
    }


    /// <summary>
    /// Release a com interop object 
    /// </summary>
    /// <param name="obj"></param>
     public static void ReleaseComObject(object obj)
     {
         if (obj != null && InteropServices.Marshal.IsComObject(obj))
             try
             {
                 InteropServices.Marshal.FinalReleaseComObject(obj);
             }
             catch { }
             finally
             {
                 obj = null;
             }

         GC.Collect();
         GC.WaitForPendingFinalizers();
         GC.Collect();
         GC.WaitForPendingFinalizers();
     }

Тогда принцип состоит в том, чтобы создать класс и захватить ссылки следующим образом:

//Create helper class
xlsHandler xlObj = new xlsHandler();

..

//Sample - Capture reference to Excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());

..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns() 
xlObj.add(_targetCell.Columns).AutoFit();

..

//Release all objects collected by helper class
xlObj.unregister();

Не возможно, код прекрасной красоты, но может вдохновить на что-то полезное.

6
flodis

Как указано в других ответах, использование две точки создаст скрытые ссылки, которые не могут быть закрыты с помощью Marshal.FinalReleaseComObject. Я просто хотел поделиться своим решением, которое избавляет от необходимости помнить Marshal.FinalReleaseComObject - его действительно легко пропустить, и найти виновника очень сложно.

Я использую универсальный класс-оболочку IDisposable , который можно использовать с любым COM-объектом. Он работает как шарм, и в нем все красиво и чисто. Я даже могу повторно использовать личные поля (например, this.worksheet). Он также автоматически освобождает объект, когда что-то выдает ошибку, из-за природы IDisposable (метод Dispose запускается как finally).

using Microsoft.Office.Interop.Excel;

public class ExcelService
{
    private _Worksheet worksheet;

    private class ComObject<TType> : IDisposable
    {
        public TType Instance { get; set; }

        public ComObject(TType instance)
        {
            this.Instance = instance;
        }

        public void Dispose()
        {
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
        }
    }

    public void CreateExcelFile(string fullFilePath)
    {
        using (var comApplication = new ComObject<Application>(new Application()))
        {
            var excelInstance = comApplication.Instance;
            excelInstance.Visible = false;
            excelInstance.DisplayAlerts = false;

            try
            {
                using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
                using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
                using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
                {
                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Action";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Status";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "ItemPrices";
                        this.worksheet.Activate();

                        using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
                        using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
                        {
                            comRange.Instance.Select();
                            comWindow.Instance.FreezePanes = true;
                        }
                    }

                    if (this.fullFilePath != null)
                    {
                        var currentWorkbook = (workbook.Instance as _Workbook);
                        currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
                        currentWorkbook.Close(false);
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.Message);
                throw;
            }
            finally
            {
                // Close Excel instance
                excelInstance.Quit();
            }
        }
    }
}
2
Heliac

В случае, если вы отчаянно . Не используйте этот подход, если вы не понимаете, что он делает:

foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("Excel"))
{
  proc.Kill();
}

Примечание: это убивает каждый процесс с именем «Excel».

Я должен был сделать это, потому что, несмотря на то, что я закрыл каждый COM-объект в моем коде, у меня все еще был упрямый процесс Excel.exe. Конечно, это ни в коем случае не лучшее решение.

1
Denis Molodtsov

Кроме того, вы можете убить процесс Excel, как объяснено здесь .

Сначала импортируйте функцию SendMessage:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

Затем отправьте сообщение WM_CLOSE в главное окно:

SendMessage((IntPtr)Excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
1
Kevin Vuilleumier

@ Денис Молодцов в попытке быть полезным предложил убить все процессы с именем «Excel». Это, кажется, напрашивается на неприятности. Уже есть много ответов, описывающих способы остановки процесса после вызова Excel.quit () путем воспроизведения Nice с COM-взаимодействием. Это лучше, если вы можете заставить его работать.

@Kevin Vuilleumier было отличное предложение отправить WM_CLOSE в окно Excel. Я планирую проверить это.

Если по какой-то причине вам нужно убить процесс Excel объекта приложения Excel, вы можете специально настроить его, используя что-то вроде этого:

  using System.Diagnostics;
  using System.Runtime.InteropServices;

// . . .

    [DllImport("user32.dll", SetLastError=true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

// . . .

    uint excelAppPid;
    uint tid = GetWindowThreadProcessId(Excel.Hwnd, out excelAppPid);

    if (tid)
    {
      Process excelAppProc = Process.GetProcessById($excelPid)
      if (excelAppProc)
      {
        excelAppProc.Kill()
      }
    }

У меня нет времени на полное тестирование в C #, но я провел быстрый тест в Powershell, где у меня возникла проблема с прекращением работы Excel, и этот подход работает.

Это довольно просто. Свойство Hwnd объекта Excel является дескриптором скрытого окна процесса Excel. Передайте Excel.Hwnd в GetWindowThreadProcessId, чтобы получить идентификатор процесса. Используйте это, чтобы открыть процесс, наконец, вызовите Kill ().

По крайней мере, мы уверены, что убиваем правильный процесс. Ну, почти уверен. Если процесс Excel уже нормально завершен, его идентификатор процесса может быть повторно использован новым процессом. Чтобы ограничить эту возможность, важно не ждать между вызовом Excel.quit () и попыткой уничтожения.

0
jimhark

У меня была та же проблема, мы можем решить проблему без какого-либо убийства, мы всегда забываем закрыть интерфейсы, которые мы использовали в классе Microsoft.Office.Interop.Excel, так что здесь приведен фрагмент кода и следуйте структуре и способу очистки объектов, а также следите за интерфейсом Sheets в вашем коде. Это основная причина, по которой мы часто закрываем приложение, рабочую книгу, рабочие книги, диапазон, лист, но мы забываем или неосознанно не выпускаем объект Sheets или используемый интерфейс, поэтому вот код:

               Microsoft.Office.Interop.Excel.Application app = null;
        Microsoft.Office.Interop.Excel.Workbooks books = null;
        Workbook book = null;
        Sheets sheets = null;
        Worksheet sheet = null;
        Range range = null;

        try
        {
            app = new Microsoft.Office.Interop.Excel.Application();
            books = app.Workbooks;
            book = books.Add();
            sheets = book.Sheets;
            sheet = sheets.Add();
            range = sheet.Range["A1"];
            range.Value = "Lorem Ipsum";
            book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
            book.Close();
            app.Quit();
        }
        finally
        {
            if (range != null) Marshal.ReleaseComObject(range);
            if (sheet != null) Marshal.ReleaseComObject(sheet);
            if (sheets != null) Marshal.ReleaseComObject(sheets);
            if (book != null) Marshal.ReleaseComObject(book);
            if (books != null) Marshal.ReleaseComObject(books);
            if (app != null) Marshal.ReleaseComObject(app);
        }
0
kki