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?
If you want to remove or hide a button from Command Bar, double-click on "CanonicalName" string and remove everything from its "Value data" field i.e. make it empty. It'll immediately remove the button from Command Bar.
To customize existing buttons in Command Bar, you’ll need to open Registry Editor. Type regedit in RUN or Start Menu search box and press Enter. 2. Now go to following key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell That’s the key which contains all buttons information which are shown in Command Bar. 3.
For example, if you want to rename or remove “ Include in library ” button, its associated key is “ Windows.includeinlibrary ” under “shell” key. So its easy to find the associated key for your desired Command Bar button. 4. Once you find the key, take ownership of it so that you’ll be able to modify it.
CommandBar controls are similar to their form counterparts and include buttons, text boxes, and combo boxes. The syntax to add a control to the command bar is simple: 1. The [Type] argument can contain any of the msoControl constants (msoControlButton, msoControlEdit, msoControlDropdown, msoControlComboBox, or msoControlPopup).
I may be wrong, but I don't think InvalidCastException is because some COM object is gone, you would receive “COM object that has been separated from its underlying RCW cannot be used” in that case. InvalidCastException means what it means, that a type cannot be converted to another type, and that can happen not only in the obvious case that the types full names are different, but also I have seen it in edge cases such as
1) The type full names are the same, but come from different assemblies or even from the same assembly that somehow was loaded twice from different locations. Example: case 1 mentioned in Isolating .NET-based add-ins for the VBA editor with COM Shims
2) The type full names are the same but have been loaded in different CLRs (2.0 / 4.0) in the same process. Example: The strange case of System.InvalidCastException (“Unable to cast COM object of type ‘System.__ComObject’ to class type System.Windows.Forms.UserControl”) showing toolwindow
I'd suggest to get the full type name/assembly names/CLR of the types being cast. Adding a temporary reference to Microsoft.VisualBasic reference allows you to use Microsoft.VisualBasic.Information.TypeName(object) to get the actual type behind a __ComObject.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With