I have an issue with my COM add-in that has been dragging for months, and I can't figure out why.
The IDTExtensibility2
implementation has been peer reviewed by Carlos Quintero (the guy behind MZ-Tools) already, and deemed correct.
Per his recommendations the OnBeginShutdown
implementation sets a flag that's checked in OnDisconnection
, to ensure ShutdownAddIn
only runs once (some VBE host applications don't call OnBeginShutdown
, that's why):
public void OnBeginShutdown(ref Array custom)
{
_isBeginShutdownExecuted = true;
ShutdownAddIn();
}
My add-in uses Ninject for DI/IoC, and my ShutdownAddIn
method boils down to calling Dispose
on the Ninject IKernel
instance, and then releasing all COM objects with Marshal.ReleaseComObject
:
private void ShutdownAddIn()
{
if (_kernel != null)
{
_kernel.Dispose();
_kernel = null;
}
_ide.Release();
_isInitialized = false;
}
I cannot think of an earlier time to run this code. Yet, when Dispose
runs on my commandbar and menu wrappers, I'm getting an InvalidCastException
in StopEvents
when the commandbar/menus try to dismantle their controls:
public void HandleEvents()
{
// register the unmanaged click events
((Microsoft.Office.Core.CommandBarButton)Target).Click += Target_Click;
}
public void StopEvents()
{
// unregister the unmanaged click events
((Microsoft.Office.Core.CommandBarButton)Target).Click -= Target_Click;
}
public event EventHandler<CommandBarButtonClickEventArgs> Click;
private void Target_Click(Microsoft.Office.Core.CommandBarButton ctrl, ref bool cancelDefault)
{
// handle the unmanaged click events and fire a managed event for managed code to handle
var handler = Click;
if (handler == null)
{
return;
}
var args = new CommandBarButtonClickEventArgs(new CommandBarButton(ctrl));
handler.Invoke(this, args);
cancelDefault = args.Cancel;
}
The InvalidCastException
says that it can't cast to IConnectionPoint
- and what I've found is that the reason for this is because when this code runs, my Target
(the wrapped __ComObject
) is gone already, and I'm left with an invalid pointer and a lingering reference to a COM object that no longer exists.
If I catch all exceptions thrown during my teardown process (I have more exceptions stemming from the same root problem, when I try to Delete
the buttons and menus), the host application closes but the host process remains - and then I have to kill it from Task Manager. This behavior is consistent with a memory leak caused by not-removed click handlers I think.
Is there a more robust way I can deal with adding/removing event handlers for a Microsoft.Office.Core.CommandBarButton
wrapper? Why would my wrapped COM objects be already "gone" when OnBeginShutdown
runs, if I haven't released them yet?