Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleaning up CommandBar buttons

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?

like image 404
Mathieu Guindon Avatar asked Nov 30 '16 19:11

Mathieu Guindon


People also ask

How to remove or hide a button from command bar?

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.

How to customize command bar buttons in Windows 10?

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.

How to rename or remove command bar button in Windows 10?

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.

How do I add a control to the command bar?

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).


1 Answers

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.

like image 161
Carlos Quintero Avatar answered Oct 19 '22 13:10

Carlos Quintero