PRB: The Garbage Collector Does Not Release a COM Object That Handles Events as You Expect (827417)



The information in this article applies to:

  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 1.0
  • Microsoft Common Language Runtime (included with the .NET Framework 1.1)
  • Microsoft Common Language Runtime (included with the .NET Framework) 1.0

SYMPTOMS

The garbage collector does not release the Component Object Model (COM) object that handles events as you might expect. For example, you create a COM object and then you add an event-handler to your COM object. You remove this event-handler from your COM object. Next, you set the reference to your COM object not to refer to an object by using null in Microsoft Visual C# .NET or by using Nothing in Microsoft Visual Basic .NET. If you run the garbage collector and then you wait for the thread that processes the queue of finalizers to empty the queue, you might expect the garbage collector to release your COM object. However, the garbage collector does not release your COM object.

CAUSE

You can handle unmanaged COM events by using the connection point event model. You can handle managed events by using the delegate model. A COM object in managed code uses synchronization objects that have finalizers to wire COM events to their corresponding managed event-handlers. Therefore, the garbage collector releases such a COM object only after it releases the associated synchronization objects.

When you run the garbage collector for the first time after it considers releasing a synchronization object, the garbage collector does not release the synchronization object because the synchronization object has a finalizer. Instead, the garbage collector adds the synchronization object to the queue of finalizers. You might wait for the thread that processes the queue of finalizers to empty the queue. However, the garbage collector releases your synchronization object only when you run the garbage collector again. Therefore, even if you wait for the thread that processes the queue of finalizers to empty the queue, the garbage collector does not release the associated COM object, and the problem that is described in the "Symptoms" section of this article occurs.

WORKAROUND

To work around this problem, run the garbage collector again, and then wait for the thread that processes the queue of finalizers to empty the queue. To do this, follow these steps:

Note The following procedure is based on the sample from the "More Information" section of this article. The code and the file names in this procedure may differ from your code and from your file names.
  1. Quit your application.
  2. In Visual C# .NET, locate the following code in the Form1.cs file:
    GC.WaitForPendingFinalizers();
    In Visual Basic .NET, locate the following code in the Form1.vb file:
    GC.WaitForPendingFinalizers()
  3. In Visual C# .NET, add the following code after the code that you located in step 2:
    // Run the garbage collector again.
    GC.Collect();
    // Wait for the thread that processes the queue of finalizers to empty the queue.
    GC.WaitForPendingFinalizers();
    In Visual Basic .NET, add the following code after the code that you located in step 2:
    ' Run the garbage collector again.
    GC.Collect()
    ' Wait for the thread that processes the queue of finalizers to empty the queue.
    GC.WaitForPendingFinalizers()
  4. Build your application, and then run your application.

    By default, Form1 is displayed.
  5. Click MyButton.

    Your application breaks at the set breakpoint when the garbage collector releases your COM object.

STATUS

This behavior is by design.

MORE INFORMATION

Steps to Reproduce the Behavior

  1. Start Microsoft Visual Studio .NET.
  2. Create a Microsoft Visual C++ Active Template Library (ATL) project that is named MyCOMServer.

    Note To do this, click the Dynamic-link library (DLL) option under Server type on the Application Settings page of the ATL Project Wizard.
  3. Add an ATL Simple Object that is named MyCOMClass to the MyCOMServer project.

    By default, the MyCOMClass.h file and the MyCOMClass.cpp file are added to the MyCOMServer project.
  4. Locate the following code in the MyCOMClass.h file:
    // CMyCOMClass
  5. Add the following code before the code that you located in step 4:
    // _IMyCOMClassEvents
    
    [
    	dispinterface,
    	uuid({E07F3BEA-9460-42fc-9272-56AF9C2D4F21}),
    	helpstring("_IMyCOMClassEvents Interface")
    ]
    
    __interface _IMyCOMClassEvents
    {
    	[id(1), helpstring("Close method")] HRESULT Close(void);
    };
  6. Locate the following code in the MyCOMClass.h file:
    vi_progid("MyCOMServer.MyCOMClass"),
  7. Add the following code before the code that you located in step 6:
    event_source("com"),
  8. Locate the following code in the MyCOMClass.h file:
    DECLARE_PROTECT_FINAL_CONSTRUCT()
  9. Add the following code before the code that you located in step 8, and then build the MyCOMServer project:
    __event __interface _IMyCOMClassEvents;
  10. Add a new Microsoft Windows application that is named ManagedClient to your solution by using Visual C# .NET or by using Visual Basic .NET.

    By default, Form1 is created.
  11. Add a project reference to the MyCOMServer.dll file that you built in step 9 to the ManagedClient project.
  12. Add a Button control to Form1.

    By default, button1 is added to Form1 in Visual C# .NET or Button1 is added to Form1 in Visual Basic .NET.
  13. In Visual C# .NET, use the Properties window to modify the Text property of button1 to MyButton.

    In Visual Basic .NET, use the Properties window to modify the Text property of Button1 to MyButton.
  14. In Visual C# .NET, add the following code to the button1_Click event-handler:
    // Create a COM object.
    MyCOMServer.CMyCOMClassClass MyCOMObject = new MyCOMServer.CMyCOMClassClass();
    // Add an event-handler to your COM object.
    MyCOMObject.Close += 
       new MyCOMServer._IMyCOMClassEvents_CloseEventHandler(MyCOMObject_Close);
    // Remove the event-handler that you added from your COM object.
    MyCOMObject.Close -= 
       new MyCOMServer._IMyCOMClassEvents_CloseEventHandler(MyCOMObject_Close);
    // Set the reference to your COM object not to refer to an object.
    MyCOMObject = null;
    // Run the garbage collector.
    GC.Collect();
    // Wait for the thread that processes the queue of finalizers to empty the queue.
    GC.WaitForPendingFinalizers();
    
    MessageBox.Show("After GC.WaitForPendingFinalizers()");
    In Visual Basic .NET, add the following code to the Button1_Click event-handler:
    ' Create a COM object.
    Dim MyCOMObject As MyCOMServer.CMyCOMClassClass = New MyCOMServer.CMyCOMClassClass()
    ' Add an event-handler to your COM object.
    AddHandler MyCOMObject.Close, _
       New MyCOMServer._IMyCOMClassEvents_CloseEventHandler(AddressOf MyCOMObject_Close)
    ' Remove the event-handler that you added from your COM object.
    RemoveHandler MyCOMObject.Close, _
       New MyCOMServer._IMyCOMClassEvents_CloseEventHandler(AddressOf MyCOMObject_Close)
    ' Set the reference to your COM object not to refer to an object.
    MyCOMObject = Nothing
    ' Run the garbage collector.
    GC.Collect()
    ' Wait for the thread that processes the queue of finalizers to empty the queue.
    GC.WaitForPendingFinalizers()
    
    MessageBox.Show("After GC.WaitForPendingFinalizers()")
  15. In Visual C# .NET, add the following code to the Form1.cs file after the button1_Click event-handler:
    private void MyCOMObject_Close()
    {
    }
    In Visual Basic .NET, add the following code to the Form1.vb file after the Button1_Click event-handler:
    Private Sub MyCOMObject_Close()
    End Sub
  16. Set the ManagedClient project as the startup project of your solution.
  17. Activate unmanaged debugging for the ManagedClient project.
  18. In the MyCOMClass.h file, set a breakpoint on the FinalRelease function.
  19. Build your application, and then run your application.

    By default, Form1 is displayed.
  20. Click MyButton.

    You receive the following message:After GC.WaitForPendingFinalizers()Your application does not reach the set breakpoint. The garbage collector does not release your COM object.

REFERENCES

For additional information, click the following article number to view the article in the Microsoft Knowledge Base:

317866 INFO: Roadmap for Garbage Collection in the Microsoft .NET Framework

For more information, visit the following Microsoft Web sites:

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework
http://msdn.microsoft.com/msdnmag/issues/1100/gci/

Garbage Collection-Part 2: Automatic Memory Management in the Microsoft .NET Framework
http://msdn.microsoft.com/msdnmag/issues/1200/GCI2/


Modification Type:MajorLast Reviewed:10/2/2003
Keywords:kbManaged kbMemory kbide kbDebug kbCOMInterop kbEvent kbProgramming kbinterop kbGarbageCollect kbprb KB827417 kbAudDeveloper