2016-11-30 66 views
7

我的COM加载项已经拖了好几个月了,我找不到原因。清理CommandBar按钮

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

按他的建议OnBeginShutdown实现设置是在OnDisconnection检查,以确保ShutdownAddIn只运行一次一个标志(有些VBE主机应用程序不叫OnBeginShutdown,这就是为什么):

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

我的外接使用Ninject的DI/IoC的,而我的ShutdownAddIn方法归结为对Ninject IKernel实例调用Dispose,然后释放所有的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对象已经“消失”了?

回答

5

我可能是错的,但我不认为InvalidCastException的是因为某些COM对象不在了,你会收到在这种情况下,“已从与其基础RCW分开,不能使用COM对象”。 InvalidCastException意味着一种类型不能转换为另一种类型,这种情况不仅可以发生在类型全称不同的明显情况下,而且我也可以在边缘情况下看到它,例如

1 )类型的全名是相同的,但来自不同的程序集,甚至来自相同的程序集,以某种方式从不同的位置加载两次。例如:Isolating .NET-based add-ins for the VBA editor with COM Shims中提到的情况1

2)类型全名相同,但在同一进程中已加载到不同的CLR(2.0/4.0)中。示例:The strange case of System.InvalidCastException (“Unable to cast COM object of type ‘System.__ComObject’ to class type System.Windows.Forms.UserControl”) showing toolwindow

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

+0

从不同位置加载的程序集......可能'Rubberduck.VBEditor.dll'被搞砸了......边缘案例1似乎是一个真正的可能性。 –

+0

这听起来很奇怪,但这可能源于我们使用微软的互操作PIA库而不是生成我们自己的@Mat'sMug?在黑暗中拍摄。 https://www.mztools.com/articles/2012/MZ2012011.aspx – RubberDuck

+1

昨晚我没有看到它,但现在我看到,在调用handler.Invoke后,你不是在ctrl上调用Marshal.ReleaseComObject .NET-您在Target_Click事件处理程序中从COM端接收到的RCW。这是一个泄漏。或者,如果将CommandBarButton包装器作为参数接收,它会在Dispose方法中调用它,您应该在事件处理程序中调用其Dispose方法,而不是在下一次垃圾回收之前将其保留为浮动状态。 –