7

我的 COM 加载项有一个问题,拖了几个月,我不知道为什么。

IDTExtensibility2实现已经由 Carlos Quintero(MZ-Tools 背后的人)进行了同行评审,并被认为是正确的。

根据他的建议,OnBeginShutdown实现设置了一个已签入的标志OnDisconnection,以确保ShutdownAddIn只运行一次(某些 VBE 主机应用程序不调用OnBeginShutdown,这就是原因):

public void OnBeginShutdown(ref Array custom)
{
    _isBeginShutdownExecuted = true;
    ShutdownAddIn();
}

我的插件使用 Ninject 进行 DI/IoC,我的ShutdownAddIn方法归结为调用DisposeNinjectIKernel实例,然后释放所有 COM 对象Marshal.ReleaseComObject

private void ShutdownAddIn()
{
    if (_kernel != null)
    {
        _kernel.Dispose();
        _kernel = null;
    }
    _ide.Release();
    _isInitialized = false;
}

我想不出更早的时间来运行这段代码。然而,当在我的命令栏和菜单包装器上运行时,当命令栏/菜单尝试拆除它们的控件时Dispose,我会进入:InvalidCastExceptionStopEvents

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;
}

InvalidCastException说它不能转换为IConnectionPoint- 我发现这是因为当这段代码运行时,我的Target(包装的__ComObject)已经消失了,我留下了一个无效的指针和一个挥之不去的指针对不再存在的 COM 对象的引用。

如果我在我的拆卸过程中捕获所有异常(当我尝试使用按钮和菜单时,我有更多源于同一个根本问题的异常Delete),主机应用程序将关闭但主机进程仍然存在 - 然后我必须将其从任务管理器。这种行为与我认为由未删除的点击处理程序引起的内存泄漏是一致的。


有没有更强大的方法可以处理为Microsoft.Office.Core.CommandBarButton包装器添加/删除事件处理程序?OnBeginShutdown如果我还没有释放它们,为什么我包装的 COM 对象在运行时已经“消失”了?

4

1 回答 1

5

我可能错了,但我不认为 InvalidCastException 是因为某些 COM 对象消失了,在这种情况下,您会收到“无法使用已与其底层 RCW 分离的 COM 对象”。InvalidCastException 的意思是,一种类型不能转换为另一种类型,这不仅可能发生在类型全名不同的明显情况下,而且我在边缘情况下也看到过这种情况,例如

1)类型全名相同,但来自不同的程序集,甚至来自以某种方式从不同位置加载两次的同一个程序集。示例:使用 COM Shims 为 VBA 编辑器隔离基于 .NET 的插件中提到的案例 1

2) 类型全名相同但在同一个进程中加载​​到不同的CLR(2.0/4.0)。示例:显示工具窗口的 System.InvalidCastException(“无法将 'System.__ComObject' 类型的 COM 对象转换为类类型 System.Windows.Forms.UserControl”)的奇怪情况

我建议获取正在转换的类型的完整类型名称/程序集名称/CLR。添加对 Microsoft.VisualBasic 引用的临时引用允许您使用 Microsoft.VisualBasic.Information.TypeName(object) 来获取 __ComObject 后面的实际类型。

于 2016-11-30T22:06:13.000 回答