Использование объектов Wrapper для правильной очистки объектов взаимодействия Excel - PullRequest
11 голосов
/ 04 мая 2010

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

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

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

Некоторые утверждают, что 2 слишком утомительно, и всегда есть некоторая неуверенность, забыли ли вы придерживаться этого правила в некоторых местах кода. Тем не менее, 1 кажется мне грязным и подверженным ошибкам, также я предполагаю, что в ограниченном окружении попытка уничтожить процесс может вызвать ошибку безопасности.

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

  • У каждого класса Excel Interop есть свой прокси, который оборачивает объект этого класса.
  • Прокси-сервер освобождает COM-объект в своем финализаторе.
  • Прокси имитирует интерфейс класса Interop.
  • Любые методы, которые изначально возвращали COM-объект, изменяются и возвращают прокси. Другие методы просто делегируют реализацию внутреннему COM-объекту.
* +1040 * Пример: * * тысяча сорок один
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 (), а не в финализатор - почему?)
  • Если подход имеет смысл, есть предложения по его улучшению?

Ответы [ 5 ]

5 голосов
/ 04 мая 2010

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

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

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

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

2 голосов
/ 04 мая 2010

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

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

1 голос
/ 28 мая 2010

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

0 голосов
/ 26 октября 2012

Что бы это ни стоило, Служба обновления 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);
        }
    }
0 голосов
/ 27 мая 2010

Что бы я сделал:

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