2.0 Differences in GC.Collect?

Are there any differences in GC.Collect between the 1.1 and 2.0 framework that can keep all objects available for GC from being collected

I have a COM application that detects when a .NET client is running in the application and if so, creates a .NET object I implemented that has its own "Collect" member function. The .NET object does this:

GC.Collect(GC.MaxGeneration);

GC.WaitForPendingFinalizers();

GC.Collect(GC.MaxGeneration);

With the 1.1 framework, the above approach was sufficient to cause any RCWs created for a .NET client that were no longer referenced explicitly in the .NET client code to be collected. That in turn allowed the RCWs to release my COM objects.

Now with 2.0, this no longer seems to work reliably.

This is causing major problems in my application when .NET 2.0 clients try to work with it.

I know about ReleaseComObject. Unfortunately all the VB 6 programmers that are moving to .NET do not. Nor may they realize that RCWs are created implicitly. For example, a VB user may choose to implement a subset of an event interface that is passed COM objects. If they did not implement a delegate for an event that has a COM object as input, a RCW is still created when I fire the event and the VB coder has no way to call ReleaseComObject (unless more code is added simply to call ReleaseComObject.)

My attempt to save VB .NET users from lifetime management issues seems to be kaput when running with the 2.0 framework.

I know relying on GC is a bad practice but it is totally impractical to rely on everyone calling ReleaseComObject all the time.

Which, by the way, makes me wonder why the RCW's do not implement Dispose where the COM object would be automatically released. The runtime could simply call Dispose on an RCW when it become GC eligible.

From the posts regarding Excel not closing and the number of times I see that the "fix" for a COM interop problem is to call ReleaseComObject, which can cause its own problems if the RCW happens to still be referenced somewhere else in the client code, it looks like implementing Dispose and having GC invoke it on RCW's when they become GC eligible sure would make life easier on .NET interop users as well as the COM based applications.



Answer this question

2.0 Differences in GC.Collect?

  • Wil Burton

    I have debugged this issue more than once. It appears to me that the root cause of the problem is that the CreateRCW function in mscorwks.dll add refs the COM object (fair enough) but the corresponding release occurs in RCW::Cleanup when Cleanup is called from the RCWCleanupList::ReleaseRCWListRaw, which is called during RunFinalizers when I force the garbage collection.

    So I have to run GC to (try to) get the RCW::Cleanup method to be invoked. I have debugged this with a VB .NET program that stored a COM object in instance data and also simply by declaring a local variable (object) in a method and setting the variable to a COM object. The local variable goes out of scope and the RCW behind it immediately is a candidate for GC.

    Dispose may not be the answer but by holding onto RCWs forever and a day causes major headaches. It is very easy for Microsoft to declare that calling ReleaseComObject is the "solution" to this problem. It is not so easy to implement. Perhaps the solution is as simple as not adding RCWs to the "RCWCleanupList" or at least calling Release on the COM object when an RCW is added to the cleanup list.

    As you indicate, using the GC is not reliable. However, calling ReleaseComObject can be just as dangerous since the caller must make sure that overrelease does not occur. For instance, just how many times must ReleaseComObject be called and when That depends on the code, and the coders, until now, have always been able to rely on the VB runtime to make object lifetime a non-issue.

    Your links are useful. I am sure that the numerous VB 6 users that are having VB .NET foisted on them are just waiting to learn more about debugging memory leak issues, which until now, they never had to deal with. As a professional programmer who has to deal with lifetime issues and debuggers, I find the link useful. I am not so sure about all the VB 6 users that mostly exist simply because of the ease of use of VB 6. But the link is only useful when there is a "leak". When the runtime adds an RCW to the cleanup list, that is not a leak, its a .NET runtime archetecture issue.

    Microsoft seems to have forgotten, or never understood, why VB was, until VB .NET came along, so popular with the non-programming professions. Then again, I may be wrong. After all, I hear that VB 6 been given an extended life (last I heard it will live until at least 2008) and I have to wonder why Microsoft has done that.

    My attempts to try and alleviate the lifetime issues for VB programmers is two-fold. First is to make VB .NET more like VB 6, as far as lifetime is concerned, when programming against our applications. The second is to try and avoid aborts when an RCW is released because that little RCW sitting there in the GC list may represent hundreds of megabytes of virtual memory and our application cannot wait for GC to (possibly) release our objects on its own good time. That is, when one of our documents is closed, we simply have to give up the memory it has consumed or we run the real risk that the user cannot open another document or manipulate the closed document (e.g, "file is in use" causes problems when a database application such as SharePoint tries to work with a document that has an RCW keeping it in use.)

    In my opinion, Microsoft is hurting itself here. This is not just a question of VB 6 users complaining. The perception is that .NET is unstable or just not a reliable platform when it comes to working with existing (COM) applications. I cannot count how many times I get a problem report that goes like this:

    I have a VB 6 application that works fine. I opened the project in VB .NET and the project converted. But when I run ...

    So how much time and money has been lost due to not calling ReleaseComObject Searching these forums gives a hint. And that is just a drop in the bucket.


  • eddy05

    The problem is even worse for our application. We offer a good number of event sets that have methods that we invoke and pass in the dispatch interface of some of our objects. VB 6 users only have to implement individual methods of the event interfaces they are interested in. The same is true for VB .NET and C# .NET.

    Like VB 6, the other methods not implemented are supplied in the event interface when a client connects to us and handled quietly by the .NET framework. That is, we fire an event and the VB 6 or now the .NET framework handles the incoming event call and if there is a delegate supplied by the client for the individual event the call is passed to the delegate. If there is no delegate, the call returns.

    However a wrapper object is always created by the .NET framework that holds a reference to the interface we passed in the event method. So even if the client code never "sees" a com wrapper object, one is created and then immediately discarded and available for GC. The result is that we now are locked in memory until .NET decides, if ever, to release our com object.

    So now every VB 6 programmer must implement every method of the event set simply to add a call to release the com objects. Now how many VB 6 programmers do you think actually go thru that effort after converting their code to VB .NET

    By constantly trying to force collection, at least with version 1 of the framework we have avoided foisting this really poor .NET design on hapless VB users that are rarely programmers by profession. But now with version 2 of the framework the GC manipulation by our software does not work the same and now customers have to live with enormous amounts of memory being locked by .NET (our documents can run into the gigabytes as they might be as simple as a design of a coffee maker or more complex like the design of an automobile or earth moving equipment). Or even a crash as .NET accesses components that we have recovered and reused the memory are eventually released (or attempted to be released) by .NET.

    Mostly now we strongly recommend that our customers stay away from .NET. They can beg borrow or steal VB6 or use a non .NET based Visual Studio solution. Plus there are other non-Microsoft products that can manipulate our software via automation.


  • Magos294963

    solved it....I think 3.0 should have 500-600 lines of code for ReleaseComObject

    everything less complicated from Microsoft would be nice surprise



  • Ian Jagger

    Hi RD

    To answer your original question, there have been a number of changes in the GC between v1.1 and v2.0. By relying on GC.Collect, WaitForPendingFainlizers, Collect, you were depending on the GC to always behave in the same way. The GC may have decided to more efficiently manage memory and not collect the objects you were expecting (which is why it's bad practice to rely on the GC). It's impossible to tell exactly what's going on from your brief description, but I can point you to some instruction to determine what may be holding your RCWs alive:

    http://blogs.msdn.com/ricom/archive/2004/12/10/279612.aspx

    Our guidelines are to explicitly release unmanaged resources manually whenever possible (in your case COM objects), because relying on the GC to do so is not deterministic (as you point out).

    About the Dispose method: the runtime does not call Dispose automatically (see my blog post about Dispose: http://blogs.msdn.com/clyon/archive/2004/09/21/232445.aspx), so adding the Dispose method to RCWs is no different than having a ReleaseComObject method; it still needs to be called manually by the user (except of course for "using" blocks).

    Hopefully the links I provided can help you further debug your issue.

    -Chris


  • Andrea N.

    I dont care about differences in framework versions.....in 1.0 my

    System.Runtime.InteropServices.Marshal.ReleaseComObject and gc.collect worked fine

    and now, in 2.0, I have 100 of Word docs open....my code....

    Dim word_server As New Word.ApplicationClass

    Dim xW As Microsoft.Office.Interop.Word.Document

    xW = word_server.Documents.Open(a(i))

    If xW.Revisions.Count > 1 Then xW.AcceptAllRevisions()

    'xW.Select()

    'word_server.Visible = True

    With word_server.Selection.Find()

    .Text = "some text"

    .Forward = True

    .Format = False

    .MatchCase = False

    .MatchWholeWord = False

    .MatchWildcards = False

    .MatchSoundsLike = False

    .MatchAllWordForms = False

    word_server.Selection.Find.Execute()

    If word_server.Selection.Find.Found Then

    Console.WriteLine(word_server.ActiveDocument.Paragraphs(1).Range.Text)

    End If

    End With

    xW.Save()

    If Not xW Is Nothing Then

    Do

    If System.Runtime.InteropServices.Marshal.ReleaseComObject(xW) = 0 Then

    Exit Do

    End If

    Loop

    End If

    If Not word_server Is Nothing Then

    Do

    If System.Runtime.InteropServices.Marshal.ReleaseComObject(word_server) = 0 Then

    Exit Do

    End If

    Loop

    End If

    System.Threading.Thread.Sleep(1000)

    xW = Nothing

    word_server = Nothing

    System.GC.Collect()



  • 2.0 Differences in GC.Collect?