Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Тест 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 - RCW Reference Counting Rules!= Правила подсчета ссылок COM

2. Эран Сандлер - Внутренние и нарушающие обходные файлы, используемые для работы в режиме реального времени

3. Eran Sandler - Marshal.ReleaseComObject и CPU Spinning

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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);
}

Ответ 5

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

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