C # + COM Interop, детерминированный релиз - PullRequest
12 голосов
/ 04 июня 2009

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

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

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

Исходная проблема: у нас есть приложение C #, интенсивно использующее библиотеку COM, и оно протекает как сумасшедший. Кажется, что проблема заключается «между» кодом C ++ и кодом C # (у нас есть доступ к обоим), но мы не можем его зафиксировать.

Ответы [ 3 ]

16 голосов
/ 04 июня 2009

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

5 голосов
/ 04 июня 2009

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

Другим хорошим методом является рефакторинг клиентского кода для использования безопасных типов вокруг кода взаимодействия - если в вашем коде есть известная ссылка на каждый RCW взаимодействия, это увеличивает вероятность того, что ссылка взаимодействия будет 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();

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

ReleaseComObject(xyzzy); // etc...
2 голосов
/ 03 февраля 2010

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

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

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