Подтвердить что ты не робот

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

У меня проблема с 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 это должна быть работа, но ничего. Как я могу это исправить?

Спасибо.

4b9b3361

Ответ 1

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

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);

Ответ 2

Вот фрагмент кода, который я написал, потому что у меня была такая же проблема, как и у вас. В принципе, вам нужно закрыть книгу, выйти из приложения, а затем освободить ВСЕ ваши 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();
    }

Ответ 3

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

- одна точка

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

Ответ 4

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

excel.Workbooks.Open(...)

excel.Workbooks создает COM-объект. Затем вы вызываете функцию Open из этого COM-объекта. Однако вы не освобождаете COM-объект, когда закончите.

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

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

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

Ответ 5

Сложно избавиться от всех ссылок, поскольку вам нужно угадать, если такие вызовы:

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

Как указано в других ответах, использование двух точек создаст скрытые ссылки, которые не могут быть закрыты 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();
            }
        }
    }
}

Ответ 7

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

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

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

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

Ответ 8

В качестве альтернативы вы можете убить процесс 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);

Ответ 9

У меня была такая же проблема, мы можем решить проблему без какого-либо убийства, мы всегда забываем закрыть интерфейсы, которые мы использовали в классе 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);
        }

Ответ 10

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

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

Если по какой-то причине вам нужно убить процесс Excel App Object 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()
      }
    }

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

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

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