Я использую взаимодействие Excel в C# (ApplicationClass) и поместил следующий код в свое предложение finally:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Хотя этот вид работает, процесс Excel.exe все еще находится в фоновом режиме даже после того, как я закрыл Excel. Он выпускается только после того, как мое приложение закрывается вручную.
Что я делаю не так, или есть альтернатива, чтобы гарантировать правильное удаление объектов взаимодействия?
Я пытаюсь убедиться, что неуправляемые объекты взаимодействия удаляются должным образом. Так что процессы Excel не будут зависать, даже когда пользователь закончил работу с электронной таблицей Excel, которую мы создали из приложения.
Если вы можете попытаться сделать это, создав файлы XML Excel, в противном случае рассмотрите возможность неуправляемого / неуправляемого управления памятью VSTO: jake.ginnivan.net/vsto-com-interop
Хорошо ли это переводится в Excel?
Ответ ниже. Но в любом случае у меня есть два совета для приведенного выше кода: 1) Вы должны использовать Marshal.FinalReleaseComObject (excelSheet) вместо использования этого цикла while 2) Строка «excelSheet = null;» не требуется, если "excelSheet" является локальной переменной
См. (Помимо ответов ниже) эту статью поддержки от Microsoft, где они конкретно дают решения этой проблемы: support.microsoft.com/kb/317109





Это сработало для проекта, над которым я работал:
excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;
Мы узнали, что важно установить для ссылки каждый на COM-объект Excel значение null, когда вы закончите с ним. Это включало ячейки, таблицы и все остальное.
Excel не закрывается, потому что ваше приложение все еще содержит ссылки на COM-объекты.
Я предполагаю, что вы вызываете по крайней мере один член COM-объекта, не назначая его переменной.
Для меня это был объект excelApp.Worksheets, который я использовал напрямую, не назначая его переменной:
Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);
Я не знал, что внутри C# была создана оболочка для COM-объекта Рабочие листы, который не был выпущен моим кодом (потому что я не знал об этом) и был причиной того, что Excel не был выгружен.
Я нашел решение своей проблемы в эта страница, в котором также есть хорошее правило использования COM-объектов в C#:
Never use two dots with COM objects.
Итак, обладая этими знаниями, правильный способ сделать это:
Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
ПОСЛЕ СМЕРТНОГО ОБНОВЛЕНИЯ:
Я хочу, чтобы каждый читатель внимательно прочитал этот ответ Ханса Пассанта, поскольку он объясняет ловушку, в которую я и многие другие разработчики наткнулись. Когда я писал этот ответ много лет назад, я не знал о влиянии отладчика на сборщик мусора и сделал неправильные выводы. Я оставляю свой ответ неизменным ради истории, но, пожалуйста, прочтите эту ссылку, и не идет путем "двух точек": Понимание сборки мусора в .NET и Очистите объекты взаимодействия Excel с помощью IDisposable
К сожалению, я считаю, что эту политику практически невозможно правильно использовать на практике. Если кто-нибудь когда-нибудь проскользнет в любом месте и использует «две точки», или перебирает ячейки с помощью цикла для каждого цикла, или любой другой подобный вид команды, тогда у вас будут COM-объекты без ссылок и риск зависнуть ...
... и не было бы никакого способа узнать, где в коде у вас была такая ошибка. Вам придется вручную сканировать ВСЕ свой код и надеяться, что вы найдете их все. Для всех, кто читает это, я настоятельно рекомендую прочитать мой полный ответ по этому поводу ниже, а затем решить для себя.
Тогда я предлагаю не использовать Excel из COM и избавить себя от всех проблем. Форматы Excel 2007 можно использовать, даже не открывая Excel, великолепно.
Я не понял, что означают «две точки». Не могли бы вы объяснить?
Это означает, что вам не следует использовать шаблон comObject.Property.PropertiesProperty (вы видите две точки?). Вместо этого назначьте comObject.Property переменной и используйте и удалите эту переменную. Более формальной версией приведенного выше правила может быть sth. например: «Назначьте com-объект переменным перед их использованием. Это включает в себя com-объекты, которые являются свойствами другого com-объекта».
Я не знал, что мне даже пришлось закрыть созданные мной рабочие листы. workheet = null удалил два экземпляра Excel, которые я запустил. Отличная ветка - ценю :)
Поскольку лист является дочерним элементом листов, не будет ли он выпущен первым?
@Nick: На самом деле, вам не нужна никакая очистка, поскольку сборщик мусора сделает это за вас. Единственное, что вам нужно сделать, это присвоить каждому COM-объекту его собственную переменную, чтобы GC знал об этом.
@VSS, это чушь, GC очищает все, поскольку переменные оболочки создаются инфраструктурой .net. Сборщику мусора может потребоваться целая вечность, чтобы его очистить. Вызов GC.Collect после интенсивного взаимодействия - неплохая идея.
@I__: Поскольку я не знаю вашего кода, я могу только догадываться, что вы где-то пропустили COM-объект (точку).
@vvs спасибо за вашу помощь. что означает точка где-то?
Вау ... Это может создать довольно длинный код даром. Поэтому я не могу сделать рабочий лист (strrange) .value2. Я должен определить диапазон, ЗАТЕМ присвоить значение, чтобы убедиться, что все в порядке?
@sixlettervariables, но как вы можете писать в Excel без COM, у вас есть ссылочный URL?
@sixlettervariables А, написание XML, который кажется немного большим количеством кода для построения XML, но в целом намного проще с точки зрения отсутствия работы с COM-объектами =)
Я просто громко застонал "ОООООООООООООООООООООООУ" посреди своего офиса. Я никогда толком не понимал этой штуки с двумя точками до сих пор. Спасибо!
Спасибо. У меня была ситуация, когда все советы, казалось, не работали, пока я не попробовал комбинацию осторожности очень сильно, чтобы избежать «двойной точки», с использованием группы If objWhatever IsNot Nothing Then System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objWhatever) (VB) в обратном порядке. Затем я обнаружил, что запуск GC вручную не требуется, поскольку, наконец, EXCEL больше не существует. Надвигающийся последний подход Dim NewestExcelProcess = Process.GetProcessesByName("EXCEL").OrderByDescending(Function(p) p.StartTime).First() / NewestExcelProcess.Kill() не нужен.
В ответе опечатка или я что-то упустил? Согласно этот ответ stackoverflow нам нужно освободить com-объекты в обратном порядке. Разве это не означает, что нам нужно поменять местами Marshal.ReleaseComObject(sheets); и Marshal.ReleaseComObject(sheet); в ответе?
Ханс Пассан не согласен с этим ответом здесь: stackoverflow.com/a/25135685/3852958 Не говоря уже о том, что он прав. Просто сказать, что он не согласен ... и его мнение имеет определенный вес.
Вы должны опубликовать посмертное обновление в верхней части ответа. Оказывается, решение Ганса Пассанта идеальное. Сделайте реальное решение более заметным.
Ответ Ханса действительно правильный, но обратите внимание, что он не применяется ко всем сценариям взаимодействия с COM. Здесь .NET порождает процесс EXCEL.EXE и имеет полный контроль над всем. Если ваш код .NET представляет собой надстройку COM, размещенную внутри процесса редактором Visual Basic, .NET создается COM, и это все меняет. Правило «двух точек» - не совсем чушь.
Правило «двух точек» - это всегда тупица. Единственное, чего не хватает в ответе - это то, что COM STA - надстройки для офиса - требуют перекачки сообщений. Если вы вызываете их из потоков без перекачки сообщений, тогда есть сценарии, в которых они не могут выйти чисто.
Все, что находится в пространстве имен Excel, должно быть освобождено. Период
Вы не можете делать:
Worksheet ws = excel.WorkBooks[1].WorkSheets[1];
Вы должны делать
Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];
с последующим освобождением объектов.
Как насчет xlRange.Interior.Color например.
Интерьер должен быть освобожден (он находится в пространстве имен) ... Цвет, с другой стороны, не работает (потому что это из System.Drawing.Color, iirc)
на самом деле Color - это цвет Excel, а не цвет .Net. вы просто проходите длинный. Также рабочие тетради def. должны быть выпущены, рабочие листы ... в меньшей степени.
Это неправда, правило «две точки - плохо, одна точка - хорошо» - это ерунда. Кроме того, вам не нужно выпускать рабочие книги, рабочие листы, диапазоны, это работа GC. Единственное, что вы никогда не должны забывать, - это вызвать Quit для одного-единственного объекта Excel.Application. После вызова Quit обнулить объект Excel.Application и вызвать GC.Collect (); GC.WaitForPendingFinalizers (); дважды.
Я думаю, что отчасти это просто способ, которым фреймворк обрабатывает приложения 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 где-либо, кроме минимального количества времени, но я, вероятно, просто параноидально отношусь к этому.
Как указывали другие, вам необходимо создать явную ссылку для каждого используемого объекта 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-приложения, и все будет намного проще, поскольку вам нужно будет выпустить только эту ссылку.
Фактически вы можете полностью освободить объект приложения 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().
Это сложная тема, но это действительно все. После того, как вы установите этот шаблон для своей процедуры очистки, вы можете кодировать как обычно, без использования оберток и т. д. :-)
У меня есть руководство по этому поводу:
Автоматизация офисных программ с помощью VB.Net / COM Interop
Он написан для VB.NET, но не отчаивайтесь, принципы точно такие же, как при использовании C#.
Соответствующее обсуждение можно найти на форуме ExtremeVBTalk .NET Office Automation, здесь: xtremevbtalk.com/showthread.php?t=303928.
А если ничего не помогает, можно использовать Process.Kill () (в крайнем случае), как описано здесь: stackoverflow.com/questions/51462/…
Рад, что это сработало, Ричард. :-) И вот тонкий пример, когда просто избежать «двух точек» недостаточно для предотвращения проблемы: stackoverflow.com/questions/4964663/…
Вот мнение по этому поводу Миши Шнеерсона из Microsoft по этой теме в рамках даты комментария 7 октября 2010 г. (blogs.msdn.com/b/csharpfaq/archive/2010/09/28/…)
Я надеюсь, что это поможет решить нашу постоянную проблему. Безопасно ли выполнять сборку мусора и освобождать вызовы внутри блока finally?
Сработало у меня, но только после переноса GC.Collect()после на Marshal.FinalReleaseComObject(xlApp). GC.WaitForPendingFinalizers() не нужен.
Вы должны знать, что 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
Я нашел - полезный универсальный шаблон, который может помочь реализовать правильный шаблон удаления для 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/
да, это хорошо. Я думаю, что этот код можно обновить, теперь, когда доступен FinalReleaseCOMObject.
По-прежнему раздражает наличие блока using для каждого объекта. См. Здесь stackoverflow.com/questions/2191489/… для альтернативы.
ОБНОВИТЬ: добавлен код C# и ссылка на Windows Jobs.
Некоторое время я пытался решить эту проблему, и в то время XtremeVBTalk был наиболее активным и отзывчивым. Вот ссылка на мой исходный пост, Чистое закрытие процесса взаимодействия с Excel, даже если ваше приложение вылетает. Ниже приводится краткое изложение сообщения и код, скопированный в это сообщение.
Application.Quit() и Process.Kill() работает по большей части, но не работает, если приложения катастрофически вылетают. Т.е. если приложение выйдет из строя, процесс 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 как:Causes all processes associated with the job to terminate when the last handle to the job is closed.
Дополнительный вызов Win32 API для получения идентификатора процесса (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);
Явное уничтожение внепроцессного COM-сервера (который может обслуживать других COM-клиентов) - ужасная идея. Если вы прибегаете к этому, это потому, что ваш протокол COM сломан. COM-серверы не следует рассматривать как обычное оконное приложение.
Обычные разработчики, у меня ни одно ваше решение не помогло, поэтому я решил реализовать новый обманывать.
Сначала позвольте указать "Какова наша цель?" => «Не видеть объект excel после нашей работы в диспетчере задач»
В порядке. Позвольте не оспаривать и начать разрушать его, но подумайте о том, чтобы не уничтожать другие экземпляры ОС 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();
Это решает мою проблему, надеюсь и вашу.
.... это что-то вроде подхода следгаммера? - это похоже на то, что я также использую :(, но необходимо изменить. Проблема с использованием этого подхода заключается в том, что часто при открытии excel на машине, на которой он запущен, в левой панели появляется предупреждение, говорящее что-то вроде «Excel был закрыт неправильно»: не очень элегантно. Я думаю, что предпочтительнее одно из предыдущих предложений в этой ветке.
Проголосовали против, потому что это уродливый взлом, а не решение.
Предисловие: мой ответ содержит два решения, поэтому будьте внимательны при чтении и ничего не пропустите.
Существуют различные способы и советы по выгрузке экземпляра Excel, например:
Явное освобождение КАЖДОГО com-объекта
с Marshal.FinalReleaseComObject ()
(не забывая о неявном
созданные com-объекты). Освободить
каждый созданный объект com, вы можете использовать
здесь упоминается правило двух точек:
Как правильно очистить объекты взаимодействия Excel?
Вызов GC.Collect () и GC.WaitForPendingFinalizers (), чтобы сделать CLR освобождает неиспользуемые com-объекты * (на самом деле это работает, подробности см. В моем втором решении)
Проверка наличия приложения com-server возможно показывает окно сообщения, ожидающее пользователь должен ответить (хотя я не уверен, что это может помешать 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 закроется =)
печально, что ветка уже настолько старая, что ваш отличный ответ появляется так далеко внизу, что, я думаю, является единственной причиной того, что за него не проголосовали больше раз ...
@VVS: Абсолютно неверная аналогия. И ваше «правило двух точек» не всегда работает. Далеко не всегда.
@nightcoder: У вас есть образец? Напоминает codinghorror.com/blog/2008/03/…
Я могу найти образец, но не хочу тратить время и спорить с вами. Многие подтверждают, что ReleaseComObject () не всегда помогает.
Должен признаться, когда я впервые прочитал ваш ответ, я подумал, что это гигантский кладж. Примерно через 6 часов борьбы с этим (все выпущено, у меня нет двойных точек и т. д.), Я теперь думаю, что ваш ответ гениален.
Спасибо за это. Пару дней боролся с Excel, который не закрывался, несмотря ни на что. Превосходно.
Я не замечаю никаких проблем с использованием GC.Collect() и GC.WaitForPendingFinalizers() в режиме отладки ... если я сделаю только ваш второй шаг («оберните метод, работающий с Excel, в отдельный метод»), он, похоже, будет работать нормально. Фактически, Excel красиво исчезает из моего списка процессов диспетчера задач, даже если я никогда не использую Marshal.ReleaseComObject(). Если вы используете какой-либо из ваших методов (KillProcessByMainWindowHwnd или GC.Collect() и GC.WaitForPendingFinalizers() с обернутым методом), разве вручную не освобождать com-объекты совершенно ненужно?
DanM, если не убить процесс Excel и не использовать GC.Collect () + GC.WaitForPendingFinalizers, тогда вам все равно нужно вызвать Marshal.ReleaseComObject () и установить null для оболочек COM-объектов (например, объектов книги и т. д.), Иначе процесс Excel будет не быть закрытым. Если вы убьете процесс Excel, я бы все равно вызвал Marshal.ReleaseComObject () и установил null для оболочек COM-объектов, потому что это поможет быстрее освободить память и это более правильно (например, если вы не вызываете ReleaseComObject (), тогда эти оболочки будут попробуйте получить доступ к несуществующим объектам com, которые уже были уничтожены, когда вы убили процесс Excel).
@nightcoder, спасибо за ответ. Позвольте мне убедиться, что мы говорим об одном и том же. Вот что я пробовал: (1) поместить весь код, имеющий отношение к COM-объектам Excel, в автономный метод (или иерархию методов), (2) создать вызывающий метод, который вызывает автономный метод в блоке try, а затем вызывает GC.Collect() и GC.WaitForPendingFinalizers() в блоке finally. В моем тестировании это каждый раз очищает процесс Excel из диспетчера задач. Шаг 1 гарантирует, что COM-объекты хранятся только в локальных переменных, поэтому после завершения метода не должно быть живых ссылок.
@nightcoder (продолжение), Вы, кажется, говорите, что это может быть ненадежно, если я также вручную не вызываю Marshal.ReleaseComObject() для определенных оболочек COM-объектов. Если GC.Collect() и GC.WaitForPendingFinalizers() действительно недостаточно (опять же, предполагая, что все ссылки на COM-объекты ограничены локальными переменными, которые больше не входят в область видимости), (1) почему? и (2) какие COM-объекты Excel необходимо освободить? В моем тестовом примере я нет выпускаю следующие объекты без видимых вредных эффектов: Application, Workbook, Sheets, Worksheet и Range. Еще раз спасибо!
@nightcoder: отличный ответ, очень подробный. Ваши комментарии относительно режима отладки очень верны и важны, что вы указали на это. Тот же самый код в режиме выпуска часто может быть нормальным. Однако Process.Kill хорош только при использовании автоматизации, а не тогда, когда ваша программа работает в Excel, например, как управляемая надстройка COM.
@DanM: Ваш подход на 100% верен и никогда не подведет. Сохранение всех ваших переменных локальными для метода делает все ссылки недоступными для кода .NET. Это означает, что когда ваш раздел finally вызывает GC.Collect (), финализаторы для все ваших COM-объектов будут вызываться с уверенностью. Затем каждый финализатор вызывает Marshal.FinalReleaseComObject для финализируемого объекта. Таким образом, ваш подход прост и надежен. Не бойтесь использовать это. (Единственное предостережение: если вы используете VSTO, в чем я сомневаюсь, вам нужно будет вызвать GC.Collect () и GC.WaitForPendingFinalizers ДВАЖДЫ.)
@MikeRosenblum, спасибо за ответ на мой комментарий. Я ценю заверения. Для конкретного проекта, над которым я работал в то время, я полностью обошел проблему, используя файловый подход (с использованием EPPlus) вместо COM. Однако, если мне действительно понадобится снова использовать COM-взаимодействие, я буду знать, что делать.
Я думаю, что это доводит «дырявую абстракцию» до крайности.
Явное уничтожение или отправка WM_CLOSE на внепроцессный COM-сервер (который может обслуживать других COM-клиентов) - ужасная идея. Если вы прибегаете к этому, это потому, что ваш протокол COM сломан. COM-серверы не следует рассматривать как обычное оконное приложение.
второй метод очистки не работал бы с Marshal.ReleaseComObject, его заменили на 2xClose () и Quit (), и он сработал! так что парень, который сказал использовать ReleaseComObject, должен кое-что объяснить!
Принятый здесь ответ правильный, но также обратите внимание, что следует избегать не только «двухточечных» ссылок, но и объектов, которые извлекаются через индекс. Вам также не нужно ждать, пока вы закончите работу с программой, чтобы очистить эти объекты, лучше всего создать функции, которые будут очищать их, как только вы закончите с ними, когда это возможно. Вот созданная мной функция, которая назначает некоторые свойства объекта 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 будет все еще работает на вашем сервере.
Это требует большого внимания к деталям и выполнения множества тестов при мониторинге диспетчера задач при написании этого кода, но это избавляет вас от хлопот отчаянного поиска по страницам кода, чтобы найти один экземпляр, который вы пропустили. Это особенно важно при работе в циклах, когда вам нужно освободить КАЖДЫЙ ЭКЗЕМПЛЯР объекта, даже если он использует одно и то же имя переменной при каждом цикле.
Внутри выпуска проверьте значение Null (ответ Джо). Это позволит избежать ненужных нулевых исключений. Я испытал МНОГО методов. Это единственный способ эффективно выпустить Excel.
Я не могу поверить, что эта проблема преследовала мир в течение 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 не является частью вашего прямого контроля. Таким образом, он останется открытым, если ссылка будет освобождена!
Всем хорошего программирования ~~
Нет, вы этого не сделаете, особенно если вы хотите разрешить взаимодействие пользователя с приложением COM (в данном случае Excel - OP не указывает, предполагается ли взаимодействие с пользователем, но не ссылается на его завершение). Вам просто нужно заставить вызывающего абонента отпустить, чтобы пользователь мог выйти из Excel при закрытии, скажем, одного файла.
это отлично сработало для меня - сбой, когда у меня был только objExcel.Quit (), но когда я также сделал objExcel.Application.Quit () заранее, закрылся чисто.
Чтобы добавить к причинам, почему 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
И еще одна причина - повторное использование ссылки без предварительного сброса предыдущего значения.
Спасибо. Я также использовал "для каждого", ваше решение сработало для меня.
+1 Спасибо. Кроме того, обратите внимание, что если у вас есть объект для диапазона Excel, и вы хотите изменить диапазон в течение срока его существования, я обнаружил, что мне пришлось выполнить ReleaseComObject перед его переназначением, что делает код немного неаккуратным!
Когда все вышеперечисленное не сработало, попробуйте дать Excel немного времени, чтобы закрыть свои листы:
app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();
...
FinalReleaseComObject(app);
Ненавижу произвольное ожидание. Но +1, потому что вы правы в том, что нужно дождаться закрытия книг. Альтернативой является опрос коллекции книг в цикле и использование произвольного ожидания в качестве тайм-аута цикла.
Excel не предназначен для программирования на C++ или C#. COM API специально разработан для работы с Visual Basic, VB.NET и VBA.
Кроме того, все примеры кода на этой странице не являются оптимальными по той простой причине, что каждый вызов должен пересекать управляемую / неуправляемую границу и дополнительно игнорировать тот факт, что API Excel COM может сбой любого вызова с загадочным HRESULT, указывающим, что сервер RPC является занятый.
На мой взгляд, лучший способ автоматизировать Excel - это собрать ваши данные в как можно больший массив и отправить их в функцию или подпрограмму VBA (через Application.Run), которая затем выполняет любую необходимую обработку. Кроме того, при вызове Application.Run обязательно следите за исключениями, указывающими, что excel занят, и повторите вызов Application.Run.
C# и VB.NET работают под CLR, поэтому он не может быть разработан для VB.NET, а не C#. В конечном итоге это одно и то же, только с другой семантикой языка для создания приложения.
Попробуйте вызвать метод Run из объекта Excel Application в C# (версии 3.0 и более ранней) или C++. Затем попробуйте сделать то же самое с VB.net. После пропущенного 10-го или 12-го параметра вы, вероятно, поймете, что я имел в виду:]
Кроме того, как вы упомянули, C# и VB.net являются языками CLR. Они предлагают программистам различные подмножества функций CLR вместе с различным синтаксическим сахаром. Так уж получилось, что подмножество, предлагаемое VB.net, упрощает программирование на com и excel.
Но опять же, это всего лишь семантика языка (до 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, поскольку это вся управляемая оболочка.
Excel is not designed to be programmed via C++ or C# - неверная информация.
@ Джереми, это правда. Он в первую очередь предназначен для программирования через клиентов с поздним связыванием, таких как VBA и VB. Использование C++ или C# для работы с Excel - кошмар.
Можно согласиться, но с этим можно не согласиться, ИМХО это далеко не кошмар. Использование C# .Net 4.0 с поддержкой необязательных параметров практически то же самое, что и VB.Net. По моему опыту, все больше компаний переносят старый код VBA на C#, а не на VB.Net.
Кажется, что это было слишком сложно. По моему опыту, есть всего три ключевых момента, чтобы 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 будет очищен (обычно нажатием кнопки «Стоп» во время отладки вашей программы).
Этот ответ работает и намного удобнее принятого ответа. Не беспокойтесь о «двух точках» с COM-объектами, никакого использования Marshal.
Вы должны быть очень осторожны при использовании приложений взаимодействия Word / Excel. После опробования всех решений у нас все еще оставалось много открытых процессов WinWord на сервере (с более чем 2000 пользователей).
После нескольких часов работы над проблемой я понял, что если я открою более пары документов, используя Word.ApplicationClass.Document.Open() в разных потоках одновременно, рабочий процесс IIS (w3wp.exe) выйдет из строя, оставив все процессы WinWord открытыми!
Поэтому я думаю, что нет абсолютного решения этой проблемы, кроме перехода на другие методы, такие как разработка Office Open XML.
«Никогда не используйте две точки с COM-объектами» - это отличное практическое правило, позволяющее избежать утечки ссылок на COM, но Excel PIA может привести к утечке многими способами, чем кажется на первый взгляд.
Один из этих способов - подписаться на любое событие, предоставляемое любым из COM-объектов объектной модели Excel.
Например, подписка на событие WorkbookOpen класса Application.
Классы COM предоставляют группу событий через интерфейсы обратного вызова. Чтобы подписаться на события, клиентский код может просто зарегистрировать объект, реализующий интерфейс обратного вызова, и класс COM будет вызывать свои методы в ответ на определенные события. Поскольку интерфейс обратного вызова является интерфейсом COM, реализующий объект обязан уменьшить счетчик ссылок любого COM-объекта, который он получает (в качестве параметра) для любого из обработчиков событий.
Excel PIA представляет события COM класса приложения Excel как обычные события .NET. Когда клиентский код подписывается на событие .NET (акцент на «a»), PIA создает экземпляр ан класса, реализующего интерфейс обратного вызова, и регистрирует его в Excel.
Следовательно, ряд объектов обратного вызова регистрируется в Excel в ответ на различные запросы подписки из кода .NET. Один объект обратного вызова на подписку на событие.
Интерфейс обратного вызова для обработки событий означает, что PIA должна подписываться на все события интерфейса для каждого запроса подписки на события .NET. Он не может выбирать. При получении обратного вызова события объект обратного вызова проверяет, заинтересован ли связанный обработчик событий .NET в текущем событии или нет, а затем либо вызывает обработчик, либо молча игнорирует обратный вызов.
Все эти объекты обратного вызова не уменьшают счетчик ссылок любого из COM-объектов, которые они получают (в качестве параметров) для любого из методов обратного вызова (даже для тех, которые игнорируются молча). Они полагаются исключительно на сборщик мусора CLR для освобождения COM-объектов.
Поскольку запуск сборщика мусора не является детерминированным, это может привести к приостановке процесса Excel на более длительный срок, чем требуется, и создать впечатление «утечки памяти».
Единственное решение на данный момент - избегать поставщика событий PIA для класса COM и писать собственного поставщика событий, который детерминированно освобождает COM-объекты.
Для класса Application это можно сделать, реализовав интерфейс AppEvents, а затем зарегистрировав реализацию в Excel с помощью IConnectionPointContainer интерфейс. Класс Application (и в этом отношении все COM-объекты, отображающие события с помощью механизма обратного вызова) реализует интерфейс IConnectionPointContainer.
Убедитесь, что вы освободили все объекты, относящиеся к Excel!
Я потратил несколько часов, пробуя разные способы. Все отличные идеи, но я наконец нашел свою ошибку: Если вы не освободите все объекты, ни один из вышеперечисленных способов вам не поможет. как в моем случае. Убедитесь, что вы освободили все объекты, включая первый диапазон!
Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);
Параметры вместе здесь.
Очень хороший момент! Я думаю, что, поскольку я использую Office 2007, от моего имени была произведена некоторая очистка. Я воспользовался советом, но не сохранил переменные, как вы предложили здесь, и EXCEL.EXE завершает работу, но мне может просто повезти, и если у меня возникнут другие проблемы, я обязательно посмотрю на эту часть моего кода =)
Отличная статья о выпуске 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();
}
}
Эти слова взяты прямо из статьи:
In almost all situations, nulling the RCW reference and forcing a garbage collection will clean up properly. If you also call GC.WaitForPendingFinalizers, garbage collection will be as deterministic as you can make it. That is, you'll be pretty sure exactly when the object has been cleaned up—on the return from the second call to WaitForPendingFinalizers. As an alternative, you can use Marshal.ReleaseComObject. However, note that you are very unlikely to ever need to use this method.
Я традиционно следовал совету из Ответ ВВС. Однако, чтобы поддерживать этот ответ в актуальном состоянии с учетом последних опций, я думаю, что все мои будущие проекты будут использовать библиотеку «NetOffice».
NetOffice является полной заменой Office PIA и полностью не зависит от версии. Это набор управляемых оболочек COM, которые могут обрабатывать очистку, которая часто вызывает такие головные боли при работе с Microsoft Office в .NET.
Некоторые ключевые особенности:
Я никоим образом не связан с проектом; Я искренне ценю резкое уменьшение головных болей.
На самом деле это должно быть отмечено как ответ. NetOffice абстрагирует всю эту сложность.
Я использую NetOffice в течение значительного количества времени, пишу надстройку для Excel, и она отлично работает. Единственное, что следует учитывать, - это то, что если вы не удаляете использованные объекты явно, он сделает это при выходе из приложения (потому что он все равно отслеживает их). Таким образом, практическое правило NetOffice - всегда использовать шаблон «использование» с каждым объектом Excel, таким как ячейка, диапазон, лист и т. д.
Как некоторые, вероятно, уже писали, важно не только то, как Закрыть вы Закрыть Excel (объект); Также важно, как вы его открытым, а также по типу проекта.
В приложении WPF в основном один и тот же код работает без проблем или с очень небольшим количеством проблем.
У меня есть проект, в котором один и тот же файл Excel обрабатывается несколько раз для разных значений параметра, например. анализируя его на основе значений внутри общего списка.
Я поместил все функции, связанные с Excel, в базовый класс, а парсер - в подкласс (разные парсеры используют общие функции Excel). Я не хотел, чтобы Excel открывался и закрывался снова для каждого элемента в общем списке, поэтому я открыл его только один раз в базовом классе и закрыл в подклассе. У меня возникли проблемы при переносе кода в настольное приложение. Я пробовал многие из вышеупомянутых решений. GC.Collect() уже был реализован ранее, дважды, как предлагалось.
Тогда я решил, что перенесу код открытия Excel в подкласс. Вместо того, чтобы открываться только один раз, теперь я создаю новый объект (базовый класс), открываю Excel для каждого элемента и закрываю его в конце. Существует некоторое снижение производительности, но, судя по результатам нескольких тестов, процессы Excel закрываются без проблем (в режиме отладки), поэтому временные файлы также удаляются. Я продолжу тестирование и напишу еще, если получу какие-то обновления.
Суть в следующем: вы также должны проверить код инициализации, особенно если у вас много классов и т. д.
¨ ° º¤ø „¸ Стреляйте в 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);
}
Использовать:
[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);
}
}
После попытки
GC.Collect() и GC.WaitForPendingFinalizers() дважды в концепоследнее решение, которое работает для меня, - переместить один набор
GC.Collect();
GC.WaitForPendingFinalizers();
который мы добавили в конец функции в оболочку, как показано ниже:
private void FunctionWrapper(string sourcePath, string targetPath)
{
try
{
FunctionThatCallsExcel(sourcePath, targetPath);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Я обнаружил, что мне не нужно аннулировать ComObjects, только Quit () - Close () и FinalReleaseComObject. Я не могу поверить, что это все, чего мне не хватало, чтобы заставить его работать. Большой!
Правило двух точек у меня не сработало. В моем случае я создал метод очистки своих ресурсов следующим образом:
private static void Clean()
{
workBook.Close();
Marshall.ReleaseComObject(workBook);
excel.Quit();
CG.Collect();
CG.WaitForPendingFinalizers();
}
Я точно следил за этим ... Но я все равно сталкивался с проблемами 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();
Мое решение
[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();
}
Принятый ответ у меня не сработал. Следующий код в деструкторе выполнил свою работу.
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(); }
}
Пока что все ответы включают некоторые из них:
Что заставляет меня понять, насколько сложна эта проблема :)
Я работал над библиотекой, чтобы упростить доступ к 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.
Комментарии? Или это ответ на вопрос третьего типа?
Просто чтобы добавить еще одно решение к множеству перечисленных здесь, используя автоматизацию C++ / ATL (я полагаю, вы могли бы использовать что-то подобное из VB / C# ??)
Excel::_ApplicationPtr pXL = ...
:
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;
Для меня это действует как шарм ...
Во-первых, никогда должен вызывать 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 считается опасным вместе с обнаружением проблемы со ссылками, сохраняемыми под отладчиком, что сбивало с толку мое предыдущее тестирование.
Практически ВСЕ ОТВЕТЫ НА ЭТОЙ СТРАНИЦЕ НЕПРАВИЛЬНЫ, ЗА ИСКЛЮЧЕНИЕМ ЭТОГО. Они работают, но слишком много работы. ПРОПУСТИТЕ правило «2 ТОЧКИ». Позвольте GC делать вашу работу за вас. Дополнительное свидетельство из .Net GURU Hans Passant: stackoverflow.com/a/25135685/3852958
Говерт, какое облегчение найти правильный способ сделать это. Спасибо.
В настоящее время я работаю над автоматизацией 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;
}
}
}
}
Это единственный способ, который действительно работает для меня
foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
{
proc.Kill();
}
«Это определенно кажется слишком сложным. По моему опыту, есть всего три ключевых момента, чтобы 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 будет очищен (обычно нажатием кнопки «Стоп» во время отладки вашей программы) ».
Вот у меня есть идея, попробуйте убить процесс Excel, который вы открыли:
когда нужно выйти, уничтожьте идентификаторы исключений между 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;
}
}
Протестировано с Microsoft Excel 2016
Действительно проверенное решение.
Для справки по C# см .: https://stackoverflow.com/a/1307180/10442623
Ссылку на VB.net см .: https://stackoverflow.com/a/54044646/10442623
1 включить классную работу
2 реализовать класс для обработки соответствующего удаления процесса Excel
Вот действительно простой способ сделать это:
[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();
У меня была такая же проблема с закрытием PowerPoint после создания объекта Application в моем надстройке VSTO. Я попробовал все ответы здесь с ограниченным успехом.
Это решение, которое я нашел для своего случая - НЕ используйте «новое приложение», базовый класс AddInBase для ThisAddIn уже имеет дескриптор для «Приложения». Если вы используете этот дескриптор там, где он вам нужен (сделайте его статическим, если нужно), вам не нужно беспокоиться об его очистке, и PowerPoint не будет зависать рядом.
Мой ответ запоздал, и его единственная цель - поддержать решение, предложенное Говертом, Поркбаттсом и Дэйвом Кузино, полным примером. Автоматизация Excel или других COM-объектов из COM-независимого мира .NET - это «крепкий орешек», как мы говорим по-немецки, и вы можете легко сходить с ума. Я полагаюсь на следующие шаги:
Для каждого взаимодействия с Excel получите один и только один локальный экземпляр ExcelApp интерфейса приложения и создайте область, в которой живет ExcelApp. Это необходимо, поскольку среда CLR не освобождает ресурсы Excel до того, как любая ссылка на Excel выйдет за рамки. В фоновом режиме запускается новый процесс Excel.
Реализуйте функции, которые выполняют задачи, используя ExcelApp для
генерировать через свойства Collection новые объекты, такие как Workbook (s),
Рабочие листы и ячейки. В этих функциях не заботятся о
вуду: одна точка - хорошо, две точки - плохо, не пытайся получить ссылку
для каждого неявно созданного объекта и не
Marshall.ReleaseComObject ничего. Это работа мусора
Коллекция.
В рамках ExcelApp вызовите эти функции и передайте ссылку ExcelApp.
Пока ваш экземпляр Excel загружен, не допускайте никаких действий пользователя, которые могли бы обойти функцию Quit, которая снова выгружает этот экземпляр.
Когда вы закончите работу с 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 до завершения работы приложения. Вот почему я предпочитаю локальные ссылки, которые выходят за рамки, когда они больше не нужны.
Из трех общих стратегий, рассмотренных в других ответах, убийство процесса excel явно является взломом, тогда как вызов сборщика мусора - это жестокий подход, предназначенный для компенсации неправильного освобождения COM-объектов. После множества экспериментов и переписывания управления объектами COM в моей не зависящей от версии оболочке и оболочке с поздним связыванием я пришел к выводу, что точные и своевременные вызовы Marshal.ReleaseComObject() являются наиболее эффективной и элегантной стратегией. И нет, вам никогда не понадобится FinalReleaseComObject(), потому что в хорошо написанной программе каждый COM получает один раз и, следовательно, требует единственного уменьшения счетчика ссылок.
Следует убедиться, что каждый объект COM освобожден, желательно сразу после того, как он больше не нужен. Но вполне возможно освободить все сразу после выхода из приложения Excel, только за счет более высокого использования памяти. Excel закроется, как ожидалось, пока вы не потеряете или не забудете освободить объект COM.
Самым простым и очевидным помощником в этом процессе является упаковка каждого объекта взаимодействия в класс .СЕТЬ, реализующий 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, если пользователь предпочитает обменять некоторое использование ОЗУ на более простой код.
Вы пытаетесь закрыть Excel.exe, не закрывая приложение? Не уверен, что полностью понимаю ваш вопрос.