2009-06-04 7 views
12

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

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

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

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

ответ

16

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

+2

+1, потому что это спасло мою жизнь более чем один раз. – OregonGhost

5

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

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

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

ReleaseComObject(xyzzy); // etc... 
2

Это от related (but subtly different) question, но я думаю, что ответ довольно опрятный, поэтому я подумал, что это тоже необходимо добавить.

Вот вариант, который использует 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(); 
    } 
}