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

У меня есть приложение, использующее сборки взаимодействия 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 ]

43 голосов
/ 24 апреля 2011

Я тоже изучал этот вопрос, работая над приложением, ориентированным на 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

3 голосов
/ 11 января 2011

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

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

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

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

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

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

1 голос
/ 20 января 2017

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

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);
}
1 голос
/ 04 января 2011

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

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

0 голосов
/ 04 января 2011

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

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

...