BUG: NullReferenceException Is Thrown by an Unmanaged Object After IVsaEngine.Reset (319799)



The information in this article applies to:

  • Microsoft Visual Studio for Applications SDK 1.0

This article was previously published under Q319799

SYMPTOMS

A managed Visual Studio for Applications (VSA) host creates an instance of an unmanaged object by using a Runtime Callable Wrapper (RCW). The host adds the unmanaged object as an AppGlobal object, by using IVsaEngine.Items.CreateItem and IVsaSite.GetGlobalInstance. When the host calls IVsaEngine.Reset, the RCW loses its connection to the unmanaged object. This causes a System.NullReferenceException error if the host tries to make a call on the RCW.

CAUSE

When an unmanaged VSA host adds an unmanaged AppGlobal object to the VSA engine, a RCW is automatically created for the object. A COM reference to the unmanaged object is maintained by the RCW until it is cleaned up by the Microsoft .NET garbage collector. Because .NET uses a "lazy" garbage-collection scheme, this reference may take some time to be released. This may prevent the unmanaged host application from quitting cleanly.

To resolve this issue, the IVsaEngine.Reset method calls Marshal.ReleaseComObject on any unmanaged AppGlobal objects that have been added to the engine. This decrements the reference count on the COM object and permits the host to quit cleanly. Unfortunately, this resolution does not work in the case of a managed VSA host that uses unmanaged AppGlobal objects. Because the managed host maintains the RCW for the unmanaged AppGlobal object, it is incorrect for VSA to call Marshal.ReleaseComObject.

RESOLUTION

There are two possible resolutions.

The first resolution is to write a managed wrapper class for the AppGlobal object, and then to pass that wrapper class to VSA. Because the custom wrapper class is not an RCW, VSA does not try to release it. This is the better solution, because you can also add .NET attributes (such as security attributes) if you must.

If you cannot write a custom wrapper class for your object, the second resolution is to take advantage of the manner in which RCW referencing works to keep an artificial reference count on the object that you give to VSA. When a COM interface is passed to managed code, the .NET runtime looks through the existing RCWs to determine if there is already an RCW for that object. If it finds that a RCW already exists, it adds a reference to the RCW instead of creating a new RCW. When Marshal.ReleaseComObject is called during the reset, the RCW is decremented and is not "zombied" for garbage collection.

To do this, add a method to your COM object that returns an interface pointer for that same object again. Pass this reference to the VSA engine in IVsaSite.GetGlobalInstance. When you call IVsaEngine.Reset, this additional reference is decremented, but the RCW keeps its connection to the unmanaged object.

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

MORE INFORMATION

Runtime Callable Wrapper Reference Counting

To take advantage of RCW reference counting, follow these steps:
  1. Add a property to the unmanaged COM object that returns an interface pointer to the COM object. In Microsoft C++, this might be similar to the following sample code:
    /************************************************************************
    *  get_SelfReference -- This method returns a reference to the TestObject.
    *  Parameters: pmySelf -- address of an IDispatch interface pointer
    *  Returns: Any value that is returned by IUnknown::QueryInteface
    ************************************************************************/ 
    STDMETHODIMP TestObject::get_SelfReference( IDispatch** pmySelf )
    {
       TRACE("TestObject::get_SelfReference\n");
       return this->QueryInterface( IID_IDispatch, (void**)&pmySelf );
    }
    					
  2. Modify the implementation of your VSA host to use the new SelfReference property. In Microsoft C#, your IVsaSite.GetGlobalInstance method might be similar to the following sample code:
    public System.Object GetGlobalInstance ( System.String name )
    { 
       //Check the name of the global instance.
       if (String.Compare( name, "Application" ) == 0)
       {
          //Your C# object is a managed object, so it can return a
          //direct reference to VSA.
          return this;
       }
       else if (String.Compare( name, "TestObject" ) == 0)
       {
          //TestObject is unmanaged, so it has a Runtime Callable Wrapper.
          //Return a new self-reference so that the RCW is refcounted.
          return m_TestObject.SelfReference;
       }
    
       //If the name is not found, throw an ItemInvalidName exception.
       else
          throw new VsaException( VsaError.ItemNameInvalid, name + 
             " undefined." );
    }
    					

REFERENCES

For more information about Visual Studio for Applications, visit the following MSDN Web site:

Modification Type:MajorLast Reviewed:5/16/2002
Keywords:kbbug kbDSupport KB319799