Как правильно очистить объекты взаимодействия 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 ]

1 голос
/ 27 августа 2013

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

[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);
    }
}
1 голос
/ 11 апреля 2013

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

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

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

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

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

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

0 голосов
/ 19 декабря 2018

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

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

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    
0 голосов
/ 14 февраля 2017

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

        foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
        {
            proc.Kill();
        }
0 голосов
/ 07 апреля 2016

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

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

Это работает для меня как шарм ...

0 голосов
/ 19 июня 2011

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

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

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

.
0 голосов
/ 04 января 2019

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

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

К C # Ссылка, пожалуйста, смотрите: https://stackoverflow.com/a/1307180/10442623

Для ссылки на VB.net смотрите: https://stackoverflow.com/a/54044646/10442623

1 включает работу класса

2 реализует класс для обработки процесса утилизации Excel

0 голосов
/ 02 апреля 2019

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

[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();
0 голосов
/ 04 мая 2015

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

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

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

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

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

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

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

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

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

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

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

Реализация, подобная этой:

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

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

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

В заводском методе:

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

        return excel;
    }

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

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

...