RCW и подсчет ссылок при использовании COM-взаимодействия в C#

У меня есть приложение, которое использует сборки взаимодействия Office. Мне известно о "Runtime Callable Wrapper (RCW)", управляемом средой выполнения. Но я не очень уверен, как счетчик ссылок увеличивается. MSDN говорит,

RCW сохраняет только одну ссылку на обернутый COM-объект независимо от количества вызываемых им управляемых клиентов.

Если я правильно понимаю, на следующем примере,

using Microsoft.Office.Interop.Word;

static void Foo(Application wrd)
{
    /* .... */
}

static void Main(string[] args)
{
    var wrd = new Application();
    Foo(wrd);
    /* .... */
}

Я передаю экземпляр wrd в другой метод. Но это не увеличивает внутренний счетчик ссылок. Поэтому мне интересно, по каким сценариям счетчик ссылок увеличивается? Кто-нибудь может указать на сценарий, где счетчик ссылок увеличивается?

Также я читаю некоторый блог, в котором говорится, что не используйте двойные точки при программировании с COM-объектами. Что-то вроде, wrd.ActiveDocument.ActiveWindow, Автор утверждает, что компилятор создает отдельные переменные для хранения значений, которые увеличивают счетчик ссылок. ИМХО, это неправильно, и первый пример это доказывает. Это верно?

Любая помощь будет отличной!

5 ответов

Решение

Я тоже изучал этот вопрос, работая над приложением, ориентированным на COM/.Net-Interop, борясь с утечками, зависаниями и сбоями.

Краткий ответ: каждый раз, когда объект COM передается из среды COM в.NET.

Длинный ответ:

  1. Для каждого COM-объекта есть один RCW-объект [Тест 1] [Ссылка 4]
  2. Счетчик ссылок увеличивается каждый раз, когда объект запрашивается из COM-объекта (вызывая свойство или метод COM-объекта, который возвращает COM-объект, возвращаемый счетчик ссылок COM-объекта будет увеличиваться на единицу) [Тест 1]
  3. Счетчик ссылок не увеличивается путем приведения к другим интерфейсам COM объекта или перемещения ссылки RCW вокруг [Тест 2]
  4. Счетчик ссылок увеличивается каждый раз, когда объект передается как параметр в событии, вызванном COM [ссылка 1]

С другой стороны: вы должны ВСЕГДА освобождать COM-объекты, как только вы закончите их использовать. Передача этой работы в GC может привести к утечкам, неожиданному поведению и блокировке событий. Это в десять раз важнее, если вы обращаетесь к объекту не в потоке STA, в котором он был создан. [Ссылка 2] [Ссылка 3] [Мучительный личный опыт]

Я надеюсь, что я охватил все случаи, но COM - жесткое печенье. Приветствия.

Тест 1 - подсчет ссылок

private void Test1( _Application outlookApp )
{
    var explorer1 = outlookApp.ActiveExplorer();
    var count1 = Marshal.ReleaseComObject(explorer1);
    MessageBox.Show("Count 1:" + count1);

    var explorer2 = outlookApp.ActiveExplorer();
    var explorer3 = outlookApp.ActiveExplorer();
    var explorer4 = outlookApp.ActiveExplorer();

    var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer4);
    var count2 = Marshal.ReleaseComObject(explorer4);
    MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals);
}
Output:
Count 1: 4
Count 2: 6, Equals: True

Тест 2 - счетчик продолж.

private static void Test2(_Application outlookApp)
{
    var explorer1 = outlookApp.ActiveExplorer();
    var count1 = Marshal.ReleaseComObject(explorer1);
    MessageBox.Show("Count 1:" + count1);

    var explorer2 = outlookApp.ActiveExplorer();

    var explorer3 = explorer2 as _Explorer;
    var explorer4 = (ExplorerEvents_10_Event)explorer2;
    var explorerObject = (object)explorer2;
    var explorer5 = (Explorer)explorerObject;

    var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer5);
    var count2 = Marshal.ReleaseComObject(explorer4);
    MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals);
}
Output:
Count 1: 4
Count 2: 4, Equals: True

Источники, о которых я рассказываю в дополнение к моему опыту и тестированию:

1. Johannes Passing's - Правила подсчета ссылок RCW!= Правила подсчета ссылок COM

2. Эран Сэндлер - внутреннее устройство Callable Wrapper и распространенные ошибки

3. Эран Сэндлер - Маршал. Освободить объект и вращение процессора

4. MSDN - Runal Callable Wrapper

Я не видел код для RCW - даже не уверен, что он является частью SSCLI - но мне пришлось реализовать аналогичную систему для отслеживания времени жизни COM-объектов в SlimDX, и мне пришлось провести немало исследований в RCW. Это то, что я помню, надеюсь, это достаточно точно, но примите это с оттенком соли.

Когда система впервые видит указатель интерфейса COM, она просто идет в кэш, чтобы посмотреть, есть ли RCW для этого указателя интерфейса. Предположительно, в кеше будут использоваться слабые ссылки, чтобы не предотвратить завершение и сбор RCW.

Если для этого указателя есть действующая оболочка, система возвращает оболочку - если интерфейс был получен способом, который увеличил счетчик ссылок интерфейса, предположительно, система RCW вызовет Release() в этот момент. Он нашел живую обертку, поэтому он знает, что обертка является единственной ссылкой, и он хочет поддерживать ровно одну ссылку. Если в кэше нет живой обертки, она создает новую и возвращает ее.

Оболочка вызывает Release для нижележащего указателя (ов) COM-интерфейса из финализатора.

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

К сожалению, у меня нет хорошего понимания того, как система RCW обрабатывает генерацию прокси-объектов для отправки вещей через домены приложений или квартиры потоков; это не тот аспект системы, который мне нужно было скопировать для SlimDX.

Вам не нужно никакого специального лечения. Среда выполнения содержит только одну ссылку на COM-объект. Причина этого заключается в том, что GC отслеживает все управляемые ссылки, поэтому, когда RCW выходит из области действия и собирается, ссылка COM освобождается. Когда вы передаете управляемую ссылку, GC отслеживает ее для вас - это одно из самых больших преимуществ среды выполнения на основе GC по сравнению со старой схемой AddRef/Release.

Вам не нужно вручную вызывать Marshal.ReleaseComObject, если вы не хотите более детерминированного выпуска.

Принятое решение действительно, но вот некоторая дополнительная справочная информация.

RCW содержит одну или несколько внутренних ссылок на интерфейс COM-объекта для своего COM-объекта.

Когда RCW освобождает свой базовый COM-объект, либо из-за сбора мусора, либо из-за Marshal.ReleaseComObject() вызывая его, он освобождает все свои внутренние COM-объекты.

Здесь на самом деле существует много подсчетов ссылок, один из которых определяет, когда RCW.NET должен выпустить свои базовые интерфейсы COM-объектов, а затем каждый из этих необработанных интерфейсов COM имеет свой собственный счетчик ссылок, как в обычном COM.

Вот код, чтобы получить сырой COM IUnknown количество ссылок на интерфейс:

int getIUnknownReferenceCount(object comobject)
{
    var iUnknown = Marshal.GetIUnknownForObject(comObject);
    return Marshal.Release(iUnknown);
}

И вы можете получить то же самое для других COM-интерфейсов объекта, используя Marshal.GetComInterfaceForObject(),

В дополнение к способам, перечисленным в принятом решении, мы также можем искусственно увеличить счетчик ссылок.NET RCW, вызвав что-то вроде Marshal.GetObjectForIUnknown(),

Вот пример кода, использующего эту технику для получения счетчика ссылок RCW данного COM-объекта:

int comObjectReferenceCount(object comObject)
{
    var iUnknown = Marshal.GetIUnknownForObject(comObject);
    Marshal.GetObjectForIUnknown(iUnknown);
    Marshal.Release(iUnknown);
    return Marshal.ReleaseComObject(comObject);
}

Вам нужно позвонить Marshal.ReleaseComObject в вашей переменной wrd, чтобы освободить вашу ссылку на слово application.

Таким образом, если Word не отображается и вы закрываете приложение, исполняемый файл также будет выгружен, если вы не сделали его видимым для пользователя.

Другие вопросы по тегам