none
Should I call ReleaseComObject on an interface? RRS feed

  • Question

  • I have a VB .net project that gets a COM object (RCW) from a COM application. I have declared the variable as "Object" in my .net class.

    Once I get the object I have another local variable in a function that is declared as an interface that resides in the COM object's type lib and I do this:

    pTheInterface = pTheObject

    ... make a call via the interface

    System.Runtime.InteropServices.Marshal.ReleaseComObject(pTheInterface)

    Once I make the call to ReleaseComObject, I can no longer execute this code again:

    pTheInterface = pTheObject as I get a runtime exception telling me the underlying com object has ben separated from the RCW.

    When I examine the two RCWs in the debugger, pTheInterface and pTheObject, they appear to be two different RCWs.

    Should I be calling ReleaseComObject on an interface? If not, what is the explanation for not doing so (other than it causes the problem I see)?


    R.D. Holland

    Thursday, August 2, 2012 6:01 PM

Answers

  • The key here is knowing when a "COM interface pointer is mapped to it." A COM interface pointer is not mapped to a .NET RCW each time you reference it.

    The following method will NOT increment the reference count on myComObj so long as the method is called from managed code. If the method is called by unmanaged code (if it is made COM-visible and called from a COM object) then myComObj's RCW ref count will be incremented once and only once. The reason is that you aren't mapping new pointers to the COM interface, you're mapping managed pointers to other managed pointers. The only time you increment RCW ref counts is when you "cross the interop boundary"; in other words, it is incremented when you map an unmanaged pointer to a managed pointer or a managed pointer to an unmanaged pointer.

    private void Test( IComObject myComObj )
    {
     IComObject obj1, obj2, obj3;
     IComObject2 diffInterface;

      obj1 = myComObj;
      obj2 = myComObj;
      obj3 = obj1;
      diffInterface = (IComObject2)obj2;
    }

    • Proposed as answer by Jason R. Pike Saturday, August 4, 2012 1:53 PM
    • Marked as answer by RD Holland Saturday, August 4, 2012 3:29 PM
    Saturday, August 4, 2012 1:38 PM

All replies

  • You should not use the RCW after releasing the underline COM object - even indirectly.

    Check http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx.



    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful, so they will appear differently to other users who are visiting your thread for the same problem.
    Visual C++ MVP

    Thursday, August 2, 2012 6:23 PM
  • Are these two different RCWs or not?

    And if I understand the MSDN documentation on ReleaseComObject, the ref count on the RCW is increased "every time a COM interface pointer is mapped to it". Since I am explicitly declaring an interface (RCW) and setting it equal to my object (RCW), doesn't that imply the RCW's count would at least be two?


    R.D. Holland

    Thursday, August 2, 2012 6:49 PM
  • Depends on how COM server implements QueryInterface. If it returns the same pointer then the same RCW could be reused. If you want uniqueness to ensure you can use ReleaseComObject, use Marshal.GetUniqueObjectForIUnknown.


    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful, so they will appear differently to other users who are visiting your thread for the same problem.
    Visual C++ MVP

    Friday, August 3, 2012 12:29 AM
  • User's cannot be expected to know how a COM server implements QueryInterface (and for each IID at that). Only the IUnknown should be expected to be the same pointer returned from QI. It would appear that a user can only determine the correct calls to make by testing the code, and against each and every COM server that may be encountered at that!

    I did try out the GetUniqueObjectForIUnknown. That routine is definitely useful if a .net client wants to store an RCW and not worry about some other .net client in the same app domain making a call to ReleaseComObject or (especially) FinalReleaseComObject.


    R.D. Holland

    Friday, August 3, 2012 2:44 PM
  • That's why Microsoft thinks ReleaseComObject is too dangerous to be used in Visual Studio plugins...


    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful, so they will appear differently to other users who are visiting your thread for the same problem.
    Visual C++ MVP

    Friday, August 3, 2012 3:52 PM
  • And the way .NET interop is designed is why Office users need to manually shim their add-ins. I sure wish I understood why an RCW that becomes eligible for GC doesn't immediately release the underlying COM object and why .NET thinks every .net client needs to reuse an RCW used by another client.

    I did find that if I obtained an interface from my "unique" RCW obtained from GetUniqueObjectForIUnknown and then called ReleaseComObject on that interface, the "unique" RCW becomes invalid. That is, it doesn't stay unique for long if you try and do something useful with the RCW :(


    R.D. Holland

    Friday, August 3, 2012 7:32 PM
  • R.D.,

    You can call ReleaseComObject on an instance of an object once for each time you get a reference to it from the COM side because it increments the RCW's internal reference count (this is separate from the COM object's reference count, which is only incremented once per RCW that is created.) I'll give some examples that will hopefully make it a little easier to understand what I'm describing.

    This is an advanced topic, so just be aware that this example only scratches the surface. I would recommend the following book (Adam Nathan's ".NET and COM: The Complete Interoperability Guide") if you're going to be doing a lot of COM Interop: http://www.amazon.com/NET-COM-Complete-Interoperability-Volume/dp/067232170X

    The example will be in C# since that is what I'm most familiar with, but the same can be applied to VB .NET.

    private void MyMethod( SomeCOMObject comObj )
    {
      // Do not release RCW parameter instances, like comObj, unless MyMethod() is called
      // by a COM object or the RCW ref count is explicitly incremented somewhere in the
      // method. The reason is that passing instances of COM object's RCWs from method
      // to method in .NET does not increment the RCW's ref count. 

      IComInterface1 interface1 = null;
      IComInterface2 interface2 = null;

      try
      {
        // Getting a property on a COM object increments the instance of "AnotherObject"'s
        // RCW ref count. It can be safely released.
        interface1 = comObj.AnotherObject;

        // Casting the same instance of a COM object to a different interface does not 
        // increment the RCW ref count, so it should not be released.
        interface2 = interface1 as IComInterface2;
      }
      finally
      {
         if( interface1 != null )
           Marshal.ReleaseComObject( interface1 );
      }

    }

    As mentioned in other posts, once you have released the RCW enough that its reference count goes to zero (Marshal.ReleaseComObject will return the RCW's count post-decrement), it will become separated from the underlying COM object and you will no longer be able to use it (unless you grab another reference from the COM side.)

    I hope this helps. 

    --Jason

    • Proposed as answer by Jason R. Pike Saturday, August 4, 2012 1:53 PM
    Saturday, August 4, 2012 1:24 PM
  • The key here is knowing when a "COM interface pointer is mapped to it." A COM interface pointer is not mapped to a .NET RCW each time you reference it.

    The following method will NOT increment the reference count on myComObj so long as the method is called from managed code. If the method is called by unmanaged code (if it is made COM-visible and called from a COM object) then myComObj's RCW ref count will be incremented once and only once. The reason is that you aren't mapping new pointers to the COM interface, you're mapping managed pointers to other managed pointers. The only time you increment RCW ref counts is when you "cross the interop boundary"; in other words, it is incremented when you map an unmanaged pointer to a managed pointer or a managed pointer to an unmanaged pointer.

    private void Test( IComObject myComObj )
    {
     IComObject obj1, obj2, obj3;
     IComObject2 diffInterface;

      obj1 = myComObj;
      obj2 = myComObj;
      obj3 = obj1;
      diffInterface = (IComObject2)obj2;
    }

    • Proposed as answer by Jason R. Pike Saturday, August 4, 2012 1:53 PM
    • Marked as answer by RD Holland Saturday, August 4, 2012 3:29 PM
    Saturday, August 4, 2012 1:38 PM
  • Jason,

    Thanks for the replies. I got Nathan's original book after he mentioned it after I opened a case with MS support when .NET first came out. No doubt it is very useful.

    This is indeed what I have observed. Obtaining an interface pointer from an RCW does not increment the reference count on the RCW from which I obtained the interface. Calling RCO on either the original RCW or the RCW for the interface results in a return value of zero and both RCWs are disconnected. It is hard (impossible?) to tell whether the RCWs are identical or not, even with the debugger since so much is hidden from the user. And of course getting the IUnknown (IntPtr) from either RCW will give the same value, as it should since my COM object is the same object behind each RCW.

    FYI, I'm trying to make sure I can control the RCW lifetimes, so I can force them to call Release on my COM objects in a timely fashion. Hence I am observing the impact various managed code segments have on my COM objects. When my COM server detects that either .NET clr 2 or 4 is injected into my process by the OS I have to force GC to cleanup all unreferenced RCWs in our document when it is closed. When I force GC to run, I want to make sure I understand why any outstanding reference (via a RCW) does not call Release on the actual COM object. In particular, I want to know whether my routine that runs GC in each .net runtime forces each and every RCW eligible for collection to be collected so that the finalizers release my objects.  I have to delete all the com objects when we close a document and if an RCW is not collected before I do that but gets collected later, I find that I can trap exceptions in my process originating deep down in the .net runtime. The exceptions are handled by .net (now but they were not in .net 1.1 as far as I remember) but that is only after attempting to interact with the COM object that has been unmapped from memory. I have observed access violations or even branching into the middle of "nowhere" - in the middle of a valid code block or an invalid code region (???? in the disassembly window).

    Next up, what to do with ghost RCWs, if anything :)


    R.D. Holland

    Saturday, August 4, 2012 3:29 PM
  • Jason,

    Thanks for the replies. I got Nathan's original book after he mentioned it after I opened a case with MS support when .NET first came out. No doubt it is very useful.

    This is indeed what I have observed. Obtaining an interface pointer from an RCW does not increment the reference count on the RCW from which I obtained the interface. Calling RCO on either the original RCW or the RCW for the interface results in a return value of zero and both RCWs are disconnected. It is hard (impossible?) to tell whether the RCWs are identical or not, even with the debugger since so much is hidden from the user. And of course getting the IUnknown (IntPtr) from either RCW will give the same value, as it should since my COM object is the same object behind each RCW.

    FYI, I'm trying to make sure I can control the RCW lifetimes, so I can force them to call Release on my COM objects in a timely fashion. Hence I am observing the impact various managed code segments have on my COM objects. When my COM server detects that either .NET clr 2 or 4 is injected into my process by the OS I have to force GC to cleanup all unreferenced RCWs in our document when it is closed. When I force GC to run, I want to make sure I understand why any outstanding reference (via a RCW) does not call Release on the actual COM object. In particular, I want to know whether my routine that runs GC in each .net runtime forces each and every RCW eligible for collection to be collected so that the finalizers release my objects.  I have to delete all the com objects when we close a document and if an RCW is not collected before I do that but gets collected later, I find that I can trap exceptions in my process originating deep down in the .net runtime. The exceptions are handled by .net (now but they were not in .net 1.1 as far as I remember) but that is only after attempting to interact with the COM object that has been unmapped from memory. I have observed access violations or even branching into the middle of "nowhere" - in the middle of a valid code block or an invalid code region (???? in the disassembly window).

    Next up, what to do with ghost RCWs, if anything :)


    R.D. Holland

    R.D.,

    Calling garbage collection should decrement the RCW's reference count for all references to the RCW that are no longer rooted. That said, if you have .NET objects that are holding a reference to the RCW and are rooted, the RCW count will be greater than one and the COM Object will not be released by the RCW. Also, you should keep in mind that any of the .NET objects that have finalizers implemented will require a minimum of two garbage collections to be collected (the first garbage collection will put the object on the F-Reachable Queue and the second time to collect the object on that is now on the F-Reachable Queue. 

    As far as ghost RCWs are concerned, there are cases when they need to be released. I currently have an issue open on the forum about this. Perhaps you could take a look; I'd really appreciate it if you were able to make any suggestions for me. I've included links to some really good discussions about ghost RCWs in this thread:

    Why does the "Embed Interop Types" option not generate the appropriate DispIdAttributes in some cases?

    Thanks!

    --Jason


    Sunday, August 5, 2012 7:28 PM