Interfejs .NET/COM jest dobrze zaprojektowany i działa poprawnie. W szczególności .Barget Garbage Collector poprawnie śledzi odniesienia COM i poprawnie wypuści obiekty COM, gdy nie będą zawierały żadnych odniesień do środowiska wykonawczego. Zakłócenie liczby odwołań obiektu COM przez wywołanie Marshal.ReleaseComObject(...)
lub Marshal.FinalReleaseComObject(...)
jest niebezpiecznym, ale powszechnym wzorcem zapobiegającym. Niestety, niektóre złe rady wyszły z Microsoftu.
Twój kod .NET może poprawnie współdziałać z COM, ignorując wszystkie 5 reguł. Jeśli trzeba wywołać deterministyczne oczyszczanie obiektów COM, które nie są już przywoływane z środowiska wykonawczego, można bezpiecznie wymusić GC (i ewentualnie czekać na ukończenie finalizatorów). W przeciwnym razie nie musisz robić nic specjalnego w swoim kodzie, aby poradzić sobie z obiektami COM.
Jest jedno ważne zastrzeżenie, które mogło przyczynić się do pomyłki w kwestii roli śmieciarza. Podczas debugowania kodu .NET, zmienne lokalne sztucznie mają wydłużony czas życia do końca metody, w celu wsparcia oglądania zmiennych pod debuggerem. Oznacza to, że możesz nadal zarządzać odniesieniami do obiektu COM (a więc GC nie oczyści się) później niż oczekiwać od samego spojrzenia na kod. Dobrym sposobem obejścia tego problemu (który występuje tylko w debugerze) jest podział zakresu wywołań COM z wywołań czyszczenia GC.
Oto przykład kodu C#, który współdziała z programem Excel i czyści poprawnie. Możesz wkleić do aplikacji konsoli (wystarczy dodać odwołanie do Microsoft.Office.Interop.Excel):
using System;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace TestCsCom
{
class Program
{
static void Main(string[] args)
{
// 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 (repeat, until no more)
do
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
while (Marshal.AreComObjectsAvailableForCleanup());
}
static void DoTheWork()
{
Application app = new Application();
Workbook book = app.Workbooks.Add();
Worksheet worksheet = book.Worksheets["Sheet1"];
app.Visible = true;
for (int i = 1; i <= 10; i++) {
worksheet.Cells.Range["A" + i].Value = "Hello";
}
book.Save();
book.Close();
app.Quit();
// NOTE: No calls the Marshal.ReleaseComObject() are ever needed
}
}
}
zobaczysz, że proces Excel poprawnie wyłącza się, wskazując, że wszystkie obiekty COM zostały odpowiednio oczyszczone w górę.
VSTO nie zmienia żadnego z tych problemów - jest to po prostu biblioteka .NET, która owija i rozszerza natywny model obiektu Office COM.
Istnieje wiele fałszywych informacji i nieporozumień związanych z tym problemem, w tym wiele postów na MSDN i StackOverflow.
To, co w końcu przekonało mnie do bliższego przyjrzenia się i znalezienia właściwej porady, było tym razem https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ wraz ze znalezieniem problemu z referencjami utrzymywanymi przy życiu w ramach debuggera w odpowiedzi na StackOverflow.
Jednym wyjątkiem od tego ogólnego poradnika jest sytuacja, gdy model obiektów COM wymaga zwolnienia interfejsów w określonej kolejności. Opisane tutaj podejście GC nie daje kontroli nad kolejnością, w której obiekty COM są publikowane przez GC.
Nie mam żadnego odniesienia, aby wskazać, czy naruszałoby to umowę z Komisją. Ogólnie, oczekiwałbym, że hierarchie COM będą używać wewnętrznych odniesień, aby zapewnić właściwe zarządzanie zależnościami w sekwencji. Na przykład. w przypadku Excela można oczekiwać, że obiekt Range będzie zachowywał wewnętrzne odwołanie do nadrzędnego obiektu Worksheet, dzięki czemu użytkownik modelu obiektowego nie musi jawnie utrzymywać obydwu przy życiu.
Może się zdarzyć, że nawet aplikacje Office będą czułe na sekwencję zwalniania obiektów COM.Wydaje się, że jest jeden przypadek, gdy używane jest osadzanie OLE - patrz: https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/04/11/excel-ole-embedding-errors-if-you-have-managed-add-in-sinking-application-events-in-excel-2/
Możliwe byłoby stworzenie modelu obiektu COM, który nie powiedzie się, jeśli obiekty zostaną zwolnione w niewłaściwej kolejności, a współdziałanie z takim modelem COM byłoby wówczas możliwe. wymagają większej ostrożności i mogą wymagać ręcznej ingerencji w referencje.
Ale dla ogólnego współdziałania z modelami obiektów Office COM, zgadzam się z VS blog post nazywając "Marshal.ReleaseComObject - problem ukryty jako rozwiązanie".
COM jest COM, i od dzisiaj pozostaje COM. Nie sądzę, aby odpowiedzialność za uwolnienie zasobów od dewelopera. strona i jej użycie oraz administracja w .NET zmieniły się/ewoluowały w jakiś sposób, w każdym razie nie jestem niezarządzanym kodem gurú. Czy istnieje jednak dobry powód, dla którego stosujesz trzecią zasadę przez te wszystkie lata ?. 'FinalReleaseComObject' powinien uwolnić jednocześnie liczbę referencyjną RCW, unikając zbędnych pojedynczych wywołań do' ReleaseCOMObject'. Zobacz tę odpowiedź: http://stackoverflow.com/questions/1827059/why-use-finalreleasecomobject-instead-of-releasecomobject – ElektroStudios
@ElektroStudios, a nawet FinalReleaseComObject opublikuje wszystkie liczniki referencyjne RCW na raz, ale dla lepszego zarządzania, celu debugowania, Wolałem używać ReleaseComObject. Korzystanie z FinalReleaseComObject może szybko doprowadzić do awarii programu z komunikatem "Obiekt COM oddzielony od jego bazowej RCW". – ehh
Dlaczego w ogóle trzeba ręcznie zwolnić obiekty COM, zamiast polegać na GC, aby ostatecznie je wyczyścić? na przykład czy obiekty COM przechowują ograniczone zasoby, takie jak obsługa plików itp. – adrianm