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 das GC.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 explizites AddRef() / 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, was GC.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.aspx

    Remove 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 explizites AddRef() / 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ährend Native.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 Objekt x an der Stelle im Programm wo das GC.KeepAlive(x) steht noch reachable ist. Sofern es sonst keine Referenzen mehr gibt kann es sofort nach dem GC.KeepAlive collected werden. Das woran du vermutlich denkst ist die GCHandle Klasse.



  • hustbaer schrieb:

    Also der Grund für meine Frage ist ...
    https://msdn.microsoft.com/en-us/library/ms228970.aspx

    Remove 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 ein GC.KeepAlive(this) hin, sonst kann der Finalizer aufgerufen werden während Native.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 mit GC.KeepAlive() nachhelfen.



  • hustbaer schrieb:

    Nene, GC.KeepAlive(x) ist einfach nur eine Möglichkeit zu erzwingen dass das Objekt x an der Stelle im Programm wo das GC.KeepAlive(x) steht noch reachable ist. Sofern es sonst keine Referenzen mehr gibt kann es sofort nach dem GC.KeepAlive collected werden. Das woran du vermutlich denkst ist die GCHandle 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.


Log in to reply