2010-02-03 18 views
13

Considerare il seguente codice C# utilizzando un oggetto COM.Rilascio di oggetti COM temporanei

 

MyComObject o = new MyComObject; 
try 
{ 
var baz = o.Foo.Bar.Baz; 
try 
{ 
    // do something with baz 
} 
finally 
{ 
    Marshal.ReleaseComObject(baz); 
} 
} 
finally 
{ 
Marshal.ReleaseComObject(o); 
} 
 

Questo rilascerà gli oggetti COM o e baz, ma non gli oggetti temporanei returnd da o.Foo e o.Foo.Bar. Ciò può causare problemi quando questi oggetti contengono una grande quantità di memoria non gestita o altre risorse.

Una soluzione ovvia ma brutta sarebbe, per ingombrare ulteriormente il codice con try-finally e Marshal.ReleaseComObject. Vedere C# + COM Interop, deterministic release

Per aggirare il problema, ho creato una classe di supporto

 

class TemporaryComObjects: IDisposable 
{ 
public C T<C>(C comObject) 
{ 
    m_objects.Add(comObject); 
    return comObject; 
} 
public void Dispose() 
{ 
    foreach (object o in m_objects) 
    Marshal.ReleaseComObject(o); 
} 
} 
 

Usage:

 

using (TemporaryComObjects t = new TemporaryComObjects()) 
{ 
MyComObject o = t.T(new MyComObject); 
var baz = t.T(t.T(t.T(o.Foo).Bar).Baz); 
// do something with baz 
} 
 

Le mie domande: ci sono potenziali problemi con questo codice? Qualcuno ha una soluzione più elegante?

+0

(aggiunto un esempio utilizzando l'approccio albero di espressione) –

+1

@downvoter: si prega di lasciare un commento – Henrik

risposta

11

La mia più grande lamentela sarebbe il nome, T; Add potrebbe essere più illusrativo dell'uso. Aggiungerei anche il where T : class al metodo generico, ma la "API fluente" sembra utilizzabile. Sarei anche incline a appiattire leggermente il codice. Posso anche vedere alcuni modi di usare la Expression API di camminare un intero albero e catturare tutti i passaggi intermedi, ma non sarebbe banale - ma immagino:

using(var com = new SomeWrapper()) { 
    var baz = com.Add(() => new MyComObject().Foo.Bar.Baz); 
} 

dove si trova un albero di espressione e otteniamo gli intermediari automaticamente.

(anche, si potrebbe Clear() o null l'elenco di cui Dispose())


Come così:

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[] Walk(IEnumerable<Expression> args) 
    { 
     if (args == null) return null; 
     return args.Select(arg => Walk(arg)).ToArray(); 
    } 
    object WalkImpl(Expression expr) 
    { 
     switch (expr.NodeType) 
     { 
      case ExpressionType.Constant: 
       return ((ConstantExpression)expr).Value; 
      case ExpressionType.New: 
       NewExpression ne = (NewExpression)expr; 
       return ne.Constructor.Invoke(Walk(ne.Arguments)); 
      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(); 

       } 
      case ExpressionType.Call: 
       MethodCallExpression mce = (MethodCallExpression)expr; 
       return mce.Method.Invoke(Walk(mce.Object), Walk(mce.Arguments)); 
      default: 
       throw new NotSupportedException(); 
     } 
    } 
    public void Dispose() 
    { 
     foreach(object obj in objects) { 
      Marshal.ReleaseComObject(obj); 
      Debug.WriteLine("Released: " + obj); 
     } 
     objects.Clear(); 
    } 
} 
+0

Wow! Grazie mille per questa risposta dettagliata. Lo proverò sicuramente. – Henrik

+0

@ Henrik - aggiornato per aggiungere il supporto di chiamata del metodo –

+0

Grazie, funziona. Ho modificato leggermente il tuo codice per non rilasciare i campi. Questi saranno rilasciati nel metodo Dispose dell'oggetto contenente. Per esempio. var bar = com.Add (() => this.m_foo.Bar); non dovrebbe rilasciare m_foo. – Henrik

0

La soluzione di Marc Gravell non funziona con .Net 4. + perché di introduzione di Dynamic in COM anziché oggetto. Inoltre, quando si esegue il test con la COM di Excel, esiste un'eccezione con il costruttore che dice "Converti non supportato" (impostazione predefinita dell'interruttore di WalkImpl).

Ci sono altri limiti con le espressioni come nessuna proprietà indicizzate e nessun argomento facoltativo. Mai prima di codificare Expression non ho idea di come affrontare questi problemi.

non verrà compilato o eseguire:

using (var wrapper = new ComWrapper()) 
    { 
    var application = wrapper.Add(() => new Excel.Application()); 
    var workbook = wrapper.Add(() => application.Workbooks.Open(@"C:\MyExcel.xls")); 

    Excel.Range range = wrapper.Add(() => workbook.Sheets[1].UsedRange); 
    string value = wrapper.Add(() => range.Cells[1, 1]).Value2; 
    }