С# + COM Interop, детерминированный релиз

Объекты COM обычно имеют детерминированное уничтожение: они освобождаются при освобождении последней ссылки.

Как это обрабатывается в С# - COM Interop? Классы не реализуют IDisposable, поэтому я не вижу возможности запуска явного IUnknown:: Release.

Случайный тест показывает, что непринятые объекты COM собираются лениво (т.е. сборщик мусора запускает выпуск). Что мне делать для объектов OCM, которые должны быть выпущены агрессивно? (например, проведение больших или общих критических ресурсов)?

Исходная проблема: у нас есть приложение С#, сильно использующее библиотеку COM, и оно протекает как безумное. Похоже, что проблемы "между" С++ и С# -кодом (у нас есть доступ к обоим), но мы не можем сгладить его.

Ответ 1

Вы можете манипулировать ссылками COM-взаимодействия с помощью класса System.Runtime.InteropServices.Marshal. В частности, вы можете посмотреть Marshal.ReleaseComObject.

Ответ 2

Мы сильно пострадали от этого. Лучше не пытаться загружать слишком много ссылок interop в среду .NET. Кроме того, вы можете использовать API Marshal.ReleaseComObject, если вам нужно немедленно выпустить что-то.

Еще один хороший метод - реорганизовать ваш клиентский код для использования оболочек typafe вокруг кода взаимодействия - если у вас есть известная ссылка в коде для каждого взаимодействия RCW, это увеличивает вероятность того, что ссылка на interop будет GCed в своевременно. Основная проблема, которую она пытается избежать, - это "слишком много точек":

foo.bar.quux.xyzzy.groo(); // where foo, bar, quux and xyzzy are all COM references

Каждый из объектов между точками в вышеуказанном коде эффективно просачивается (возможно, не в конечном итоге в конечном итоге), поскольку у нас есть неявная ссылка на экземпляр. Вам нужно будет создать именованные ссылки для каждого из экземпляров, чтобы иметь возможность их очистить:

Foo foo;
Bar bar=foo.bar;
Quux quux=bar.quux;
Xyzzy xyzzy=quux.xyzzy;
xyzzy.groo();

Теперь, возможно, используйте runtime для освобождения ссылки:

ReleaseComObject(xyzzy); // etc...

Ответ 3

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

Здесь опция, которая использует деревья Expression, чтобы обсудить наше намерение, зафиксировав значение в каждом node - разрешив одну версию:

static class ComExample {
    static void Main()
    {
        using (var wrapper = new ReleaseWrapper())
        {
            var baz = wrapper.Add( () => new Foo().Bar.Baz );
            Console.WriteLine(baz.Name);
        }
    }
}
class ReleaseWrapper : IDisposable
{
    List<object> objects = new List<object>();
    public T Add<T>(Expression<Func<T>> func)
    {
        return (T)Walk(func.Body);
    }
    object Walk(Expression expr)
    {
        object obj = WalkImpl(expr);
        if (obj != null && Marshal.IsComObject(obj)
              && !objects.Contains(obj)) { objects.Add(obj); }
        return obj;
    }
    object WalkImpl(Expression expr)
    {
        switch (expr.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)expr).Value;
            case ExpressionType.New:
                NewExpression ne = (NewExpression)expr;
                object[] args = ne.Arguments.Select(arg => Walk(arg)).ToArray();
                return ne.Constructor.Invoke(args);
            case ExpressionType.MemberAccess:
                MemberExpression me = (MemberExpression)expr;
                object target = Walk(me.Expression);
                switch (me.Member.MemberType)
                {
                    case MemberTypes.Field:
                        return ((FieldInfo)me.Member).GetValue(target);
                    case MemberTypes.Property:
                        return ((PropertyInfo)me.Member).GetValue(target, null);
                    default:
                        throw new NotSupportedException();

                }
            default:
                throw new NotSupportedException();
        }
    }
    public void Dispose()
    {
        foreach(object obj in objects) {
            Marshal.ReleaseComObject(obj);
            Debug.WriteLine("Released: " + obj);
        }
        objects.Clear();
    }
}