2016-11-30 2 views
7

J'ai un problème avec mon complément COM qui traîne depuis des mois, et je ne peux pas comprendre pourquoi.Nettoyage des boutons CommandBar

La mise en œuvre IDTExtensibility2 a été examiné par des pairs Carlos Quintero (le gars derrière MZ-Tools) déjà, et jugé correct.

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

Mon complément:

Par ses recommandations, la mise en œuvre OnBeginShutdown définit un indicateur qui est vérifié dans OnDisconnection, pour assurer ShutdownAddIn fonctionne qu'une seule fois (certaines applications hôtes VBE n'appellent OnBeginShutdown, c'est pourquoi) utilise Ninject pour DI/IoC, et ma méthode ShutdownAddIn se résume à appeler Dispose sur le Ninject IKernel instance, puis libérer tous les objets COM avec Marshal.ReleaseComObject:

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

Je ne peux pas penser à un temps plus tôt pour exécuter ce code. Pourtant, quand Dispose fonctionne sur mes emballages commandbar et le menu, je reçois un InvalidCastException en StopEvents lorsque le CommandBar/menus essayer de démonter leurs contrôles:

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

Le InvalidCastException dit qu'il ne peut pas jeter à IConnectionPoint - et ce que j'ai trouvé est que la raison en est que lorsque ce code est exécuté, mon Target (l'enveloppé __ComObject) est déjà parti, et je suis parti avec un pointeur non valide et une référence persistante à un objet COM qui ne existe. Si j'attrape toutes les exceptions lancées pendant mon processus de démontage (j'ai plus d'exceptions provenant du même problème racine, quand j'essaie de Delete les boutons et les menus), l'application hôte se ferme mais le processus hôte reste - et alors je avoir à le tuer de Gestionnaire des tâches. Ce comportement est cohérent avec une fuite de mémoire causée par les gestionnaires de clics non supprimés, je pense.


est-il un moyen plus robuste que je peux faire face à l'ajout/suppression des gestionnaires d'événements pour un emballage Microsoft.Office.Core.CommandBarButton? Pourquoi mes objets COM enveloppés sont-ils déjà "partis" lorsque OnBeginShutdown s'exécute, si je ne les ai pas encore libérés?

Répondre

5

Je peux me tromper, mais je ne pense pas InvalidCastException est parce que certains objets COM est parti, vous recevrait "Objet COM qui a été séparé de son RCW sous-jacent ne peut pas être utilisé" dans ce cas. InvalidCastException signifie ce que cela signifie, qu'un type ne peut pas être converti en un autre type, et cela peut arriver non seulement dans le cas évident que les types sont différents, mais aussi dans les cas de bords tels que

1 Les noms complets de type sont les mêmes, mais proviennent d'assemblages différents ou même du même assemblage qui a été en quelque sorte chargé deux fois à partir d'emplacements différents. Exemple: cas 1 mentionné dans Isolating .NET-based add-ins for the VBA editor with COM Shims

2) Les noms complets de type sont les mêmes mais ont été chargés dans différents CLR (2.0/4.0) dans le même processus. Exemple: The strange case of System.InvalidCastException (“Unable to cast COM object of type ‘System.__ComObject’ to class type System.Windows.Forms.UserControl”) showing toolwindow

Je vous suggère d'obtenir les noms nom/assemblage de type complet/CLR des types en cours de coulée. Ajout d'une référence temporaire à la référence Microsoft.VisualBasic vous permet d'utiliser Microsoft.VisualBasic.Information.TypeName (objet) pour obtenir le type réel derrière un __ComObject.

+0

Des assemblages chargés à partir d'emplacements différents ... pourrait 'Rubberduck.VBEditor.dll' être vissés les choses ... edge case 1 semble être une réelle possibilité ici. –

+0

Cela semble bizarre de le dire, mais cela pourrait-il provenir de notre utilisation des bibliothèques interopérables de PIA de Microsoft au lieu de générer notre propre @ Mat'sMug? Une balle dans l'obscurité. https://www.mztools.com/articles/2012/MZ2012011.aspx – RubberDuck

+1

Je ne l'ai pas vu la nuit dernière, mais maintenant je vois qu'après avoir appelé handler.Invoke vous n'appelez pas Marshal.ReleaseComObject sur le ctrl .NET- côté RCW que vous recevez du côté COM dans le gestionnaire d'événements Target_Click. C'est une fuite. Ou si le wrapper CommandBarButton qui le reçoit en tant que paramètre l'appelle dans une méthode Dispose, vous devez appeler sa méthode Dispose dans le gestionnaire d'événements plutôt que de le laisser flottant jusqu'à la prochaine garbage collection. –