Использование объектов Wrapper для правильной очистки объектов interc interel

Все эти вопросы:

бороться с проблемой, что С# не освобождает объекты COM Excel должным образом после их использования. В этом вопросе есть главным образом два направления:

  • Убейте процесс Excel, когда Excel больше не используется.
  • Позаботьтесь о том, чтобы явно назначить каждый COM-объект, используемый для переменной, и гарантировать, что в конечном итоге Marshal.ReleaseComObject будет выполняться на каждом из них.
Некоторые заявили, что 2 слишком утомительно, и всегда есть какая-то неопределенность, забываете ли вы забыть придерживаться этого правила в некоторых местах кода. Все еще 1 кажется грязным и подверженным ошибкам для меня, также я предполагаю, что в ограниченной среде, пытающейся убить процесс, может возникнуть ошибка безопасности.

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

  • Каждый класс Excel Interop имеет свой прокси-сервер, который обертывает объект этого класса.
  • Прокси-сервер выпускает COM-объект в своем финализаторе.
  • Прокси имитирует интерфейс класса Interop.
  • Любые методы, которые первоначально возвратили COM-объект, изменяются для возврата прокси-сервера. Другие методы просто делегируют реализацию внутреннему COM-объекту.

Пример:

public class Application
{
    private Microsoft.Office.Interop.Excel.Application innerApplication
        = new Microsoft.Office.Interop.Excel.Application innerApplication();

    ~Application()
    {
        Marshal.ReleaseCOMObject(innerApplication);
        innerApplication = null;
    }

    public Workbooks Workbooks
    {
        get { return new Workbooks(innerApplication.Workbooks); }
    }
}

public class Workbooks
{
    private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks;

    Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks)
    {
        this.innerWorkbooks = innerWorkbooks;
    }

    ~Workbooks()
    {
        Marshal.ReleaseCOMObject(innerWorkbooks);
        innerWorkbooks = null;
    }
}

Мои вопросы для вас в частности:

  • Кто считает эту плохую идею и почему?
  • Кто считает эту идею ужасом? Если да, то почему никто не реализовал и не опубликовал такую ​​модель? Это только благодаря усилиям, или мне не хватает проблемы с убийством с этой идеей?
  • Невозможно ли/неправильно/подвержено ошибкам делать ReleaseCOMObject в финализаторе? (Я только видел предложения поместить его в Dispose(), а не в финализатор - почему?)
  • Если подход имеет смысл, любые предложения по его улучшению?

Ответ 1

Невозможно/плохо/опасно делать ReleaseCOMObject в деструкторе? (Я только видел предложения поместить его в Dispose(), а не в деструктор - почему?)

Рекомендуется не помещать ваш очищающий код в финализатор, потому что в отличие от деструктора в С++ он не называется детерминированным. Его можно вызвать вскоре после того, как объект выходит за рамки. Это может занять час. Это никогда не может быть вызвано. В общем случае, если вы хотите утилизировать неуправляемые объекты, вы должны использовать шаблон IDisposable, а не финализатор.

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

Если вместо этого вы хотите попытаться явно очистить, руководство "не использовать две точки с объектами COM" поможет вам запомнить ссылку на каждый созданный объект, чтобы вы могли очистить их, когда вы Сделано.

Ответ 2

Мы используем класс LifetimeScope, описанный в журнале MSDN. Использование его правильно очищает объекты и отлично справляется с экспортом Excel. Код можно скачать здесь, а также содержит статью в журнале:

http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266

Ответ 3

Посмотрите на мой проект MS Office для .NET. Решена проблема с объектами оболочки referencich и собственными объектами через собственную способность связывания VB.NET.

Ответ 4

Что я буду делать:

class ScopedCleanup<T> : IDisposable where T : class
{
    readonly Action<T> cleanup;

    public ScopedCleanup(T o, Action<T> cleanup)
    {
        this.Object = o;
        this.cleanup = cleanup;
    }

    public T Object { get; private set; }

    #region IDisposable Members

    public void Dispose()
    {
        if (Object != null)
        {
            if(cleanup != null)
                cleanup(Object);
            Object = null;
            GC.SuppressFinalize(this);
        }
    }

    #endregion

    ~ScopedCleanup() { Dispose(); }
}

static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class
{
    return new ScopedCleanup<T>(o, cleanup);
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class
{
    return
        CleanupObject(
            comObject,
            o =>
            {
                if(actionBeforeRelease != null)
                    actionBeforeRelease(o);
                Marshal.ReleaseComObject(o);
            }
        );
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class
{
    return CleanupComObject(comObject, null);
}

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

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit()))
using (var workbooks = CleanupComObject(excel.Object.Workbooks))
    {
        ...
    }

Ответ 5

Для того, что стоит, Служба обновления Excel в codeplex использует эту логику:

    public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
    {
        if (reference == null) return;
        try
        {
            doThis(reference);
        }
        finally
        {
            Marshal.ReleaseComObject(reference);
        }
    }