views:

536

answers:

5

I'm stuck with .Net 1.1 application (i.e. I can not use the generics goodies from 2.0 for now), and I was trying to optimize some parts of the code. As it deals a lot with runtime callable wrappers, which need to be released, I ended up to create a utility method which loops until all references are released. The signature of the method is:

void ReleaseObject(object comObject)

After releasing all comObjects, I call GC.Collect and GC.WaitForPendingFinalizers (don't ask - anybody dealing with Office interop knows).

And ... as usual, I hit a corner case - if I do not assign the corresponding managed reference to null before the GC.Collect call, it does not cleanup properly.

So, my code looks like:

ReleaseObject(myComObject);
myComObject = null;
GC.Collect()
...

As, there are a bunch of xxx=null, I decided to put this in the util method, but as there is a difference between passing by reference, and passing a reference parameter, obviously I had to change the method to:

void ReleaseObject(out object comObject)
{
   //do release
   comObject = null;
}

and edit the caller to:

MyComClass myComObject = xxxx;
ReleaseObject(out myComObject);

This fails with a message: "Cannot convert from 'out MyComClass' to 'out object'"

While I can think of why it can be a problem (i.e. the reverse cast from object to MyComClass is not implicit, and there is no guarantee what the method will do), I was wondering if there is a workaround, or I need to stay with my hundreds assignments of nulls.

Note: I have a bunch of different COM objects types, thats why I need a "object" parameter, and not a type safe one.

+1  A: 

Why is it better to call a method than to just set the variable to null? They're both single line calls, and the latter is a lot simpler.

It does sound very odd that you need to set them to null in the first place though. Are these static variables, or instance variables whose values need to be released earlier than their containing object? If the variable is just a local variable which will go out of scope anyway, setting it to null shouldn't make any difference (in release).

Do the RCWs not implement IDisposable? If so, calling Dispose (preferably via a using statement) would be the best bet.

(After discussions in comments.)

These are local variables, which aren't referenced later in the method. That means the garbage collector will realise that they don't need to be treated as "root" references - so setting them to null shouldn't make any difference.

To answer the original question directly, however: no, you can't pass a variable by reference unless the method parameter is of exactly the same type, so you're out of luck here. (With generics it would be possible, but you've said you're limited to .NET 1.1.)

Jon Skeet
@Jon: =null is repeated operation - this refactor :) ; RCW are not IDisposable; it's Office interop specific that I need to force GC before end of the scope. As I said, I can leave it as is, but I was curious if there is a way to do it using "out".
Sunny
You'd be refactoring "setting variable to null repeatedly" into "making method call repeatedly" - where's the benefit?As for whether or not you need to do it - setting the variable to null doesn't force a GC. What happens (in release mode, not under the debugger, importantly) ... (continued)
Jon Skeet
if you call GC.Collect() without setting things to null? If they're local variables, it should be fine.
Jon Skeet
I'm calling the utility method anyway - to encapsulate ReleaseComObject, so no added load. Unfortunately, GC.Collect is not enough (all are local vars). I experience the problem only in Release build, and I detect it only indirectly (continues)
Sunny
i.e. because the client experience some difficulties until GC is called in another method. Setting the var to null solved the problem. I was just curious if this can be done with ref and/or out, not that it have to. Just interesting in a language (c#) perspective.
Sunny
If setting the variable to null solves the problem even though it wasn't referenced later in the method, in release mode, there's something very odd going on. A local variable which is no longer used isn't considered a GC root.
Jon Skeet
But no, to answer your question directly - you won't be able to use ref/out in this case. Not without one overload per RCW, anyway...
Jon Skeet
I have no edit rights yet, so please edit your answer to include the latest conclusions in your comments, so I can mark it as accepted answer. Thanks for the info.
Sunny
Incl. the GC root explanation, thanks.
Sunny
A: 

You should be calling Marshal.ReleaseComObject, which AFAIK was available in 1.1.

You probably mean "ref":

static void ReleaseObject(ref object comObject)
{
   if(comObject != null)
   {
     //do release
     comObject = null;
   }
}

[edit re comments] however, this will only work for untyped objects, so not much use without generics! Oh for C# 2.0...

Re the "ref"; if the variables are truly variables (meaning: method variables), then they will go out of scope shortly and get collected. The "ref" would only be useful to release fields. But to be honest, it would be simpler just to set them to null...

A typical COM pattern is:

SomeType obj = new SomeType();
try {
  obj.SomeMethod(); // etc
} finally {
  Marshal.ReleaseComObject(obj);
}
Marc Gravell
Yep, I use ReleaseComObject in a loop in the utility method. It's just a curiosity about how ref and out work, not the specific case, as I already have a solution :)
Sunny
Note I updated for "ref"
Marc Gravell
ref fails with similar error message during compilation.
Sunny
Ah, yes; it will, thinking about it. So you can't do that...
Marc Gravell
A: 

In my opinion you won't be able to set those objects to null in another method (BTW you would need to use ref parameter instead of out to make it working, anyway you would hit the same problem with "Cannot convert..." error.) I'd recommend to create and array of objects and than iterate through that array, calling the ReleaseObject method and setting those objects to null. Something like:

List garbagedObjects = new List();
garbagedObjects.Add(myComObject1);
garbagedObjects.Add(myComObject2);
...
foreach(object garbagedObject in garbagedObjects)
{
  ReleaseObject(garbagedObject);
  garbagedObject = null;
}
garbagedObjects = null;
GC.Collect();
...
Leopold Cimrman
Thanks, but this adds complexity - i.e. instead to refer to my objects by their name, I have to use indexes, which is more error prone.
Sunny
A: 

Take a look at Deterministic disposal of your COM objects in .Net.

Petar Repac
Thanks, interesting implementation, but not an answer to the question, as it is more about the ussage of ref an out, than RCW at all. I included the actual problem just to avoid questions like "Why you need it". See comments for Jon's answer.
Sunny
Also, the problem I'm trying to solve is not solved with this approach, as in the Dispose method it sets the "local" reference to null, not the reference in the caller (i.e. service variable in Main class). If you check after "using" block, service is still not null.
Sunny
But that shouldn't matter if the COM object itself has been disposed. Likewise a variable which isn't used any more shouldn't be a GC root anyway. That's why it sounds to me like something else is going on.
Jon Skeet
+2  A: 

Sunny, ref and out are a marshalling hints + contract to the compiler. Ref and out are a carryover to COM days - the marshalling hints for objects when sent over the wire / between processes.

The out contract

void foo( out MyClass x)
  1. foo() will set x to something before it returns.
  2. x has no value when foo() is entered, and you get a compiler error if you attempt to use x before setting it. (use of unassigned out parameter x)

The ref contract

void foo( ref MyClass x)
  1. ref allows changing the callers reference.
  2. x has to be assignable
    • you cannot cast something to an intermediate variable foo( ref (object) something)
    • x can not be a property

The reality of the last two points are likely to stop you doing what you're trying to do, because in effect they make no sense when you understand what references really are. If you want to know that, ask Jon Skeet (he wrote a book).

When marshalling ref, it says that in addition to the return value, bring back ref values as well. When marshalling out, it says don't bother sending the out value when the method is called, but remember to bring back the out value in addition to the return value.


DISCLAIMER DISCLAIMER DISCLAIMER

As others point out, something fishy is going on. It appears the brute-force code you are maintaining has some subtle bugs and suffers from coding by coincidence. The best solution is probably to add another layer of indirection. i.e. A wrapper to the wrapper class that ensures deterministic cleanup where you can write the messy code once and only once instead of peppering it throughout your codebase.


That said ..

Alternative 1

Ref won't do the trick unless you provide overloads for every type of (com) object you will call it with.

// need a remove method for each type. 
void Remove( ref Com1 x ) { ...; x = null; }
void Remove( ref Con2 x ) { ...; x = null; }
void Remove( ref Com3 x ) { ...; x = null; }

// a generics version using ref.
void RemoveComRef<ComT>(ref ComT t) where ComT : class
{
    System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
    t = null; 
}

Com1 c1 = new Com1();
Com2 c2 = new Com2();
Remove( ref c1 );
RemoveComRef(ref c2); // the generics version again.

Alternative 2

If you don't want to do that, return null from the Remove() method and cast back to the type of object.

class Remover
{
    // .net 1.1 must cast if assigning
    public static object Remove(object x)
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(x);
        return null;
    }

    // uses generics.
    public static ComT RemoveCom<ComT>(ComT t) where ComT : class
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
        return null;
    }   
}

Com1 c1 = new Com1();
Com2 c2 = new Com2();
c1 = (Com1)Remover.Remove(c1); // no reliance on generics
c2 = Remover.RemoveCom(c2); // relies on generics

* I added generic versions for comparison.

The above code has the effect that when looking at code you become suspicious when you see a call to Remove(x) without the assignment (making wrong code look wrong). You could even Grep through the codebase looking for calls to Remove where assignment doesn't take place.


DISCLAIMER - all the above is predicated on your needing to set the reference to null manually, which (normally) isn't necessary.

Robert Paulson
Now I have to make hard decision. You put a lot of effort in your answer, and good explanation of how ref works, but you refer to Jon as source, and he already provided an answer. So - points for you for the answer, but I'll mark Jon's as accepted. Thanks.
Sunny
Well, I said if you wanted to know more about references in general to ask Jon, not that I was expanding on his answer. I don't much care about rep anyways. Glad the answer was helpful.
Robert Paulson