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

Если код модуля находится в строке, вы можете отказаться от использования StringIO и использовать его непосредственно с exec, как показано ниже, с файлом с именем dynmodule.py. Работает в Python 2 & amp; 3.

from __future__ import print_function

class _DynamicModule(object):
    def load(self, code):
        execdict = {'__builtins__': None}  # optional, to increase safety
        exec(code, execdict)
        keys = execdict.get(
            '__all__',  # use __all__ attribute if defined
            # else all non-private attributes
            (key for key in execdict if not key.startswith('_')))
        for key in keys:
            setattr(self, key, execdict[key])

# replace this module object in sys.modules with empty _DynamicModule instance
# see Stack Overflow question: http://bit.ly/ff94g6)
import sys as _sys
_ref, _sys.modules[__name__] = _sys.modules[__name__], _DynamicModule()

if __name__ == '__main__':
    import dynmodule  # name of this module
    import textwrap  # for more readable code formatting in sample string

    # string to be loaded can come from anywhere or be generated on-the-fly
    module_code = textwrap.dedent("""\
        foo, bar, baz = 5, 8, 2

        def func():
            return foo*bar + baz

        __all__ = 'foo', 'bar', 'func'  # 'baz' not included
        """)

    dynmodule.load(module_code)  # defines module's contents

    print('dynmodule.foo:', dynmodule.foo)
    try:
        print('dynmodule.baz:', dynmodule.baz)
    except AttributeError:
        print('no dynmodule.baz attribute was defined')
    else:
        print('Error: there should be no dynmodule.baz module attribute')
    print('dynmodule.func() returned:', dynmodule.func())

Выход:

dynmodule.foo: 5
no dynmodule.baz attribute was defined
dynmodule.func() returned: 42

Установка записи '__builtins__' в None в словаре execdict не позволяет коду непосредственно выполнять какой-либо встроенный функции, такие как __import__, и поэтому делает его более безопасным. Вы можете облегчить это ограничение, выборочно добавляя к нему вещи, которые вы чувствуете в порядке и / или требуются.

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

719
задан Peter Mortensen 26 December 2016 в 14:28
поделиться

13 ответов

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

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

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

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

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

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

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

<час>

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

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

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

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

665
ответ дан VVS 26 December 2016 в 14:28
поделиться

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

кроме того, и возможно я по упрощению вещей, но я думаю, что Вы можете просто...

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

1
ответ дан Peter Mortensen 26 December 2016 в 14:28
поделиться

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

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

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

необходимо делать

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

сопровождаемый выпуском объектов.

29
ответ дан Peter Mortensen 26 December 2016 в 14:28
поделиться

Как другие указали, необходимо создать прямую ссылку для каждого объекта Excel, который что Вы используете, и Маршал вызова. ReleaseComObject на той ссылке, как описано в эта статья KB. Также необходимо использовать попытку/наконец гарантировать, что 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);
}

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

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

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

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

4
ответ дан Joe 26 December 2016 в 14:28
поделиться

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

можно найти, что необходимо установить культуру на EN-США прежде, чем вызвать функции 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

6
ответ дан 26 December 2016 в 14:28
поделиться

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

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

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

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

, Например, предполагая, что у Вас была переменная объекта Диапазона, названная xlRng, Переменная документа, названная xlSheet, переменная Рабочей книги, названная xlBook и переменная Excel Application, названная xlApp, тогда Ваш код очистки мог посмотреть что-то как следующее:

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

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

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

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

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

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

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

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

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

, у меня есть учебное руководство на этом здесь:

Программы Office Автоматизации с VB.Net / COM Interop

Это записано для VB.NET, но не пугайтесь этого, принципы являются точно тем же как тогда, когда с помощью C#.

271
ответ дан 8 revs, 2 users 94% 26 December 2016 в 14:28
поделиться

Это работало на проект, я продолжал работать:

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

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

35
ответ дан Peter Mortensen 26 December 2016 в 14:28
поделиться

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

17
ответ дан Edward Wilde 26 December 2016 в 14:28
поделиться
  • 1
    Просто загруженный штраф для меня. Название страницы, " Aptana и Eclispe - Средство форматирования Кода для Инструментов разработки PHP для Eclipse". – Chris 1 June 2011 в 18:14

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

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

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

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

Код задания Windows

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

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

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

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

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

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

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

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

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

    private IntPtr m_handle;
    private bool m_disposed = false;

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

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

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

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

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

    #region IDisposable Members

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

    #endregion

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

        if (disposing) {}

        Close();
        m_disposed = true;
    }

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

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

}

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

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

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

Код задания 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 как:

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

Код задания 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);
49
ответ дан 22 November 2019 в 21:30
поделиться

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

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

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

13
ответ дан 22 November 2019 в 21:30
поделиться

Preface: my answer contains two solutions, so be careful when reading and don't miss anything.

There are different ways and advice of how to make Excel instance unload, such as:

  • Releasing EVERY com object explicitly with Marshal.FinalReleaseComObject() (not forgetting about implicitly created com-objects). To release every created com object, you may use the rule of 2 dots mentioned here:
    How do I properly clean up Excel interop objects?

  • Calling GC.Collect() and GC.WaitForPendingFinalizers() to make CLR release unused com-objects * (Actually, it works, see my second solution for details)

  • Checking if com-server-application maybe shows a message box waiting for the user to answer (though I am not sure it can prevent Excel from closing, but I heard about it a few times)

  • Sending WM_CLOSE message to the main Excel window

  • Executing the function that works with Excel in a separate AppDomain. Some people believe Excel instance will be shut, when AppDomain is unloaded.

  • Killing all excel instances which were instantiated after our excel-interoping code started.

BUT! Sometimes all these options just don't help or can't be appropriate!

For example, yesterday I found out that in one of my functions (which works with excel) Excel keeps running after the function ends. I tried everything! I thoroughly checked the whole function 10 times and added Marshal.FinalReleaseComObject() for everything! I also had GC.Collect() and GC.WaitForPendingFinalizers(). I checked for hidden message boxes. I tried to send WM_CLOSE message to the main Excel window. I executed my function in a separate AppDomain and unloaded that domain. Nothing helped! The option with closing all excel instances is inappropriate, because if the user starts another Excel instance manually, during execution of my function which works also with Excel, then that instance will also be closed by my function. I bet the user will not be happy! So, honestly, this is a lame option (no offence guys). So I spent a couple of hours before I found a good (in my humble opinion) solution: Kill excel process by hWnd of its main window (it's the first solution).

Here is the simple code:

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

As you can see I provided two methods, according to Try-Parse pattern (I think it is appropriate here): one method doesn't throw the exception if the Process could not be killed (for example the process doesn't exist anymore), and another method throws the exception if the Process was not killed. The only weak place in this code is security permissions. Theoretically, the user may not have permissions to kill the process, but in 99.99% of all cases, user has such permissions. I also tested it with a guest account - it works perfectly.

So, your code, working with Excel, can look like this:

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

Voila! Excel is terminated! :)

Ok, let's go back to the second solution, as I promised in the beginning of the post. The second solution is to call GC.Collect() and GC.WaitForPendingFinalizers(). Yes, they actually work, but you need to be careful here!
Many people say (and I said) that calling GC.Collect() doesn't help. But the reason it wouldn't help is if there are still references to COM objects! One of the most popular reasons for GC.Collect() not being helpful is running the project in Debug-mode. In debug-mode objects that are not really referenced anymore will not be garbage collected until the end of the method.
So, if you tried GC.Collect() and GC.WaitForPendingFinalizers() and it didn't help, try to do the following:

1) Try to run your project in Release mode and check if Excel closed correctly

2) Wrap the method of working with Excel in a separate method. So, instead of something like this:

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

you write:

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)
    ...
  }
}

Now, Excel will close =)

209
ответ дан 22 November 2019 в 21:30
поделиться

Принятый здесь ответ правильный, но также учтите, что следует избегать не только «двухточечных» ссылок, но и объектов, которые извлекаются через индекс. Вам также не нужно ждать, пока вы закончите работу с программой, чтобы очистить эти объекты, лучше создать функции, которые будут очищать их, как только вы закончите с ними, когда это возможно. Вот созданная мной функция, которая назначает некоторые свойства объекта 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 по-прежнему будет работать на вашем сервере.

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

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

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

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

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

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

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

8
ответ дан 22 November 2019 в 21:30
поделиться

Мой ответ является поздним, и его единственная цель состоит в том, чтобы поддерживать решение, предложенное Govert, Porkbutts и Dave Cousineau с полным примером. Automating Excel или другие COM-объекты от COM-агностика, мир.NET является “tough гайкой, ”, как мы говорим на немецком языке, и можно легко сойти с ума. Я полагаюсь на следующие шаги:

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

  2. функции Реализации, которые делают задачи при помощи ExcelApp для генерации через свойства Collection новых объектов как Рабочая книга (книги), Рабочий лист (листы) и Ячейка (ячейки). В этих функциях don’t заботятся о вуду one-dot-good, two-dot-bad правило, don’t пытаются получить ссылку для каждого неявно созданного объекта и don’t Marshall.ReleaseComObject что-либо. Это - задание Сборки "мусора".

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

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

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

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

, Если я изменил Основную функцию на [1 123]

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

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

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

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

1
ответ дан 22 November 2019 в 21:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: