Как правильно очистить объекты взаимодействия Excel?

Я использую взаимодействие Excel в C # (ApplicationClass) и поместил следующий код в свое предложение finally:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

Хотя это работает, процесс Excel.exe все еще находится в фоновом режиме даже после того, как я закрыл Excel. Он выпускается только после того, как мое приложение закрывается вручную.

Что я делаю не так, или есть альтернатива для правильного удаления объектов взаимодействия?


person HAdes    schedule 01.10.2008    source источник
comment
Вы пытаетесь закрыть Excel.exe, не закрывая приложение? Не уверен, что полностью понимаю ваш вопрос.   -  person Bryant    schedule 01.10.2008
comment
Я пытаюсь убедиться, что неуправляемые объекты взаимодействия удаляются должным образом. Так что процессы Excel не будут зависать, даже когда пользователь закончил работу с электронной таблицей Excel, которую мы создали из приложения.   -  person HAdes    schedule 01.10.2008
comment
Если вы можете попытаться сделать это, создав файлы XML Excel, в противном случае рассмотрите возможность неуправляемого / управляемого управления памятью VSTO: jake.ginnivan.net/vsto-com-interop   -  person Jeremy Thompson    schedule 25.04.2012
comment
Хорошо ли это переводится в Excel?   -  person Coops    schedule 26.11.2012
comment
Ответ ниже. Но в любом случае у меня есть два совета для приведенного выше кода: 1) Вы должны использовать Marshal.FinalReleaseComObject (excelSheet) вместо использования этого цикла while 2) Строка excelSheet = null; не требуется, если excelSheet является локальной переменной   -  person jreichert    schedule 26.09.2014
comment
См. (Помимо ответов ниже) эту статью поддержки от Microsoft, где они конкретно дают решения этой проблемы: support. microsoft.com/kb/317109   -  person Arjan    schedule 24.02.2015


Ответы (42)


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

Я предполагаю, что вы вызываете по крайней мере один член COM-объекта, не назначая его переменной.

Для меня это был объект excelApp.Worksheets, который я использовал напрямую, не назначая его переменной:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

Я не знал, что C # внутренне создал оболочку для COM-объекта Worksheets, который не был выпущен моим кодом (потому что я не знал об этом) и был причиной того, что Excel не был выгружен.

Я нашел решение своей проблемы на этой странице, в котором также есть хорошее правило использования COM-объектов в C #:

Никогда не используйте две точки с COM-объектами.


Итак, обладая этими знаниями, правильный способ сделать это:

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

ПОСЛЕ СМЕРТНОГО ОБНОВЛЕНИЯ

Я хочу, чтобы каждый читатель внимательно прочитал этот ответ Ханса Пассанта, поскольку он объясняет ловушку, в которую я и многие другие разработчики наткнулись. Когда я писал этот ответ много лет назад, я не знал о влиянии отладчика на сборщик мусора и сделал неправильные выводы. Я оставляю свой ответ неизменным ради истории, но, пожалуйста, прочтите эту ссылку и не идите путем "двух точек": Общие сведения о сборке мусора в .NET и Очистить объекты взаимодействия Excel с помощью IDisposable

person VVS    schedule 01.10.2008
comment
К сожалению, я считаю, что эту политику практически невозможно правильно использовать на практике. Если кто-нибудь когда-нибудь проскользнет в любом месте и использует две точки, или перебирает ячейки с помощью цикла for, или любой другой подобной команды, тогда у вас будут объекты COM, на которые нет ссылок, и вы рискуете зависнуть ... - person Mike Rosenblum; 30.10.2008
comment
... и не было бы никакого способа узнать, где в коде у вас была такая ошибка. Вам придется сканировать ВСЕ свой код вручную и надеяться, что вы их все найдете. Для всех, кто читает это, я настоятельно рекомендую прочитать мой полный ответ по этому поводу ниже, а затем решить для себя. - person Mike Rosenblum; 30.10.2008
comment
Тогда я предлагаю не использовать Excel из COM и избавить себя от всех проблем. Форматы Excel 2007 можно использовать, даже не открывая Excel, великолепно. - person user7116; 18.12.2009
comment
Я не понял, что означают две точки. Не могли бы вы объяснить? - person A9S6; 27.01.2010
comment
Это означает, что вам не следует использовать шаблон comObject.Property.PropertiesProperty (вы видите две точки?). Вместо этого назначьте comObject.Property переменной и используйте и удалите эту переменную. Более формальной версией приведенного выше правила может быть sth. например Назначьте объект com переменным, прежде чем использовать их. Сюда входят объекты com, являющиеся свойствами другого объекта com. - person VVS; 04.02.2010
comment
Я не знал, что мне даже пришлось закрыть созданные мной рабочие листы. workheet = null удалил два экземпляра Excel, которые я запустил. Отличная ветка - ценю :) - person sonstabo; 11.02.2010
comment
Поскольку лист является дочерним элементом листов, не будет ли он выпущен первым? - person CtrlDot; 18.08.2010
comment
@Nick: На самом деле, вам не нужна никакая очистка, поскольку сборщик мусора сделает это за вас. Единственное, что вам нужно сделать, это назначить каждый COM-объект его собственной переменной, чтобы GC знал об этом. - person VVS; 18.10.2010
comment
@VSS, это чушь, GC очищает все, поскольку переменные оболочки создаются инфраструктурой .net. Сборщику мусора может потребоваться целая вечность, чтобы его очистить. Вызов GC.Collect после интенсивного взаимодействия - неплохая идея. - person CodingBarfield; 11.08.2011
comment
@I__: Поскольку я не знаю вашего кода, я могу только догадываться, что вы где-то пропустили COM-объект (точку). - person VVS; 03.04.2012
comment
@vvs спасибо за вашу помощь. что означает точка где-то? - person Alex Gordon; 03.04.2012
comment
Вау ... Это может создать довольно длинный код даром. Поэтому я не могу сделать рабочий лист (strrange) .value2. Я должен определить диапазон, ЗАТЕМ присвоить значение, чтобы убедиться, что все в порядке? - person David Brunelle; 13.11.2012
comment
@sixlettervariables, но как вы можете писать в Excel без COM, у вас есть ссылочный URL? - person Coops; 26.11.2012
comment
@sixlettervariables А, написание XML, который кажется немного большим количеством кода для построения XML, но в целом намного проще с точки зрения отсутствия работы с COM-объектами =) - person Coops; 27.11.2012
comment
Я просто громко застонал ОООООООООООООООООООООООО посреди своего офиса. Я никогда толком не понимал этой штуки с двумя точками до сих пор. Спасибо! - person Sandy Gifford; 19.09.2014
comment
Спасибо. У меня была ситуация, когда все советы, казалось, не работали, пока я не попробовал комбинацию крайней осторожности, чтобы избежать двойной точки, с использованием группы If objWhatever IsNot Nothing Then System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objWhatever) (VB) в обратном порядке. Затем я обнаружил, что запуск GC вручную не требуется, поскольку, наконец, EXCEL больше не существует. Надвигающийся последний подход _2 _ / _ 3_ не понадобился. - person AjV Jsy; 08.10.2014
comment
В ответе опечатка или я что-то упустил? Согласно этому ответу на stackoverflow, нам нужно освободить com-объекты в обратном порядке. Разве это не означает, что нам нужно изменить порядок Marshal.ReleaseComObject(sheets); и Marshal.ReleaseComObject(sheet); в ответе? - person user3141326; 10.10.2014
comment
Ханс Пассан не согласен с этим ответом здесь: stackoverflow.com/a/25135685/3852958 Не говоря уже о том, что он прав. Просто сказать, что он не согласен ... и его мнение имеет определенный вес. - person Joe Gayetty; 18.04.2018
comment
Вы должны опубликовать посмертное обновление вверху ответа. Оказывается, решение Ганса Пассанта идеальное. Сделайте реальное решение более заметным. - person Maxter; 28.02.2019
comment
Ответ Ханса действительно правильный, но обратите внимание, что он не применяется ко всем сценариям взаимодействия с COM. Здесь .NET порождает процесс EXCEL.EXE и имеет полный контроль над всем. Если ваш код .NET представляет собой надстройку COM, размещенную внутри процесса редактором Visual Basic, .NET создается COM, и это все меняет. Правило двух точек - это не совсем чушь. - person Mathieu Guindon; 16.07.2019
comment
Правило двух точек - это всегда тупица. Единственное, чего не хватает в ответе - это то, что COM STA - надстройки для офиса - требуют перекачки сообщений. Если вы вызываете их из потоков без перекачки сообщений, тогда есть сценарии, в которых они не могут выйти чисто. - person MikeJ; 18.11.2020
comment
Чтобы подчеркнуть сказанное выше: следуйте за Хансом Пассантом. Игнорируйте правило двух точек. Игнорируйте Marshall.ReleaseComObject. Это слишком сложно сделать правильно. Вместо этого ответ состоит в том, чтобы заставить GC очищать оставшиеся COM-объекты, как писал Ханс. - person Alan Baljeu; 01.07.2021

Фактически вы можете полностью освободить объект приложения Excel, но вы должны позаботиться об этом.

Совет поддерживать именованную ссылку для абсолютно каждого COM-объекта, к которому вы обращаетесь, а затем явно освобождать его через Marshal.FinalReleaseComObject(), является правильным в теории, но, к сожалению, им очень сложно управлять на практике. Если кто-нибудь когда-нибудь проскользнет в любом месте и использует «две точки», или перебирает ячейки с помощью цикла for each, или любой другой подобной команды, тогда у вас будут COM-объекты, на которые нет ссылок, и вы рискуете зависнуть. В этом случае не было бы возможности найти причину в коде; вам придется просмотреть весь свой код на глаз и, надеюсь, найти причину - задача, которая может оказаться практически невыполнимой для большого проекта.

Хорошая новость заключается в том, что на самом деле вам не нужно поддерживать ссылку на именованную переменную для каждого используемого COM-объекта. Вместо этого вызовите GC.Collect(), а затем GC.WaitForPendingFinalizers(), чтобы освободить все (обычно второстепенные) объекты, на которые у вас нет ссылки, а затем явно освободите объекты, на которые вы действительно имеете ссылку на именованную переменную.

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

Например, если у вас есть переменная объекта Range с именем xlRng, переменная рабочего листа с именем xlSheet, переменная Workbook с именем xlBook и переменная приложения Excel с именем xlApp, тогда ваш код очистки может выглядеть примерно так:

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

В большинстве примеров кода, которые вы увидите для очистки COM-объектов из .NET, вызовы GC.Collect() и GC.WaitForPendingFinalizers() выполняются ДВАЖДЫ, как в:

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

Однако этого не требуется, если только вы не используете инструменты Visual Studio для Office (VSTO), в которых используются финализаторы, которые вызывают перемещение всего графа объектов в очередь финализации. Такие объекты не будут освобождены до следующей сборки мусора. Однако, если вы не используете VSTO, вы сможете вызвать GC.Collect() и GC.WaitForPendingFinalizers() только один раз.

Я знаю, что явный вызов GC.Collect() - это запрет (и, конечно, повторение этого дважды звучит очень болезненно), но, честно говоря, нет никакого способа обойти это. Посредством обычных операций вы будете генерировать скрытые объекты, на которые у вас нет ссылок, которые вы, следовательно, не можете освободить никакими другими способами, кроме вызова GC.Collect().

Это сложная тема, но на самом деле это все. После того, как вы установите этот шаблон для своей процедуры очистки, вы можете кодировать как обычно, без использования оберток и т. Д. :-)

У меня есть руководство по этому поводу:

Автоматизация программ Office с помощью VB.Net / COM Interop

Он написан для VB.NET, но не отчаивайтесь, принципы точно такие же, как при использовании C #.

person Community    schedule 01.10.2008
comment
Соответствующее обсуждение можно найти на форуме ExtremeVBTalk .NET Office Automation здесь: xtremevbtalk.com /showthread.php?t=303928. - person Mike Rosenblum; 24.01.2009
comment
А если ничего не помогает, можно использовать Process.Kill () (в крайнем случае), как описано здесь: stackoverflow.com/questions/51462/ - person Mike Rosenblum; 07.02.2009
comment
Рад, что это сработало, Ричард. :-) И вот тонкий пример, когда просто избежать двух точек недостаточно, чтобы предотвратить проблему: stackoverflow.com/questions/4964663/ - person Mike Rosenblum; 13.02.2011
comment
Вот мнение по этому поводу Миши Шнеерсона из Microsoft по этой теме в рамках даты комментария 7 октября 2010 г. (blogs.msdn.com/b/csharpfaq/archive/2010/09/28/) - person Mike Rosenblum; 24.04.2011
comment
Я надеюсь, что это поможет решить нашу постоянную проблему. Безопасно ли выполнять сборку мусора и освобождать вызовы внутри блока finally? - person Brett Green; 19.05.2015
comment
У меня сработало, но только после перемещения GC.Collect() после Marshal.FinalReleaseComObject(xlApp). GC.WaitForPendingFinalizers() было ненужным. - person primo; 01.07.2015

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

Существуют различные способы и советы по выгрузке экземпляра Excel, например:

  • Явное освобождение КАЖДОГО com-объекта с помощью Marshal.FinalReleaseComObject () (не забывая о неявно созданных com-объектах). Чтобы освободить каждый созданный объект com, вы можете использовать правило двух точек, упомянутое здесь:
    Как правильно очистить объекты взаимодействия Excel?

  • Вызов GC.Collect () и GC.WaitForPendingFinalizers (), чтобы заставить CLR освободить неиспользуемые com-объекты * (на самом деле, это работает, подробности см. В моем втором решении)

  • Проверка того, может ли приложение com-server-application показать окно сообщения, ожидающее ответа пользователя (хотя я не уверен, что это может помешать закрытию Excel, но я слышал об этом несколько раз)

  • Отправка сообщения WM_CLOSE в главное окно Excel

  • Выполнение функции, которая работает с Excel, в отдельном домене приложения. Некоторые люди считают, что экземпляр Excel будет закрыт, когда AppDomain будет выгружен.

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

НО! Иногда все эти варианты просто не помогают или не подходят!

Например, вчера я узнал, что в одной из моих функций (которая работает с Excel) Excel продолжает работать после завершения функции. Все перепробовала! Я 10 раз тщательно проверил всю функцию и для всего добавил Marshal.FinalReleaseComObject ()! У меня также были GC.Collect () и GC.WaitForPendingFinalizers (). Я проверил скрытые окна сообщений. Я попытался отправить сообщение WM_CLOSE в главное окно Excel. Я выполнил свою функцию в отдельном домене приложений и выгрузил этот домен. Ничего не помогло! Вариант с закрытием всех экземпляров Excel не подходит, потому что, если пользователь запускает другой экземпляр Excel вручную, во время выполнения моей функции, которая также работает с Excel, этот экземпляр также будет закрыт моей функцией. Бьюсь об заклад, пользователь не обрадуется! Так что, честно говоря, это хромой вариант (без обид, ребята). Итак, я потратил пару часов, прежде чем нашел хорошее (на мой скромный взгляд) решение: Завершить процесс excel с помощью hWnd его главного окна (это первое решение).

Вот простой код:

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if(processID == 0) return false;
    try
    {
        Process.GetProcessById((int)processID).Kill();
    }
    catch (ArgumentException)
    {
        return false;
    }
    catch (Win32Exception)
    {
        return false;
    }
    catch (NotSupportedException)
    {
        return false;
    }
    catch (InvalidOperationException)
    {
        return false;
    }
    return true;
}

/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running). 
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if (processID == 0)
        throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
    Process.GetProcessById((int)processID).Kill();
}

Как видите, я предоставил два метода в соответствии с шаблоном Try-Parse (я думаю, что здесь это уместно): один метод не генерирует исключение, если процесс не может быть уничтожен (например, процесс больше не существует) , а другой метод вызывает исключение, если процесс не был убит. Единственное слабое место в этом коде - разрешения безопасности. Теоретически у пользователя может не быть разрешений на завершение процесса, но в 99,99% всех случаев у пользователя есть такие разрешения. Я также тестировал его с гостевой учетной записью - работает отлично.

Итак, ваш код, работающий с Excel, может выглядеть так:

int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);

Вуаля! Работа Excel прекращена! :)

Хорошо, вернемся ко второму решению, как я и обещал в начале поста. Второе решение - вызвать GC.Collect () и GC.WaitForPendingFinalizers (). Да, они действительно работают, но здесь нужно быть осторожным!
Многие говорят (и я сказал) этот вызов GC.Collect () не помогает. Но причина, по которой это не поможет, заключается в том, что ссылки на COM-объекты все еще существуют! Одна из самых популярных причин бесполезности GC.Collect () - запуск проекта в режиме отладки. В режиме отладки объекты, на которые больше не ссылаются, не будут собираться мусором до конца метода.
Итак, если вы попробовали GC.Collect () и GC.WaitForPendingFinalizers (), и это не помогло, попробуйте сделать следующее:

1) Попробуйте запустить свой проект в режиме выпуска и проверьте, правильно ли закрылся Excel.

2) Оберните метод работы с Excel в отдельный метод. Итак, вместо чего-то вроде этого:

void GenerateWorkbook(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

ты пишешь:

void GenerateWorkbook(...)
{
  try
  {
    GenerateWorkbookInternal(...);
  }
  finally
  {
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

private void GenerateWorkbookInternal(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
  }
}

Теперь Excel закроется =)

person nightcoder    schedule 12.12.2009
comment
печально, что ветка уже настолько старая, что ваш отличный ответ появляется так далеко внизу, что, я думаю, является единственной причиной того, что за него не проголосовали больше раз ... - person chiccodoro; 04.05.2010
comment
@VVS: Абсолютно неверная аналогия. И ваше правило двух точек не всегда работает. Далеко не всегда. - person nightcoder; 24.08.2010
comment
@nightcoder: У вас есть образец? Напоминает мне codinghorror.com/blog/2008/03/ - person VVS; 24.08.2010
comment
Я могу найти образец, но не хочу тратить время и спорить с вами. Многие подтверждают, что ReleaseComObject () не всегда помогает. - person nightcoder; 24.08.2010
comment
Должен признаться, когда я впервые прочитал ваш ответ, я подумал, что это гигантский кладж. Примерно через 6 часов борьбы с этим (все выпущено, у меня нет двойных точек и т. Д.), Я теперь думаю, что ваш ответ гениален. - person Mark; 27.08.2010
comment
Спасибо за это. Пару дней боролся с Excel, который не закрывался, несмотря ни на что. Превосходно. - person BBlake; 19.10.2010
comment
Я не замечаю никаких проблем с использованием GC.Collect() и GC.WaitForPendingFinalizers() в режиме отладки ... если я сделаю только ваш второй шаг (оберните метод, работающий с Excel, отдельным методом), он, похоже, работает нормально. Фактически, Excel красиво исчезает из моего списка процессов диспетчера задач, даже если я никогда не использую Marshal.ReleaseComObject(). Если вы используете какой-либо из ваших методов (KillProcessByMainWindowHwnd или GC.Collect() и GC.WaitForPendingFinalizers() с обернутым методом), разве вручную не освобождать com-объекты совершенно ненужно? - person devuxer; 02.11.2011
comment
DanM, если не убить процесс Excel и не использовать GC.Collect () + GC.WaitForPendingFinalizers, тогда вам все равно нужно вызвать Marshal.ReleaseComObject () и установить null для оболочек COM-объектов (например, объектов книги и т. Д.), Иначе процесс Excel будет не быть закрытым. Если вы убьете процесс Excel, я бы все равно вызвал Marshal.ReleaseComObject () и установил null для оболочек COM-объектов, потому что это поможет быстрее освободить память и это более правильно (например, если вы не вызываете ReleaseComObject (), тогда эти оболочки будут попробуйте получить доступ к несуществующим объектам com, которые уже были уничтожены, когда вы убили процесс Excel). - person nightcoder; 02.11.2011
comment
@nightcoder, спасибо за ответ. Позвольте мне убедиться, что мы говорим об одном и том же. Вот что я пробовал: (1) поместить весь код, имеющий отношение к COM-объектам Excel, в автономный метод (или иерархию методов), (2) создать вызывающий метод, который вызывает автономный метод в блоке try, а затем вызывает GC.Collect() и GC.WaitForPendingFinalizers() в блоке finally. В моем тестировании это каждый раз очищает процесс Excel из диспетчера задач. Шаг 1 гарантирует, что COM-объекты хранятся только в локальных переменных, поэтому после завершения метода не должно быть активных ссылок. - person devuxer; 02.11.2011
comment
@nightcoder (продолжение), Вы, кажется, говорите, что это может быть ненадежно, если я не буду вручную вызывать Marshal.ReleaseComObject() для определенных оболочек COM-объектов. Если GC.Collect() и GC.WaitForPendingFinalizers() действительно недостаточно (опять же, предполагая, что все ссылки на COM-объекты ограничены локальными переменными, которые больше не входят в область видимости), (1) почему? и (2) какие COM-объекты Excel необходимо освободить? В моем тестовом примере я не выпускаю следующие объекты без видимых вредных эффектов: Application, Workbook, Sheets, Worksheet и Range. Спасибо еще раз! - person devuxer; 02.11.2011
comment
@nightcoder: отличный ответ, очень подробный. Ваши комментарии относительно режима отладки очень верны и важны, что вы указали на это. Тот же код в режиме выпуска часто может быть нормальным. Однако Process.Kill хорош только при использовании автоматизации, а не тогда, когда ваша программа работает в Excel, например, как управляемая надстройка COM. - person Mike Rosenblum; 29.12.2011
comment
@DanM: Ваш подход на 100% верен и никогда не подведет. Сохранение всех ваших переменных локальными для метода делает все ссылки недоступными для кода .NET. Это означает, что когда ваш раздел finally вызывает GC.Collect (), финализаторы для всех ваших COM-объектов будут вызываться с уверенностью. Затем каждый финализатор вызывает Marshal.FinalReleaseComObject для финализируемого объекта. Таким образом, ваш подход прост и надежен. Не бойтесь использовать это. (Единственное предостережение: если вы используете VSTO, в чем я сомневаюсь, вам нужно будет вызвать GC.Collect () и GC.WaitForPendingFinalizers ДВАЖДЫ.) - person Mike Rosenblum; 29.12.2011
comment
@MikeRosenblum, спасибо за ответ на мой комментарий. Я ценю заверения. Для конкретного проекта, над которым я работал в то время, я полностью обошел проблему, используя файловый подход (с использованием EPPlus) вместо COM. Однако, если мне действительно понадобится снова использовать COM-взаимодействие, я буду знать, что делать. - person devuxer; 30.12.2011
comment
Я думаю, что это доводит «дырявую абстракцию» до крайности. - person LostSalad; 28.05.2014
comment
Явное уничтожение или отправка WM_CLOSE на внепроцессный COM-сервер (который может обслуживать других COM-клиентов) - ужасная идея. Если вы прибегаете к этому, это потому, что ваш протокол COM сломан. COM-серверы не следует рассматривать как обычное оконное приложение. - person MickyD; 16.03.2015
comment
второй метод очистки не работал бы с Marshal.ReleaseComObject, его заменили на 2xClose () и Quit (), и он сработал! так что парень, который сказал использовать ReleaseComObject, должен кое-что объяснить! - person colin lamarre; 14.10.2020

ОБНОВЛЕНИЕ: добавлен код C # и ссылка на задания Windows.

Некоторое время я пытался решить эту проблему, и в то время XtremeVBTalk был наиболее активным и отзывчивым. Вот ссылка на мою исходную публикацию Чистое закрытие процесса взаимодействия с Excel, даже если вы приложение вылетает. Ниже приводится краткое изложение сообщения и код, скопированный в это сообщение.

  • Закрытие процесса взаимодействия с помощью Application.Quit() и Process.Kill() работает по большей части, но не работает, если приложения катастрофически вылетают. Т.е. если приложение выйдет из строя, процесс Excel по-прежнему будет работать некорректно.
  • Решение состоит в том, чтобы позволить ОС выполнять очистку ваших процессов с помощью Объекты задач Windows с использованием вызовов Win32. Когда ваше основное приложение умирает, связанные процессы (например, Excel) также будут остановлены.

Я обнаружил, что это чистое решение, потому что ОС выполняет реальную работу по очистке. Все, что вам нужно сделать, это зарегистрировать процесс Excel.

Код вакансии Windows

Оборачивает вызовы API Win32 для регистрации процессов взаимодействия.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Примечание о коде конструктора

  • В конструкторе вызывается info.LimitFlags = 0x2000;. 0x2000 - это JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE значение перечисления, и это значение определяется MSDN как:

Вызывает завершение всех процессов, связанных с заданием, при закрытии последнего дескриптора задания.

Дополнительный вызов API Win32 для получения идентификатора процесса (PID)

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

Использование кода

    Excel.Application app = new Excel.ApplicationClass();
    Job job = new Job();
    uint pid = 0;
    Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
    job.AddProcess(Process.GetProcessById((int)pid).Handle);
person joshgo    schedule 20.08.2009
comment
Явное уничтожение внепроцессного COM-сервера (который может обслуживать других COM-клиентов) - ужасная идея. Если вы прибегаете к этому, это потому, что ваш протокол COM сломан. COM-серверы не следует рассматривать как обычное оконное приложение. - person MickyD; 16.03.2015

Это сработало для проекта, над которым я работал:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

Мы узнали, что важно установить для каждой ссылки на COM-объект Excel значение null, когда вы закончите с ним. Это включало ячейки, таблицы и все остальное.

person Philip Fourie    schedule 01.10.2008
comment
выпуск всех объектов com с таким подходом сработал для меня. Больше никаких зависаний процессов! - person sanrns; 05.07.2021

Во-первых, вам никогда не нужно вызывать Marshal.ReleaseComObject(...) или Marshal.FinalReleaseComObject(...) при взаимодействии с Excel. Это запутанный анти-шаблон, но любая информация об этом, в том числе от Microsoft, которая указывает, что вам нужно вручную освободить COM-ссылки из .NET, неверна. Дело в том, что среда выполнения .NET и сборщик мусора правильно отслеживают и очищают COM-ссылки. Для вашего кода это означает, что вы можете удалить весь цикл `while (...) вверху.

Во-вторых, если вы хотите убедиться, что ссылки COM на внепроцессный COM-объект очищаются при завершении вашего процесса (чтобы процесс Excel закрывался), вам необходимо убедиться, что сборщик мусора работает. Вы делаете это правильно с помощью вызовов GC.Collect() и GC.WaitForPendingFinalizers(). Вызов этого дважды безопасен и гарантирует, что циклы тоже будут очищены (хотя я не уверен, что это необходимо, и был бы признателен за пример, который это показывает).

В-третьих, при работе под отладчиком локальные ссылки будут искусственно сохраняться до конца метода (чтобы работала проверка локальных переменных). Таким образом, вызовы GC.Collect() неэффективны для очистки объекта, подобного rng.Cells, одним и тем же методом. Вы должны разделить код, выполняющий COM-взаимодействие из очистки GC, на отдельные методы. (Это было для меня ключевым открытием из одной части ответа, размещенного здесь @nightcoder.)

Таким образом, общая картина будет такой:

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

Есть много ложной информации и путаницы по этой проблеме, включая множество сообщений в MSDN и на Stack Overflow (и особенно этот вопрос!).

Что окончательно убедило меня приглядеться и найти правильный совет, так это сообщение в блоге Marshal.ReleaseComObject Считается опасным вместе с обнаружением проблемы со ссылками, сохраняемыми в отладчике, что сбивало с толку мое предыдущее тестирование.

person Govert    schedule 29.06.2016
comment
Практически ВСЕ ОТВЕТЫ НА ЭТОЙ СТРАНИЦЕ НЕПРАВИЛЬНЫ, ЗА ИСКЛЮЧЕНИЕМ ЭТОГО. Они работают, но слишком много работы. ПРОПУСТИТЕ правило 2 ТОЧЕК. Позвольте GC делать вашу работу за вас. Дополнительное свидетельство из .Net GURU Hans Passant: stackoverflow.com/a/25135685/3852958 - person Alan Baljeu; 13.10.2018
comment
Говерт, какое облегчение найти правильный способ сделать это. Спасибо. - person Dietrich Baumgarten; 28.09.2019

Все, что находится в пространстве имен Excel, необходимо освободить. Период

Вы не можете делать:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

Вы должны делать

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

с последующим освобождением объектов.

person MagicKat    schedule 01.10.2008
comment
Как аобуть xlRange.Interior.Color например. - person HAdes; 01.10.2008
comment
Интерьер должен быть освобожден (он находится в пространстве имен) ... Цвет, с другой стороны, не работает (потому что это из System.Drawing.Color, iirc) - person MagicKat; 01.10.2008
comment
на самом деле Color - это цвет Excel, а не цвет .Net. вы просто проходите длинный. Также рабочие тетради def. должны быть выпущены, рабочие листы ... в меньшей степени. - person Anonymous Type; 03.03.2010
comment
Это неправда, правило «две точки - плохо, одна точка - хорошо» - это ерунда. Кроме того, вам не нужно выпускать рабочие книги, рабочие листы, диапазоны, это задача GC. Единственное, что вы никогда не должны забывать, - это вызвать Quit для одного-единственного объекта Excel.Application. После вызова Quit обнулить объект Excel.Application и вызвать GC.Collect (); GC.WaitForPendingFinalizers (); дважды. - person Dietrich Baumgarten; 28.09.2019

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

Использование:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

Шаблон:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

Ссылка:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

person Edward Wilde    schedule 08.12.2008
comment
да, это хорошо. Я думаю, что этот код можно обновить, теперь, когда доступен FinalReleaseCOMObject. - person Anonymous Type; 03.03.2010
comment
По-прежнему раздражает наличие блока using для каждого объекта. См. Здесь stackoverflow.com/questions/2191489/ для альтернатива. - person Henrik; 27.06.2012

Я не могу поверить, что эта проблема преследовала мир в течение 5 лет .... Если вы создали приложение, вам нужно сначала закрыть его, прежде чем удалять ссылку.

objExcel = new Excel.Application();  
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

при закрытии

objBook.Close(true, Type.Missing, Type.Missing); 
objExcel.Application.Quit();
objExcel.Quit(); 

Когда вы создаете приложение Excel, оно открывает программу Excel в фоновом режиме. Вам нужно дать этой программе Excel команду выйти, прежде чем вы отпустите ссылку, потому что эта программа Excel не является частью вашего прямого контроля. Таким образом, он останется открытым, если ссылка будет освобождена!

Всем хорошего программирования ~~

person Colin    schedule 21.11.2010
comment
Нет, вы этого не сделаете, особенно если вы хотите разрешить взаимодействие пользователя с приложением COM (в данном случае Excel - OP не указывает, предполагается ли взаимодействие с пользователем, но не ссылается на его завершение). Вам просто нужно заставить вызывающего абонента отпустить, чтобы пользователь мог выйти из Excel при закрытии, скажем, одного файла. - person downwitch; 20.03.2015
comment
это отлично сработало для меня - сбой, когда у меня был только objExcel.Quit (), но когда я также сделал objExcel.Application.Quit () заранее, закрылся чисто. - person mcmillab; 17.11.2015

Обычные разработчики, ни одно из ваших решений у меня не сработало, поэтому я решил реализовать новый трюк.

Сначала позвольте указать "Какова наша цель?" => «Не видеть объект excel после нашей работы в диспетчере задач»

Ok. Позвольте не оспаривать и начать разрушать его, но подумайте о том, чтобы не уничтожать другие экземпляры ОС Excel, которые работают параллельно.

Итак, получите список текущих процессоров и выберите PID процессов EXCEL, а затем, как только ваша работа будет выполнена, у нас появится новый гость в списке процессов с уникальным PID, найдите и уничтожьте только этот.

‹Имейте в виду, что любой новый процесс excel во время вашего задания excel будет обнаружен как новый и уничтожен>‹ Лучшее решение - захватить PID нового созданного объекта excel и просто уничтожить его>

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

Это решает мою проблему, надеюсь и вашу.

person Mohsen Afshin    schedule 16.10.2009
comment
.... это что-то вроде подхода следгаммера? - это похоже на то, что я также использую :(, но необходимо изменить. Проблема с использованием этого подхода заключается в том, что часто при открытии excel на машине, на которой он запущен, в левой панели появляется предупреждение, говорящее что-то вроде Excel был закрыт неправильно: не очень элегантно. Я думаю, что предпочтительнее одно из предыдущих предложений в этой теме. - person whytheq; 06.06.2016
comment
Проголосовали против, потому что это уродливый взлом, а не решение. - person Ant_222; 03.12.2020

Конечно, это кажется слишком сложным. По моему опыту, есть всего три ключевых момента, чтобы Excel закрылся должным образом:

1: убедитесь, что не осталось ссылок на созданное вами приложение Excel (в любом случае у вас должна быть только одна; установите для него значение null)

2: позвоните GC.Collect()

3: Excel должен быть закрыт, либо пользователь вручную закроет программу, либо вы вызовете Quit для объекта Excel. (Обратите внимание, что Quit будет работать так же, как если бы пользователь попытался закрыть программу, и отобразит диалоговое окно подтверждения, если есть несохраненные изменения, даже если Excel не отображается. Пользователь может нажать «Отмена», и тогда Excel не будет закрыт .)

1 должно произойти раньше 2, но 3 может произойти в любое время.

Один из способов реализовать это - обернуть объект взаимодействия Excel вашим собственным классом, создать экземпляр взаимодействия в конструкторе и реализовать IDisposable с Dispose, выглядящим примерно так:

if (!mDisposed) {
   mExcel = null;
   GC.Collect();
   mDisposed = true;
}

Это уберет excel со стороны вашей программы. После закрытия Excel (вручную пользователем или при вызове Quit) процесс прекратится. Если программа уже была закрыта, то процесс исчезнет при вызове GC.Collect().

(Я не уверен, насколько это важно, но вам может потребоваться вызов GC.WaitForPendingFinalizers() после вызова GC.Collect(), но не обязательно избавляться от процесса Excel.)

Это работало для меня без проблем в течение многих лет. Имейте в виду, что пока это работает, вам нужно изящно закрыть, чтобы это сработало. Процессы excel.exe по-прежнему будут накапливаться, если вы прервете свою программу до того, как Excel будет очищен (обычно нажатием кнопки «Стоп» во время отладки вашей программы).

person Dave Cousineau    schedule 31.08.2011
comment
Этот ответ работает и намного удобнее, чем принятый ответ. Не беспокойтесь о двух точках с COM-объектами, не используйте Marshal. - person andrew.cuthbert; 26.02.2015

Я традиционно следовал совету, содержащемуся в ответе VVS. Однако, чтобы поддерживать этот ответ в актуальном состоянии с учетом последних опций, я думаю, что все мои будущие проекты будут использовать библиотеку «NetOffice».

NetOffice является полной заменой Office PIA и полностью не зависит от версии. Это набор управляемых оболочек COM, которые могут обрабатывать очистку, которая часто вызывает такие головные боли при работе с Microsoft Office в .NET.

Некоторые ключевые особенности:

  • В основном не зависящие от версии (и функции, зависящие от версии, задокументированы)
  • Никаких зависимостей
  • Нет PIA
  • Без регистрации
  • Нет ВСТО

Я никоим образом не связан с проектом; Я искренне ценю резкое уменьшение головных болей.

person BTownTKD    schedule 28.03.2013
comment
На самом деле это должно быть отмечено как ответ. NetOffice абстрагирует всю эту сложность. - person C. Augusto Proiete; 03.12.2015
comment
Я использую NetOffice в течение значительного количества времени, пишу надстройку для Excel, и она отлично работает. Единственное, что следует учитывать, - это то, что если вы не удаляете использованные объекты явно, он сделает это при выходе из приложения (потому что он все равно отслеживает их). Таким образом, практическое правило NetOffice - всегда использовать шаблон с каждым объектом Excel, таким как ячейка, диапазон, лист и т. Д. - person Stas Ivanov; 10.02.2017

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

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next
person Grimfort    schedule 06.12.2010
comment
И еще одна причина - повторное использование ссылки без предварительного сброса предыдущего значения. - person Grimfort; 06.12.2010
comment
Спасибо. Я также использовал для каждого вашего решения, сработавшего для меня. - person Chad Braun-Duin; 14.01.2011
comment
+1 Спасибо. Кроме того, обратите внимание, что если у вас есть объект для диапазона Excel, и вы хотите изменить этот диапазон в течение всего времени существования объекта, я обнаружил, что мне пришлось выполнить ReleaseComObject перед его переназначением, что делает код немного неаккуратным! - person AjV Jsy; 10.10.2014

Принятый здесь ответ правильный, но также обратите внимание, что следует избегать не только «двухточечных» ссылок, но и объектов, которые извлекаются через индекс. Вам также не нужно ждать, пока вы закончите работу с программой, чтобы очистить эти объекты, лучше всего создать функции, которые будут очищать их, как только вы закончите с ними, когда это возможно. Вот созданная мной функция, которая назначает некоторые свойства объекта Style с именем xlStyleHeader:

public Excel.Style xlStyleHeader = null;

private void CreateHeaderStyle()
{
    Excel.Styles xlStyles = null;
    Excel.Font xlFont = null;
    Excel.Interior xlInterior = null;
    Excel.Borders xlBorders = null;
    Excel.Border xlBorderBottom = null;

    try
    {
        xlStyles = xlWorkbook.Styles;
        xlStyleHeader = xlStyles.Add("Header", Type.Missing);

        // Text Format
        xlStyleHeader.NumberFormat = "@";

        // Bold
        xlFont = xlStyleHeader.Font;
        xlFont.Bold = true;

        // Light Gray Cell Color
        xlInterior = xlStyleHeader.Interior;
        xlInterior.Color = 12632256;

        // Medium Bottom border
        xlBorders = xlStyleHeader.Borders;
        xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
        xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        Release(xlBorderBottom);
        Release(xlBorders);
        Release(xlInterior);
        Release(xlFont);
        Release(xlStyles);
    }
}

private void Release(object obj)
{
    // Errors are ignored per Microsoft's suggestion for this type of function:
    // http://support.microsoft.com/default.aspx/kb/317109
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
    }
    catch { } 
}

Обратите внимание, что мне пришлось установить xlBorders[Excel.XlBordersIndex.xlEdgeBottom] в переменную, чтобы очистить это (не из-за двух точек, которые относятся к перечислению, которое не нужно выпускать, а потому, что объект, о котором я говорю, на самом деле является Border объект, который нужно освободить).

В таких вещах нет необходимости в стандартных приложениях, которые отлично справляются с очисткой после себя, но в приложениях ASP.NET, если вы пропустите хотя бы одно из них, независимо от того, как часто вы вызываете сборщик мусора, Excel будет все еще работает на вашем сервере.

Это требует большого внимания к деталям и выполнения множества тестов при мониторинге диспетчера задач при написании этого кода, но это избавляет вас от хлопот отчаянного поиска по страницам кода, чтобы найти один экземпляр, который вы пропустили. Это особенно важно при работе в циклах, когда вам нужно освободить КАЖДЫЙ ЭКЗЕМПЛЯР объекта, даже если он использует одно и то же имя переменной при каждом цикле.

person Chris McGrath    schedule 17.12.2009
comment
Внутри выпуска проверьте значение Null (ответ Джо). Это позволит избежать ненужных нулевых исключений. Я испытал МНОГО методов. Это единственный способ эффективно выпустить Excel. - person Gerhard Powell; 26.02.2015

После попытки

  1. Освободить COM-объекты в обратном порядке
  2. Добавьте GC.Collect() и GC.WaitForPendingFinalizers() дважды в конце
  3. Не более двух точек
  4. Закройте книгу и закройте приложение
  5. Запуск в режиме выпуска

последнее решение, которое работает для меня, - переместить один набор

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

который мы добавили в конец функции в оболочку, как показано ниже:

private void FunctionWrapper(string sourcePath, string targetPath)
{
    try
    {
        FunctionThatCallsExcel(sourcePath, targetPath);
    }
    finally
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
person D.G.    schedule 18.11.2013
comment
Я обнаружил, что мне не нужно аннулировать ComObjects, только Quit () - Close () и FinalReleaseComObject. Я не могу поверить, что это все, чего мне не хватало, чтобы заставить его работать. Большой! - person Carol; 02.03.2016

Я точно следил за этим ... Но я все равно сталкивался с проблемами 1 раз из 1000. Кто знает почему. Пора достать молоток ...

Сразу после создания экземпляра класса Excel Application я получаю доступ к только что созданному процессу Excel.

excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();

Затем, как только я выполнил всю вышеуказанную очистку COM, я убеждаюсь, что этот процесс не запущен. Если он все еще работает, убейте его!

if (!process.HasExited)
   process.Kill();
person craig.tadlock    schedule 25.02.2014

¨ ° º¤ø „¸ Стреляйте в Excel proc и жуйте жевательную резинку„ ø¤º ° ¨

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}
person Antoine Meltzheim    schedule 14.06.2013

Вы должны знать, что Excel очень чувствителен к культуре, в которой вы работаете.

Вы можете обнаружить, что вам нужно установить язык и региональные параметры EN-US перед вызовом функций Excel. Это относится не ко всем функциям, но к некоторым из них.

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

Это применимо, даже если вы используете VSTO.

Для получения дополнительной информации: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369

person Community    schedule 06.11.2008

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

Один из этих способов - подписаться на любое событие, предоставляемое любым из COM-объектов объектной модели Excel.

Например, подписка на событие WorkbookOpen класса Application.

Немного теории о событиях COM

COM-классы предоставляют группу событий через интерфейсы обратного вызова. Чтобы подписаться на события, клиентский код может просто зарегистрировать объект, реализующий интерфейс обратного вызова, и класс COM будет вызывать свои методы в ответ на определенные события. Поскольку интерфейс обратного вызова является интерфейсом COM, реализующий объект обязан уменьшить счетчик ссылок любого COM-объекта, который он получает (в качестве параметра) для любого из обработчиков событий.

Как Excel PIA предоставляет события COM

Excel PIA представляет события COM класса приложения Excel как обычные события .NET. Каждый раз, когда клиентский код подписывается на событие .NET (выделено «а»), PIA создает экземпляр класса, реализующего интерфейс обратного вызова, и регистрирует его в Excel. .

Следовательно, ряд объектов обратного вызова регистрируется в Excel в ответ на различные запросы подписки из кода .NET. Один объект обратного вызова на подписку на событие.

Интерфейс обратного вызова для обработки событий означает, что PIA должна подписываться на все события интерфейса для каждого запроса подписки на события .NET. Он не может выбирать. При получении обратного вызова события объект обратного вызова проверяет, заинтересован ли связанный обработчик событий .NET в текущем событии или нет, а затем либо вызывает обработчик, либо молча игнорирует обратный вызов.

Влияние на счетчик ссылок на экземпляр COM

Все эти объекты обратного вызова не уменьшают счетчик ссылок любого из COM-объектов, которые они получают (в качестве параметров) для любого из методов обратного вызова (даже для тех, которые молча игнорируются). Они полагаются исключительно на сборщик мусора CLR для освобождения COM-объектов.

Поскольку запуск сборщика мусора не является детерминированным, это может привести к приостановке процесса Excel на более длительный срок, чем требуется, и создать впечатление «утечки памяти».

Решение

Единственное решение на данный момент - избегать поставщика событий PIA для класса COM и писать собственного поставщика событий, который детерминированно освобождает COM-объекты.

Для класса Application это можно сделать, реализовав интерфейс AppEvents, а затем зарегистрировав реализацию в Excel с помощью интерфейс IConnectionPointContainer. Класс Application (и в этом отношении все COM-объекты, отображающие события с помощью механизма обратного вызова) реализует интерфейс IConnectionPointContainer.

person Amit Mittal    schedule 25.06.2012

Как указывали другие, вам необходимо создать явную ссылку для каждого используемого вами объекта Excel и вызвать Marshal.ReleaseComObject для этой ссылки, как описано в эта статья базы знаний. Вам также необходимо использовать try / finally, чтобы гарантировать, что ReleaseComObject всегда вызывается, даже если генерируется исключение. Т.е. вместо того:

Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet

вам нужно сделать что-то вроде:

Worksheets sheets = null;
Worksheet sheet = null
try
{ 
    sheets = excelApp.Worksheets;
    sheet = sheets(1);
    ...
}
finally
{
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
}

Вам также необходимо вызвать Application.Quit перед освобождением объекта Application, если вы хотите закрыть Excel.

Как видите, это быстро становится чрезвычайно громоздким, как только вы пытаетесь сделать что-нибудь, даже умеренно сложное. Я успешно разработал приложения .NET с простым классом-оболочкой, который включает в себя несколько простых манипуляций с объектной моделью Excel (открытие книги, запись в диапазон, сохранение / закрытие книги и т. Д.). Класс-оболочка реализует IDisposable, тщательно реализует Marshal.ReleaseComObject для каждого используемого объекта и не предоставляет публично какие-либо объекты Excel для остальной части приложения.

Но этот подход плохо масштабируется для более сложных требований.

Это большой недостаток .NET COM Interop. Для более сложных сценариев я бы серьезно подумал о написании ActiveX DLL на VB6 или другом неуправляемом языке, которому вы можете делегировать все взаимодействие с внепроцессными COM-объектами, такими как Office. Затем вы можете ссылаться на эту ActiveX DLL из своего .NET-приложения, и все будет намного проще, поскольку вам нужно будет выпустить только эту ссылку.

person Joe    schedule 01.10.2008

Когда все вышеперечисленное не сработало, попробуйте дать Excel немного времени, чтобы закрыть свои листы:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);
person spiderman    schedule 18.06.2011
comment
Ненавижу произвольное ожидание. Но +1, потому что вы правы в том, что нужно дождаться закрытия книг. Альтернативой является опрос коллекции книг в цикле и использование произвольного ожидания в качестве тайм-аута цикла. - person dFlat; 14.07.2011

Убедитесь, что вы освободили все объекты, относящиеся к Excel!

Я потратил несколько часов, пробуя разные способы. Все отличные идеи, но я наконец нашел свою ошибку: Если вы не освободите все объекты, ни один из вышеперечисленных способов вам не поможет, как в моем случае. Убедитесь, что вы освободили все объекты, включая первый диапазон!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

Параметры указаны вместе здесь.

person Ned    schedule 08.08.2012
comment
Очень хороший момент! Я думаю, что, поскольку я использую Office 2007, от моего имени была произведена некоторая очистка. Я воспользовался советом, но не сохранил переменные, как вы предложили здесь, и EXCEL.EXE завершает работу, но мне может просто повезти, и если у меня возникнут дальнейшие проблемы, я обязательно посмотрю на эту часть моего кода =) - person Coops; 26.11.2012

Отличная статья о выпуске COM-объектов: 2.5 Освобождение COM-объектов (MSDN).

Я бы рекомендовал обнулить ваши ссылки Excel.Interop, если они не являются локальными переменными, а затем дважды вызвать GC.Collect() и GC.WaitForPendingFinalizers(). Переменные взаимодействия с локальной областью видимости будут обработаны автоматически.

Это устраняет необходимость хранить именованную ссылку для каждого COM-объекта.

Вот пример из статьи:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

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

Эти слова взяты прямо из статьи:

Практически во всех ситуациях обнуление ссылки RCW и принудительная сборка мусора будут очищены должным образом. Если вы также вызовете GC.WaitForPendingFinalizers, сборка мусора будет максимально детерминированной. То есть вы точно будете уверены, когда объект будет очищен - после второго вызова WaitForPendingFinalizers. В качестве альтернативы вы можете использовать Marshal.ReleaseComObject. Однако учтите, что вам вряд ли когда-либо понадобится использовать этот метод.

person Community    schedule 11.01.2013

Правило двух точек у меня не сработало. В моем случае я создал метод очистки своих ресурсов следующим образом:

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}
person Hahnemann    schedule 12.12.2013

Мое решение

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}
person Loart    schedule 18.06.2014

Вы должны быть очень осторожны при использовании приложений взаимодействия Word / Excel. После опробования всех решений у нас все еще оставалось много открытых процессов WinWord на сервере (с более чем 2000 пользователями).

После нескольких часов работы над проблемой я понял, что если я открою более пары документов, используя Word.ApplicationClass.Document.Open() в разных потоках одновременно, рабочий процесс IIS (w3wp.exe) выйдет из строя, а все процессы WinWord останутся открытыми!

Так что я полагаю, что нет абсолютного решения этой проблемы, кроме перехода на другие методы, такие как Office Open XML развитие.

person Arvand    schedule 14.03.2012

Принятый ответ у меня не сработал. Следующий код в деструкторе выполнил свою работу.

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
person Martin    schedule 05.09.2014
comment
social.msdn.microsoft.com/Forums/en-US/ - person Martin; 05.09.2014

В настоящее время я работаю над автоматизацией Office и наткнулся на решение, которое каждый раз работает для меня. Это просто и не требует остановки каких-либо процессов.

Кажется, что, просто перебирая текущие активные процессы и каким-либо образом «обращаясь» к открытому процессу Excel, любой случайный зависший экземпляр Excel будет удален. Приведенный ниже код просто проверяет процессы с именем «Excel», а затем записывает свойство MainWindowTitle процесса в строку. Это «взаимодействие» с процессом, кажется, заставляет Windows догнать и прервать замороженный экземпляр Excel.

Я запускаю приведенный ниже метод непосредственно перед завершением работы надстройки, которую я разрабатываю, поскольку она запускает событие выгрузки. Он каждый раз удаляет все зависшие экземпляры Excel. Честно говоря, я не совсем уверен, почему это работает, но для меня он работает хорошо и может быть помещен в конец любого приложения Excel, не беспокоясь о двойных точках, Marshal.ReleaseComObject или убийственных процессах. Мне было бы очень интересно узнать, почему это эффективно.

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}
person Tom Brearley    schedule 02.08.2016

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

Кроме того, и, возможно, я слишком упрощаю, но я думаю, вы можете просто ...

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

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

person bill_the_loser    schedule 01.10.2008

Как некоторые, вероятно, уже писали, важно не только то, как вы закрываете Excel (объект); также важно, как вы его открываете, а также по типу проекта.

В приложении WPF в основном один и тот же код работает без проблем или с очень небольшим количеством проблем.

У меня есть проект, в котором один и тот же файл Excel обрабатывается несколько раз для разных значений параметра, например. анализируя его на основе значений внутри общего списка.

Я поместил все функции, связанные с Excel, в базовый класс, а парсер - в подкласс (разные парсеры используют общие функции Excel). Я не хотел, чтобы Excel открывался и закрывался снова для каждого элемента в общем списке, поэтому я открыл его только один раз в базовом классе и закрыл в подклассе. У меня возникли проблемы при переносе кода в настольное приложение. Я пробовал многие из вышеупомянутых решений. GC.Collect() уже был реализован ранее, дважды, как предлагалось.

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

Суть в следующем: вы также должны проверить код инициализации, особенно если у вас много классов и т. Д.

person Blaz Brencic    schedule 11.04.2013

«Конечно, это кажется слишком сложным. По моему опыту, есть всего три ключевых момента, чтобы Excel закрылся должным образом:

1: убедитесь, что не осталось ссылок на созданное вами приложение Excel (в любом случае у вас должна быть только одна; установите для него значение null)

2: вызовите GC.Collect ()

3: Excel должен быть закрыт либо пользователем вручную, закрывающим программу, либо путем вызова Quit для объекта Excel. (Обратите внимание, что «Выход» будет работать так же, как если бы пользователь попытался закрыть программу, и отобразит диалоговое окно подтверждения, если есть несохраненные изменения, даже если Excel не отображается. Пользователь может нажать «Отмена», и тогда Excel не будет закрыт .)

1 должно произойти раньше 2, но 3 может произойти в любое время.

Один из способов реализовать это - обернуть объект взаимодействия Excel вашим собственным классом, создать экземпляр взаимодействия в конструкторе и реализовать IDisposable с Dispose, выглядящим примерно так:

Это уберет excel со стороны вашей программы. После закрытия Excel (вручную пользователем или вызовом Quit) процесс прекратится. Если программа уже была закрыта, то процесс исчезнет при вызове GC.Collect ().

(Я не уверен, насколько это важно, но вам может потребоваться вызов GC.WaitForPendingFinalizers () после вызова GC.Collect (), но не обязательно избавляться от процесса Excel.)

Это работало для меня без проблем в течение многих лет. Однако имейте в виду, что, пока это работает, вам нужно изящно закрыть, чтобы это сработало. Вы по-прежнему будете накапливать процессы excel.exe, если прервите свою программу до того, как Excel будет очищен (обычно нажатием кнопки «Стоп» во время отладки вашей программы) ».

person Бурда Евгений    schedule 10.03.2017

Вот действительно простой способ сделать это:

[DllImport("User32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
...

int objExcelProcessId = 0;

Excel.Application objExcel = new Excel.Application();

GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);

Process.GetProcessById(objExcelProcessId).Kill();
person tjarrett    schedule 02.04.2019

Мой ответ запоздал, и его единственная цель - поддержать решение, предложенное Говертом, Поркбаттсом и Дэйвом Кузино, полным примером. Автоматизация Excel или других COM-объектов из COM-независимого мира .NET - это «крепкий орешек», как мы говорим по-немецки, и вы можете легко сходить с ума. Я полагаюсь на следующие шаги:

  1. Для каждого взаимодействия с Excel получите один и только один локальный экземпляр ExcelApp интерфейса приложения и создайте область, в которой живет ExcelApp. Это необходимо, поскольку среда CLR не освобождает ресурсы Excel до того, как любая ссылка на Excel выйдет за рамки. В фоновом режиме запускается новый процесс Excel.

  2. Реализуйте функции, которые выполняют задачи, используя ExcelApp для создания через свойства Collection новых объектов, таких как Workbook (s), Worksheet (s) и Cell (s). В этих функциях не обращайте внимания на правило вуду: одна точка - хорошо, две точки - плохо, не пытайтесь получить ссылку для каждого неявно созданного объекта и не Marshall.ReleaseComObject ничего. Это работа Сборщика мусора.

  3. В рамках ExcelApp вызовите эти функции и передайте ссылку ExcelApp.

  4. Пока ваш экземпляр Excel загружен, не допускайте никаких действий пользователя, которые могли бы обойти Quit функцию, повторно выгружающую этот экземпляр.

  5. Когда вы закончите работу с Excel, вызовите отдельную функцию Quit в области, предназначенной для работы с Excel. Это должно быть последнее утверждение в этой области.

Прежде чем запускать мое приложение, откройте диспетчер задач и посмотрите на вкладке «Процессы» записи в фоновых процессах. Когда вы запускаете программу, новая запись процесса Excel появляется в списке и остается там до тех пор, пока поток снова не проснется через 5 секунд. После этого будет вызвана функция выхода, останавливающая процесс Excel, который постепенно исчезает из списка фоновых процессов.

using System;
using System.Threading;
using Excel = Microsoft.Office.Interop.Excel;

namespace GCTestOnOffice
{
  class Program
  {
    //Don't: private  static Excel.Application ExcelApp = new Excel.Application();

    private static void DoSomething(Excel.Application ExcelApp)
    {
        Excel.Workbook Wb = ExcelApp.Workbooks.Open(@"D:\Aktuell\SampleWorkbook.xlsx");
        Excel.Worksheet NewWs = Wb.Worksheets.Add();

        for (int i = 1; i < 10; i++)
        {
            NewWs.Cells[i, 1] = i;
        }
        Wb.Save();
    }

    public static void Quit(Excel.Application ExcelApp)
    {
        if (ExcelApp != null)
        {
            ExcelApp.Quit(); //Don't forget!!!!!
            ExcelApp = null;
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    static void Main(string[] args)
    {
      {
        Excel.Application ExcelApp = new Excel.Application();
        Thread.Sleep(5000);
        DoSomething(ExcelApp);
        Quit(ExcelApp);
        //ExcelApp goes out of scope, the CLR can and will(!) release Excel
      }

      Console.WriteLine("Input a digit: ");
      int k = Console.Read(); 
    }
  }
}

Если я изменил функцию Main на

static void Main(string[] args)
{
  Excel.Application ExcelApp = new Excel.Application();
  DoSomething(ExcelApp);

  Console.WriteLine("Input a digit: ");
  int k = Console.Read(); 
  Quit(ExcelApp);
}

пользователь мог вместо ввода числа нажать кнопку «Закрыть» на консоли, и мой экземпляр Excel жил долго и счастливо. Таким образом, в тех случаях, когда ваш экземпляр Excel остается постоянно загруженным, ваша функция очистки может быть исправной, но ее обходят непредвиденные действия пользователя.

Если класс Program будет иметь член для экземпляра Excel, среда CLR не выгружает экземпляр Excel до завершения работы приложения. Вот почему я предпочитаю локальные ссылки, которые выходят за рамки, когда они больше не нужны.

person Dietrich Baumgarten    schedule 29.09.2019

Просто чтобы добавить еще одно решение к множеству перечисленных здесь, используя автоматизацию C ++ / ATL (я полагаю, вы могли бы использовать что-то подобное из VB / C # ??)

Excel::_ApplicationPtr pXL = ...
  :
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;

Для меня это действует как шарм ...

person dicksters    schedule 07.04.2016

Использовать:

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Объявить, добавить код в блок finally:

finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (excelApp != null)
    {
        excelApp.Quit();
        int hWnd = excelApp.Application.Hwnd;
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        Process[] procs = Process.GetProcessesByName("EXCEL");
        foreach (Process p in procs)
        {
            if (p.Id == processID)
                p.Kill();
        }
        Marshal.FinalReleaseComObject(excelApp);
    }
}
person Shivam Srivastava    schedule 27.08.2013

Пока что все ответы включают некоторые из них:

  1. Убить процесс
  2. Используйте GC.Collect ()
  3. Следите за каждым COM-объектом и выпускайте его должным образом.

Что заставляет меня понять, насколько сложна эта проблема :)

Я работал над библиотекой, чтобы упростить доступ к Excel, и я пытаюсь убедиться, что люди, использующие ее, не оставят беспорядка (скрещенные пальцы).

Вместо того, чтобы писать прямо на интерфейсах, которые предоставляет Interop, я делаю методы расширения, чтобы облегчить жизнь. Как ApplicationHelpers.CreateExcel () или workbook.CreateWorksheet ("mySheetNameThatWillBeValidated"). Естественно, все, что создано, может привести к проблемам позже при очистке, поэтому я на самом деле предпочитаю убивать процесс в крайнем случае. Тем не менее, правильная уборка (третий вариант), вероятно, наименее разрушительна и наиболее контролируема.

Итак, в этом контексте мне было интересно, не лучше ли было бы сделать что-то вроде этого:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

Я использовал Releasible, чтобы не путать с Disposable. Однако распространить это на IDisposable должно быть легко.

Такая реализация:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

И можно было бы сделать что-то подобное для всех типов COM-объектов.

Заводским способом:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var excel = new ApplicationContainer().Releasible;
        excel.Visible = !hidden;

        return excel;
    }

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

Комментарии? Или это ответ на вопрос третьего типа?

person akirakonenu    schedule 04.05.2015

Вот у меня есть идея, попробуйте убить процесс Excel, который вы открыли:

  1. перед открытием приложения Excel получите все идентификаторы процессов с именем oldProcessIds.
  2. откройте приложение Excel.
  3. получить все идентификаторы процессов excelapplication с именем nowProcessIds.
  4. когда нужно выйти, уничтожьте идентификаторы исключений между oldProcessIds и nowProcessIds.

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    
person Aloha    schedule 19.12.2018

Протестировано с Microsoft Excel 2016

Действительно проверенное решение.

Для справки по C # см .: https://stackoverflow.com/a/1307180/10442623

Ссылку на VB.net см .: https://stackoverflow.com/a/54044646/10442623

1 включить классную работу

2 реализовать класс для обработки соответствующего удаления процесса Excel

person SamSar    schedule 04.01.2019

У меня была такая же проблема с закрытием PowerPoint после создания объекта Application в моем надстройке VSTO. Я попробовал все ответы здесь с ограниченным успехом.

Это решение, которое я нашел для своего случая - НЕ используйте «новое приложение», базовый класс AddInBase для ThisAddIn уже имеет дескриптор для «Приложения». Если вы используете этот дескриптор там, где он вам нужен (сделайте его статическим, если нужно), вам не нужно беспокоиться об его очистке, и PowerPoint не будет зависать рядом.

person swax    schedule 02.07.2019

Из трех общих стратегий, рассмотренных в других ответах, убийство процесса excel явно является взломом, тогда как вызов сборщика мусора - это жестокий подход, предназначенный для компенсации неправильного освобождения COM -объектов. После множества экспериментов и переписывания управления объектами COM в моей не зависящей от версии оболочке и оболочке с поздним связыванием я пришел к выводу, что точные и своевременные вызовы Marshal.ReleaseComObject() являются наиболее эффективной и элегантной стратегией. И нет, вам никогда не понадобится FinalReleaseComObject(), потому что в хорошо написанной программе каждый COM получает один раз и, следовательно, требует единственного уменьшения счетчика ссылок.

Обязательно освободите каждый объект COM, желательно сразу после того, как он больше не нужен. Но вполне возможно освободить все сразу после выхода из приложения Excel только за счет более высокого использования памяти. Excel закроется, как и ожидалось, пока вы не потеряете или не забудете освободить объект COM.

Самая простая и очевидная помощь в этом процессе - это упаковка каждого объекта взаимодействия в класс .NET, реализующий IDisposable, где метод Dispose() вызывает ReleaseComObject() для своего объекта взаимодействия. Выполнение этого в деструкторе, как предлагается здесь, не имеет смысла, поскольку деструкторы недетерминированы.

Ниже показан метод нашей оболочки, который получает ячейку из WorkSheet, минуя промежуточный член Cells. Обратите внимание на то, как он удаляет промежуточный объект после использования:

public ExcelRange XCell( int row, int col)
{   ExcelRange anchor, res;
    using( anchor = Range( "A1") )
    {   res = anchor.Offset( row - 1, col - 1 );  }
    return res;
}

Следующим шагом может быть простой диспетчер памяти, который будет отслеживать каждый полученный объект COM и обязательно выпускать его после выхода Excel, если пользователь предпочитает торговать некоторым использованием ОЗУ. для более простого кода.

Дальнейшее чтение

  1. Как правильно освободить COM-объекты Excel,
  2. Освобождение COM-объектов: сборщик мусора против Marshal.RelseaseComObject.
person Ant_222    schedule 02.12.2020

Excel не предназначен для программирования на C ++ или C #. COM API специально разработан для работы с Visual Basic, VB.NET и VBA.

Кроме того, все примеры кода на этой странице не являются оптимальными по той простой причине, что каждый вызов должен пересекать управляемую / неуправляемую границу и дополнительно игнорировать тот факт, что API Excel COM может сбой любого вызова с загадочным HRESULT, указывающий, что сервер RPC занят.

На мой взгляд, лучший способ автоматизировать Excel - это собрать ваши данные в как можно больший массив и отправить их в функцию или подпрограмму VBA (через Application.Run), которая затем выполняет любую необходимую обработку. Кроме того, при вызове Application.Run обязательно следите за исключениями, указывающими, что excel занят, и повторите вызов Application.Run.

person quixver    schedule 19.06.2011
comment
C # и VB.NET работают под CLR, поэтому он не может быть разработан для VB.NET, а не C #. В конечном итоге это одно и то же, только с другой семантикой языка для создания приложения. - person Dominic Zukiewicz; 02.05.2012
comment
Попробуйте вызвать метод Run из объекта Excel Application в C # (версии 3.0 и более ранней) или C ++. Затем попробуйте сделать то же самое с VB.net. После пропущенного 10-го или 12-го параметра вы, вероятно, поймете, что я имел в виду:] - person quixver; 17.05.2012
comment
Кроме того, как вы упомянули, C # и VB.net являются языками CLR. Они предлагают программистам различные подмножества функций CLR вместе с различным синтаксическим сахаром. Так уж получилось, что подмножество, предлагаемое VB.net, упрощает программирование на com и excel. - person quixver; 17.05.2012
comment
Но опять же, это всего лишь семантика языка (до v3.5). Я действительно чувствую боль от Application.Run(Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,,Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);, которая была у VB даже до .NET. Управляемые библиотеки Microsoft.Office.Tools DLL позволяют легко делать все это без какой-либо очистки COM, поскольку это вся управляемая оболочка. - person Dominic Zukiewicz; 17.05.2012
comment
Excel is not designed to be programmed via C++ or C# - неверная информация. - person Jeremy Thompson; 27.09.2012
comment
@ Джереми, это правда. Он в первую очередь предназначен для программирования через клиентов с поздним связыванием, таких как VBA и VB. Использование C ++ или C # для работы с Excel - кошмар. - person quixver; 19.01.2013
comment
Можно согласиться, но с этим можно не согласиться, ИМХО это далеко не кошмар. Использование C # .Net 4.0 с поддержкой необязательных параметров практически то же самое, что и VB.Net. По моему опыту, все больше компаний переносят старый код VBA на C #, а не на VB.Net. - person Jeremy Thompson; 20.01.2013

Это единственный способ, который действительно работает для меня

        foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
        {
            proc.Kill();
        }
person Hermes Monteiro    schedule 14.02.2017