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. Автор утверждает, что компилятор создает отдельные переменные для хранения значений, которые увеличивают счетчик ссылок. ИМХО, это неправильно и первый пример это доказывает. Это правильно?

Любая помощь была бы замечательной!


person Navaneeth K N    schedule 04.01.2011    source источник
comment
Marshall.AddRef и Marshall.Release возвращают новый счетчик ссылок для COM-объекта. Не уверен, насколько это точно, но, по крайней мере, вы можете проверить утверждение автора.   -  person Arseny    schedule 04.01.2011


Ответы (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-объекты, как только вы закончите их использовать. Если оставить эту работу сборщику мусора, это может привести к утечкам, неожиданному поведению и тупиковым ситуациям. Это в десять раз важнее, если вы обращаетесь к объекту не в потоке 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. Эран Сэндлер - Runtime Callable Wrapper Внутреннее устройство и распространенные ошибки

3. Эран Сэндлер - Marshal.ReleaseComObject and CPU Spinning

4. MSDN - вызываемая оболочка времени выполнения

person Arthur    schedule 24.04.2011
comment
Эти тесты не принимают во внимание тот факт, что на самом деле существует ДВА счетчика ссылок, реальный собственный счетчик COM-объекта (который должен увеличиваться CLR только один раз) и управляемый счетчик ссылок в RCW, который считает что-то вроде количества раз объект был передан из родного мира (т. е. сколько раз вы хотели бы вызвать ReleaseComObject). Ваш тест измеряет только последнее, что является лишь частичным ответом на вопрос. - person hypersw; 14.10.2015

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

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

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

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

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

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

person Community    schedule 11.01.2011

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

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

person codekaizen    schedule 04.01.2011
comment
Вопрос не в использовании Marshal.ReleaseComObject или чего-то подобного. Я пытался понять, как работает подсчет ссылок и в какой момент он будет увеличиваться. - person Navaneeth K N; 04.01.2011
comment
Точно. Если вы прочтете еще раз, то заметите, что я говорю именно это. Есть только одна ссылка на COM. Он никогда не увеличивается. Все отслеживание RCW выполняется GC. - person codekaizen; 04.01.2011

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

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

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

На самом деле здесь много счетчиков ссылок - один определяет, когда .NET RCW должен освободить свои базовые интерфейсы 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);
}
person Jurko Gospodnetić    schedule 20.01.2017

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

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

person Spence    schedule 04.01.2011
comment
спасибо, но это не ответ на вопрос. Я знаю о методах Marshal.ReleaseComObject и Marshal.FinalReleaseComObject. Мой вопрос был больше о том, как работает внутренний счетчик ссылок и в какой момент он увеличивается. - person Navaneeth K N; 04.01.2011