COM Shim 2.3.1.0 Bug Fixes
A customer (VSP) was using the COM Shim and identified a scenario where a bug in the shim code could cause the host application to remain in memory indefinitely – thanks, VSP for finding this and bringing it to our attention! Misha did some ninja debugging and pinpointed the problem.
The shim is described here and here.
The problem is that the ManagedAggregator.CreateAggregatedInstance method is taking in an IComAggregator object (the CConnectProxy object, which is the outer object in the aggregation), but is not releasing it. This is a problem because the object was passed from the native shim code to the managed aggregator code and wrapped as an RCW. If we don’t release the RCW, the CLR will potentially hold on to it (and the underlying COM object) until the corresponding AppDomain is unloaded. However, in the current implementation the AppDomain is unloaded when COM object’s reference counter reaches zero. This essentially constitutes a circular reference problem thus preventing the host from terminating cleanly. The fix is simple: in the finally block, add a call to Marshal.ReleaseComObject on the outer object:
public void CreateAggregatedInstance(
string assemblyName, string typeName, IComAggregator outerObject)
{
IntPtr pOuter = IntPtr.Zero;
IntPtr pInner = IntPtr.Zero;
try
{
pOuter = Marshal.GetIUnknownForObject(outerObject);
object innerObject =
AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
assemblyName, typeName);
pInner = Marshal.CreateAggregatedObject(pOuter, innerObject);
outerObject.SetInnerPointer(pInner);
}
finally
{
if (pOuter != IntPtr.Zero)
{
Marshal.Release(pOuter);
}
if (pInner != IntPtr.Zero)
{
Marshal.Release(pInner);
}
// FIX: Bug discovered after release of 2.3.1.0.
// We call ReleaseComObject on the outer object (ConnectProxy)
// to make sure we delete the RCW, and prevent the CLR from
// holding onto it indefinitely (and keeping the host alive).
Marshal.ReleaseComObject(outerObject);
}
}
While we were in the code, Misha identified another potential problem. There are scenarios where it is possible that the CConnectProxy::FinalRelease may not get called – for example, if you have some other add-in or automation client that connects to this shimmed add-in via the COMAddIns collection. Right now, the FinalRelease is implemented to clean up and unload the appdomain. To make sure this always happens, you can simply move all that code to the end of the OnDisconnection method instead:
HRESULT __stdcall CConnectProxy::OnDisconnection(
ext_DisconnectMode RemoveMode, SAFEARRAY **custom)
{
HRESULT hr = S_OK;
hr = m_pConnect->OnDisconnection(RemoveMode, custom);
if (SUCCEEDED(hr))
{
m_pConnect->Release();
m_pConnect = NULL;
}
// FIX: Bug discovered after release of 2.3.1.0.
// Move the code that releases the aggregated innner object
// from the CConnectProxy::FinalRelease to
// CConnectProxy::OnDisconnection.
// This ensures that the code gets called even if this add-in gets
// handed out via the COMAddIns collection to other consumers.
if (m_pUnknownInner)
{
m_pUnknownInner->Release();
}
if (m_pCLRLoader)
{
m_pCLRLoader->Unload();
delete m_pCLRLoader;
m_pCLRLoader = NULL;
}
return hr;
}
// FinalRelease will be the last thing called in the shim/add-in, after
// OnBeginShutdown and OnDisconnection.
void CConnectProxy::FinalRelease()
{
}
Thanks again to VSP for spotting the original problem, and to Misha for identifying the cause and the fix.
Comments
- Anonymous
February 26, 2009
PingBack from http://www.clickandsolve.com/?p=15284 - Anonymous
February 27, 2009
Andrew, thank you. This was very timely! I was working on a COM shim this week for Word, and noticed that Word would not always shutdown. I thought it was my Addin! Oh, and I was also controlling the AddIn via the COMAddIns collection. Again, very timely! - Anonymous
March 29, 2009
The comment has been removed - Anonymous
March 29, 2009
nedumaran - if it works on your dev machine, but not on another machine, then you've probably got something missing in the installation. How did you get it from one machine to the other? Did you just copy the files and manually register, or did you build a setup package? Remember that the shim must be registered as the add-in, and the add-in assembly and managedaggregator assembly must be in the same folder as the shim. See the shim documentation for notes about the setup package: http://msdn.microsoft.com/en-us/library/bb508939.aspx - Anonymous
March 31, 2009
Hi Andrew,Thanks for this. Any idea when an updated wizard will be available for download?I found the ConnectProxy.cpp template file and was able to modify it to have the fix above for the current wizard, but I've been unable to find a template file for the ManagedAggregator.cs file so I can modify that with the fix.Is there some file I can modify for that, so any shims I create until a wizard update is released don't require hand modification?Thanks,Ken Slovak - Anonymous
March 31, 2009
Hi Andrew,Ignore my inability to find ManagedAggregator.cs, I was looking on a machine where I must have had an incomplete installation of the wizard. I re-installed the current wizard and was able to find that file.Ken Slovak - Anonymous
March 31, 2009
Hi Ken - we don't have an ETA for the next version yet. It's likely we'll roll these bug fixes up into a bigger release (looking ahead to O14 and 64-bit). - Anonymous
July 20, 2009
Hi Andrew,Now that we're playing with Office 2010 are there any plans to provide a version of the COM shim wizard for 64-bit addins for Office 2010 applications?Thanks,Ken Slovak - Anonymous
July 20, 2009
Hi Ken - yes, this is on my list of things to do. I'm not sure when I'll get to it, but hopefully sometime in the next few weeks. - Anonymous
July 21, 2009
Thanks, Andrew, that's great news :)Ken Slovak