Как правильно очистить объекты взаимодействия Excel? - PullRequest
714 голосов
/ 01 октября 2008

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

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

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

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

Ответы [ 39 ]

664 голосов
/ 01 октября 2008

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

ОБНОВЛЕНИЕ ПОСЛЕ MORTEM:

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

269 голосов
/ 01 октября 2008

На самом деле вы можете освободить объект приложения 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 Tools for Office (VSTO), в котором используются финализаторы, которые приводят к продвижению всего графа объектов в очереди финализации. Такие объекты не будут освобождены до следующей сборки мусора next . Однако, если вы не используете VSTO, вы сможете звонить GC.Collect() и GC.WaitForPendingFinalizers() только один раз.

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

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

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

Автоматизация офисных программ с VB.Net / COM Interop

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

204 голосов
/ 12 декабря 2009

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

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

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

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

  • Проверка, если com-сервер-приложение возможно показывает окно сообщения в ожидании пользователь, чтобы ответить (хотя я не уверен, что это может помешать 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 закроется =)

46 голосов
/ 20 августа 2009

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

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

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

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

Код задания Windows

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

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 как:

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

Дополнительный 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);
35 голосов
/ 01 октября 2008

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

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

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

29 голосов
/ 01 октября 2008

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

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

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

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

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

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

19 голосов
/ 30 июня 2016

Первый - вам никогда не придется звонить 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 и переполнении стека (и особенно в этом вопросе!).

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

18 голосов
/ 08 декабря 2008

I найдено полезный универсальный шаблон, который может помочь реализовать правильный шаблон удаления для 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/

15 голосов
/ 21 ноября 2010

Не могу поверить, что эта проблема преследует мир в течение 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 не является частью вашего прямого контроля. Следовательно, он останется открытым, если ссылка будет выпущена!

Хорошее программирование для всех ~~

12 голосов
/ 16 октября 2009

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

Сначала позвольте уточнить "Какова наша цель?" => «Не видеть объект 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();

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

...