Manière correcte de libérer des objects COM?

Parfois, lorsque je ferme l’application et qu’elle tente de libérer des objects COM, un message d’avertissement s’affiche dans le débogueur:

RaceOnRCWCleanUp été détecté

Si j’écris une classe qui utilise des objects COM, dois-je implémenter IDisposable et appeler Marshal.FinalReleaseComObject sur ces Marshal.FinalReleaseComObject dans IDisposable.Dispose pour les libérer correctement?

Si Dispose n’est pas appelé manuellement, dois-je quand même les libérer dans le finaliseur ou le GC les publiera-t-il automatiquement? Maintenant, j’appelle Dispose(false) dans le finaliseur, mais je me demande si cela est correct.

L’object COM que j’utilise possède également un gestionnaire d’événements que la classe écoute. Apparemment, l’événement est déclenché sur un autre thread. Comment le gérer correctement s’il est déclenché lors de l’élimination de la classe?

Sur la base de mon expérience avec différents objects COM (in-process ou out-of-process), je suggèrerais un Marshal.ReleaseComObject pour un franchissement de frontière COM / .NET (si, pour une instance, vous référencez un object COM afin de récupérer une autre référence COM. ).

J’ai rencontré beaucoup de problèmes simplement parce que j’ai décidé de reporter le nettoyage d’interopérabilité COM sur GC. Veuillez également noter que je n’utilise jamais Marshal.FinalReleaseComObject – certains objects COM sont des singletons et ne fonctionnent pas bien avec de tels objects.

Il est interdit de faire quoi que ce soit dans les objects gérés à l’intérieur du finaliseur (ou dans Dispose (false) de la célèbre implémentation IDisposable ). Vous ne devez pas vous fier à une référence d’object .NET dans le finaliseur. Vous pouvez libérer IntPtr , mais pas l’object COM, car il pourrait déjà être nettoyé.

Il y a un article ici à ce sujet: http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET

En un mot:

 1) Declare & instantiate COM objects at the last moment possible. 2) ReleaseComObject(obj) for ALL objects, at the soonest moment possible. 3) Always ReleaseComObject in the opposite order of creation. 4) NEVER call GC.Collect() except when required for debugging. 

Jusqu’à ce que GC se produise naturellement, la référence com ne sera pas entièrement publiée. C’est pourquoi tant de personnes doivent forcer la destruction d’objects à l’aide de FinalReleaseComObject () et GC.Collect (). Les deux sont nécessaires pour le code Interop sale.

Dispose n’est PAS appelé automatiquement par le GC. Lorsqu’un object est supprimé, le destructeur est appelé (dans un autre thread). C’est généralement à cet endroit que vous pouvez libérer de la mémoire non gérée ou des références com.

Destructeurs: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

… lorsque votre application encapsule des ressources non gérées telles que Windows, des fichiers et des connexions réseau, vous devez utiliser des destructeurs pour libérer ces ressources. Lorsque l’object est éligible à la destruction, le ramasse-miettes exécute la méthode Finalize de l’object.

Tout d’abord, vous ne devez jamais appeler Marshal.ReleaseComObject(...) ou Marshal.FinalReleaseComObject(...) lors de l’interopérabilité Excel. C’est un anti-modèle déroutant, mais toute information à ce sujet, y compris celle de Microsoft, qui indique que vous devez libérer manuellement les références COM à partir de .NET est incorrecte. Le fait est que le runtime .NET et le garbage collector assurent le suivi et le nettoyage des références COM.

Deuxièmement, si vous voulez vous assurer que les références COM à un object COM en dehors du processus sont nettoyées à la fin de votre processus (afin que le processus Excel se ferme), vous devez vous assurer que le récupérateur de place s’exécute. Vous faites cela correctement avec les appels à GC.Collect() et GC.WaitForPendingFinalizers() . Appeler deux fois est sûr, fin garantit que les cycles sont également nettoyés.

Troisièmement, lors de l’exécution sous le débogueur, les références locales seront artificiellement maintenues en vie jusqu’à la fin de la méthode (pour que l’inspection des variables locales fonctionne). Ainsi, les appels de GC.Collect() ne sont pas efficaces pour nettoyer des objects tels que rng.Cells de la même méthode. Vous devez séparer le code effectuant l’interopérabilité COM du nettoyage du GC en méthodes distinctes.

Le schéma général serait:

 Sub WrapperThatCleansUp() ' NOTE: Don't call Excel objects in here... ' Debugger would keep alive until end, preventing GC cleanup ' Call a separate function that talks to Excel DoTheWork() ' Now Let the GC clean up (twice, to clean up cycles too) GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() End Sub Sub DoTheWork() Dim app As New Microsoft.Office.Interop.Excel.Application Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add() Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1") app.Visible = True For i As Integer = 1 To 10 worksheet.Cells.Range("A" & i).Value = "Hello" Next book.Save() book.Close() app.Quit() ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed End Sub 

Il y a beaucoup de fausses informations et de confusion à propos de ce problème, y compris de nombreuses publications sur MSDN et StackOverflow.

Ce qui m’a finalement convaincu de regarder de plus près et de trouver le bon conseil, c’est ce post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ avec la recherche du problème avec les références maintenues en vie sous le débogueur sur une réponse StackOverflow.