Когда мне нужно вызвать Marshal.ReleaseComObject на интерфейс, запрошенный через COM в С#

Я работал с некоторыми интерфейсами DirectShow для воспроизведения цифрового ТВ (DVB-T) с использованием С# и DirectShow.Net. Я недавно столкнулся с ошибкой времени выполнения COM object that has been separated from its underlying RCW cannot be used.

Эта ошибка произошла в следующей строке:

_guideData = _transportInformationFilter as IGuideData;

_transportInformationFilter имеет тип IBaseFilter, объект COM, ранее назначенный через служебную функцию DirectShow.Net.

Я предположил, что ошибка вызвана тем, что _transportInformationFilter как-то преждевременно выпущена, и я проследил ее до следующего метода (обработка ошибок удалена):

private void AttachGuideDataEvent()
{
    IConnectionPoint connPoint = null;
    IConnectionPointContainer connPointContainer = null;
    try
    {
        connPointContainer = _transportInformationFilter as IConnectionPointContainer;
        if (connPointContainer == null) /* error */

        var guideDataEventGuid = typeof (IGuideDataEvent).GUID;
        connPointContainer.FindConnectionPoint(ref guideDataEventGuid, out connPoint);
        if (connPoint == null) /* error */

        int cookie;
        connPoint.Advise(this, out cookie);
        if (cookie == 0) /* error */    
        _persistIGuideDataEventCookie = cookie;
    }
    finally
    {
        if (connPointContainer != null)
            Marshal.ReleaseComObject(connPointContainer);
        if (connPoint != null)
            Marshal.ReleaseComObject(connPoint);
    }
}

Как я понял, connPointContainer = _transportInformationFilter as IConnectionPointContainer должен был привести к вызову QueryInterface в COM-объекте _transportInformationFilter и, следовательно, его нужно было бы разделить отдельно. Однако вызов Marshal.ReleaseComObject(connPointContainer) был виновником, вызывающим отрыв от _transportInformationFilter от его RCW; удаление этого вызова устраняет проблему.

Учитывая это, , в каких ситуациях мне требуется явно освобождать COM-объекты (используя Marshal.ReleaseComObject) в С#, чтобы избежать утечки ресурсов?

Ответ 1

Почти никогда. ReleaseComObject управляет счетчиком ссылок RCW, а не базовым объектом и не имеет прямого аналога с IUnknown.Release. Вы должны позволить CLR управлять своими QueryInterface 'ing и Release' ing.

У RCW есть счетчик ссылок, который увеличивается каждый раз, когда указатель интерфейса COM сопоставляется с ним. Метод ReleaseComObject уменьшает счетчик ссылок RCW. Когда счетчик ссылок достигает нуля, среда выполнения освобождает все свои ссылки на неуправляемый объект COM и выдает исключение System.NullReferenceException, если вы попытаетесь использовать объект дальше. Если один и тот же COM-интерфейс передается более одного раза из неуправляемого в управляемый код, счетчик ссылок на обертке увеличивается каждый раз, а вызов ReleaseComObject возвращает количество оставшихся ссылок.

...

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

От http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx

FYI, способ прямого вызова IUnknown.Release - Marshal.Release, а не ReleaseComObject.

Ответ 2

Я думаю, что нашел действительно законное обстоятельство для использования Marshal.ReleaseComObject. При написании excel addins в С# с использованием ExcelDNA я стараюсь использовать COM-взаимодействие из рабочих потоков и получать доступ к объектам автоматизации Excel, таким как "Приложение", "Рабочая книга" и т.д.

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