COM Runtime Callable Wrappers (RCWs) und GC.KeepAlive
-
Was meint ihr... ist das
GC.KeepAlive
im folgenden Code nötig?void Fun() { var rcw = CreateSomeComObject(); // Gibt direkt einen RCW (Runtime Callable Wrapper) eines COM Objekts zurück rcw.SomeMethod(); GC.KeepAlive(rcw); }
Dass das COM Objekt hier nicht deterministirsch released wird ist mir klar. Gehen wir einfach mal davon aus dass es "OK" ist, da das COM Objekt wirklich nur "aus Speicher besteht" - also ausser ein bisschen unmanaged Speicher keine unmanaged Resourcen belegt.
Die Frage ist ob hier wie bei
SafeHandle
dasGC.KeepAlive
entfallen kann.
-
Was machst du eigentlich immer für komische Sachen?
Warum brauchst du das KeepAlive überhaupt? Darf das Objekt nicht gleich nach SomeMethod sterben?
-
Mechanics schrieb:
Was machst du eigentlich immer für komische Sachen?
Denken
Mechanics schrieb:
Warum brauchst du das KeepAlive überhaupt? Darf das Objekt nicht gleich nach SomeMethod sterben?
Klar. Bloss nicht IN SomeMethod!
-
hustbaer schrieb:
Klar. Bloss nicht IN SomeMethod!
Also, ohne es zu wissen: Ich kann mir nicht vorstellen, dass der GC hardcore das Objekt plattmacht, wenn SomeMethod noch läuft. Schließlich müsste dann auch an die COM-Runtime das Signal "Objekt kann jetzt weg" rausgehen und die weiß ja, hey, da läuft noch was.
Solange du dir im Klaren darüber bist, dass bei GC.KeepAlive die Instanz halt bis zum Ende des Programms uncollected bleibt und das auch bei mehrmaligem Aufruf von Fun() kein Problem ist, dann würde ich sagen, schadet es zumindest nicht.
-
GPC schrieb:
Also, ohne es zu wissen: Ich kann mir nicht vorstellen, dass der GC hardcore das Objekt plattmacht, wenn SomeMethod noch läuft.
Bei CLR-Methoden macht der GC sowas schon, weil er weiß, was in der Methode passiert (cf. When does an object become available for garbage collection?).
Der Aufruf einer RCW-Methode ist für den GC ja nicht transparent. Da die Objektreferenz als Parameter mitübergeben wird und die Methode also bis zur Rückkehr des Aufrufs auf das Objekt verweisen kann, darf die Objektreferenz in der Zwischenzeit nicht freigegeben werden. Das gilt auch für sonstige referenzgezählte Funktionsargumente. Ich weiß jetzt allerdings nicht, ob der RCW das durch implizite
GC.KeepAlive()
-Aufrufe löst oder durch explizitesAddRef()
/Release()
auf den jeweiligen Argumenten.Leider erzeugt die Runtime die Implementierung von RCW-Methoden zur Laufzeit, so daß man sich den IL-Code nicht ohne weiteres ansehen kann. Mit .NET Native kann man aber neuerdings in P/Invoke-Code steppen:
.NET Native Deep Dive: Debugging into Interop Code
Vermutlich geht das auch für COM-Interop, so daß man mal nachschauen könnte, ob die Vermutung richtig ist.GPC schrieb:
Solange du dir im Klaren darüber bist, dass bei GC.KeepAlive die Instanz halt bis zum Ende des Programms uncollected bleibt
??
Bist du sicher, daß du richtig verstehst, wasGC.KeepAlive()
macht?
-
Ich hab auch schon mal in den .NET Code reindebuggt, allerdings ist es sehr unübersichtlich, wenn man sich nicht auskennt. Mir gings auch um COM Aufrufe, aber ich hab keine direkten Code Aufrufe gesehen. Ich bin dann in irgendeiner Klasse mit mehreren 10 000 Zeilen Code gelandet und die hatte zig Caching Ebenen und JIT Compiling usw., ich habs damals ziemlich schnell aufgegeben.
-
Also der Grund für meine Frage ist ...
https://msdn.microsoft.com/en-us/library/ms228970.aspxRemove GC.KeepAlive Calls
A significant amount of existing code either does not use KeepAlive when it should or uses it when it is not appropriate. After converting to SafeHandle, classes do not need to call KeepAlive, assuming they do not have a finalizer but rely on SafeHandle to finalize the operating system handles. While the performance cost of retaining a call to KeepAlive may be negligible, the perception that a call to KeepAlive is either necessary or sufficient to solve a lifetime issue that may no longer exist makes the code more difficult to maintain. However, when using the COM interop CLR callable wrappers (RCWs), KeepAlive is still required by code.Und dabei ist mir nicht ganz klar worauf sich das jetzt bezieht.
Ich hoffe auch dass es kein Problem gibt, da ich viele Dinge unter der Annahme programmiert habe dass es kein Problem gibt. Die meisten Stellen wo ich mit COM rummache werden trotzdem OK sein, weil ich COM Objekte normalerweise immer explizit mit
Marshal.ReleaseComObject
freigebe, was automatisch dazu führt dass sie bis dahin "reachable" bleiben und nicht zu früh collected werden können. SO 100% sicher bin ich mir dann aber auch nicht.Und... falls es diesbezüglich kein Problem gibt - was meint dann der oben zitierte Absatz?
Etwa wenn COM Objekte bei anderen COM Calls als Parameter mitgegeben werden? Das wäre auch übel, denn auch hier könnte und müsste die Runtime wissen was "richtig" wäre, und es entsprechend richtig machen.
-
audacia schrieb:
Der Aufruf einer RCW-Methode ist für den GC ja nicht transparent. Da die Objektreferenz als Parameter mitübergeben wird und die Methode also bis zur Rückkehr des Aufrufs auf das Objekt verweisen kann, darf die Objektreferenz in der Zwischenzeit nicht freigegeben werden. Das gilt auch für sonstige referenzgezählte Funktionsargumente. Ich weiß jetzt allerdings nicht, ob der RCW das durch implizite
GC.KeepAlive()
-Aufrufe löst oder durch explizitesAddRef()
/Release()
auf den jeweiligen Argumenten.Tjaaaah...
Ich hätte mir auch gedacht dass sowas einfach OK sein muss:class Test { public static void Run() { var foo = new Foo(); foo.DoStuff(); } } class Foo { public void DoStuff() { Native.DoStuff(m_someIntPtr); // BUG!: Hier fehlt ein GC.KeepAlive(this)! } ~Foo() { Native.DeleteNativeFoo(m_someIntPtr); } IntPtr m_someIntPtr; // ... }
Weil ich mir schon gedacht hätte... "Foo hat nen Finalizer, da wird der GC wohl schlau genug sein Foo nicht zu finalizen so lange noch Foo Methoden ausgeführt werden". Ja, nix. Pustekuchen. An der markierten Stelle gehört zwingendermassen ein
GC.KeepAlive(this)
hin, sonst kann der Finalizer aufgerufen werden währendNative.DoStuff
noch läuft.Und hier unter "Calling a COM API: Step by Step" kann ich auch nix finden woraus ich 100% sicher schliessen könnte dass das Objekt bis zum Abschluss des Aufrufs nicht collected werden kann.
----
GPC schrieb:
Solange du dir im Klaren darüber bist, dass bei GC.KeepAlive die Instanz halt bis zum Ende des Programms uncollected bleibt
Nene,
GC.KeepAlive(x)
ist einfach nur eine Möglichkeit zu erzwingen dass das Objektx
an der Stelle im Programm wo dasGC.KeepAlive(x)
steht noch reachable ist. Sofern es sonst keine Referenzen mehr gibt kann es sofort nach demGC.KeepAlive
collected werden. Das woran du vermutlich denkst ist dieGCHandle
Klasse.
-
hustbaer schrieb:
Also der Grund für meine Frage ist ...
https://msdn.microsoft.com/en-us/library/ms228970.aspxRemove GC.KeepAlive Calls
A significant amount of existing code either does not use KeepAlive when it should or uses it when it is not appropriate. After converting to SafeHandle, classes do not need to call KeepAlive, assuming they do not have a finalizer but rely on SafeHandle to finalize the operating system handles. While the performance cost of retaining a call to KeepAlive may be negligible, the perception that a call to KeepAlive is either necessary or sufficient to solve a lifetime issue that may no longer exist makes the code more difficult to maintain. However, when using the COM interop CLR callable wrappers (RCWs), KeepAlive is still required by code.Und dabei ist mir nicht ganz klar worauf sich das jetzt bezieht.
Oho, das ist ja bedenklich. Vielleicht ist es doch die Mühe wert, sich mal die RCW-Codegen anzusehen.
hustbaer schrieb:
Tjaaaah...
Ich hätte mir auch gedacht dass sowas einfach OK sein muss:
[...]
Weil ich mir schon gedacht hätte... "Foo hat nen Finalizer, da wird der GC wohl schlau genug sein Foo nicht zu finalizen so lange noch Foo Methoden ausgeführt werden". Ja, nix. Pustekuchen. An der markierten Stelle gehört zwingendermassen einGC.KeepAlive(this)
hin, sonst kann der Finalizer aufgerufen werden währendNative.DoStuff
noch läuft.Gut, aber ein
IntPtr
ist für den GC nur eine Zahl; das kann ich schon verstehen, warum er da keine Zurückhaltung zeigt. Wenn diese Zahl Zeigersemantik hat und also auf ein am Leben zu haltendes Objekt verweist, muß man halt mitGC.KeepAlive()
nachhelfen.
-
hustbaer schrieb:
Nene,
GC.KeepAlive(x)
ist einfach nur eine Möglichkeit zu erzwingen dass das Objektx
an der Stelle im Programm wo dasGC.KeepAlive(x)
steht noch reachable ist. Sofern es sonst keine Referenzen mehr gibt kann es sofort nach demGC.KeepAlive
collected werden. Das woran du vermutlich denkst ist dieGCHandle
Klasse.Du hast Recht, ich war gedanklich bei einer anderen Klasse, sorry.
-
KP
So leicht lasse ich mich nicht verunsichern wenn ich mir wo sicher bin. Und wäre ich mir nicht sicher gewesen, hätte es mir auch nicht geschadet nochmal nachzulesen.