none
Instanz einer C++/CLI Klasse wird noch während eine ihrer Member ausgeführt wird finalisiert ! RRS feed

  • Frage

  • Hallo,

    wir haben ein sehr merkwürdiges Phänomen in einer gemischten C#, C++/CLI - Anwendung festgestellt :

    WinForms-Form (C# - Anwendung) instantiiert in einem Messagehandler (und ruft Member auf von)
      CDatensatzListeCLR diese wrappt und instantiiert
        CDatensatzListe (=CDialog-abgeleitete MFC-Klasse) diese instantiiert bei Click auf einen Button
          CDatensatzDialog (=CDialog-abgeleitete MFC-Klasse)

    Noch während die von der WinForm-Form aufgerufene CDatensatzListeCLR-Member läuft, kommt es zu einer SEHException und die Anwendung stürzt ab. Debuggen (was aber nur beschränkt möglich ist, weil das Problem nur im Release-Build, nicht im Debug-Build auftritt) ergab, daß da CDatensatzListeCLR.Finalize() aufgerufen wird, was zur Destruktion der ganzen enthaltenen anderen Klassen und dann zum Absturz führt.
    Das Ganze tritt aber nicht 100% reproduzierbar auf, sondern auf manchen Rechnern fast immer, auf anderen nur/erst dann, wenn man einige Zeit Mausbewegungen oä. ausführt.
    Ich vermute, daß das Verhalten dann auftritt, wenn der GC eine Aufräumaktion startet, daß er dann meint, es gäbe schon keine Referenz mehr auf die CDatensatzListeCLR-Instanz und deshalb die Instanz finalisiert.  Die Frage ist aber, wie das passieren kann, weil die im C#-Source instantiierte CDatensatzListeCLR - Instanz noch nicht out of scope ist, ja sogar eine Member dieser Klasse/Instanz grade noch ausgeführt wird, wenn der Absturz auftritt !

    Das Problem kann komischerweise umgangen werden, wenn am Ende des Message-Handlers der C#-Form, der die CDatensatzListeCLR-Instanz instantiiert hat, einfach nochmals eine Member der Instanz (als Dummy, tut so gut wie nix) aufgerufen wird; das scheint die Finalisierung der Instanz schon beim ersten Member-Aufruf zu verhindern.

    Die og. Konstruktion funktionierte bisher schon seit mehreren Jahren problemlos mit VS 2008, MFC9 und .NET 2.0
    In unserer neuen Programm-Version, wo das Problem auftaucht, haben wir demgegenüber auf VS 2010, MFC10 und .NET 4.0 umgestellt.
    Eine weitere wesentliche Änderung der neuen Version gegenüber der alten, wo das Problem nicht auftrat, besteht darin, daß im CDatensatzDialog, der ja ein MFC-Dialog ist, über CWinFormsControl einige .NET-Controls eingebunden sind, was aber ansonsten einwandfrei funktioniert.

    Ich kann mir das og. Verhalten nicht vernünftig erklären, außer als Bug; deshalb:

    Gibt es irgendwelche Erkenntnisse, daß ein solcher Bug in VS2010, MFC10 oder .NET4 existiert, der zu diesem Effekt führen kann ?
    Oder was könnte ich da falsch gemacht haben, daß dieses Problem auftritt ?

    Bin für jede Info/Hinweis dankbar.

    Werner

    Donnerstag, 31. März 2011 10:11

Antworten

  • privatevoid buttonEditAdresse_ButtonClick(object sender, DevExpress.XtraEditors.Controls.ButtonPressedEventArgs e)
    {
      CDatensatzlisteCLR f8info = newCDatensatzlisteCLR();
      String F8Ergebnis = "";
      if (f8info.CallF8(ref F8Ergebnis, "Auswahl Adresse")) buttonEditAdresse.Text = F8Ergebnis;

      /Anmerkung: noch während der CallF8-Aufruf läuft, also der Dialog angezeigt wird, tritt der Absturz auf

      /
    folgender Dummy-Source führt aber dazu, daß der Absturz noch während CallF8 nicht mehr auftritt:
    f8info.Dummy();
    }

    Wie kann es da sein, daß der GC noch während CallF8 ausgeführt wird meint, das Objekt sei nicht mehr erreichbar ?

    Was machst Du intern in "CallF8"?
    Du sagst, dass irgendwo noch eine Unamaned Methode aufgerufen wird... was übergibst Du da? Woher weiß der GC, dass Du das Objekt dort noch brauchst?

    Wenn in "CallF8" das "this" Objekt nach dem Aufruf zu Deine Unamanged-Methode nicht mehr verwendet wird, kann der GC das Objekt ja löschen, da es ja offensichtlich nicht mehr benötigt wird.

    Man müsste also tiefer bohren, was Du genau mit dem Objekt noch machst...

    Die Lösung hast Du ja auch schon gemacht. besser wäre allerdings GC::KeepAlive.

    Für Beispiele, die vielleicht auch bei Dirt zutreffen könnten:
    http://msdn.microsoft.com/de-de/library/system.gc.keepalive.aspx
    http://stackoverflow.com/questions/805769/convenient-way-to-call-gckeepalive-in-c-cli-scenarios

    Siehe auch:
    http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx


    Jochen Kalmbach (MVP VC++)
    • Als Antwort markiert WernerK Donnerstag, 31. März 2011 15:54
    Donnerstag, 31. März 2011 11:55

Alle Antworten

  • Hallo WernerK!

    Noch während die von der WinForm-Form aufgerufene CDatensatzListeCLR-Member läuft, kommt es zu einer SEHException und die Anwendung stürzt ab. Debuggen (was aber nur beschränkt möglich ist, weil das Problem nur im Release-Build, nicht im Debug-Build auftritt) ergab, daß da CDatensatzListeCLR.Finalize() aufgerufen wird, was zur Destruktion der ganzen enthaltenen anderen Klassen und dann zum Absturz führt.

    Der GC erkennt, dass das Objekt nicht mehr verwendet wird und löscht es somit.

    Ohne Code kann man da schwer sagen, was Du da genau falsch gemacht hast.
    Beachte bitte: Der GC basiert nicht auf Scopes sondern auf "Erreichbarkeit"!

    Also, sobald es nicht mehr erreichbar ist, wird es gelöscht; unabhängig ob es im Scope noch vorhanden ist!

    Die Lösung wäre hier ganz am Ende der Methode ein "GC::KeepAlive(CDatensatzListeCLRInstanz);" einzufügen....
     Wie gesagt: Es hat nichts mit dem Scope zu tun!!!!
     Ansonsten kann ich ja mal eine Schulung über den GC machen ;) er funktioniert eben nicht so, wie man das aus C++ gewohnt ist...

    In .NET 4 hat sich der GC geändert, so dass er "aggressiever" löscht...


    Jochen Kalmbach (MVP VC
    Donnerstag, 31. März 2011 10:34
  • Hallo Jochen,

    vielen Dank für die schnelle Antwort.

    Daß der GC nicht so funktioniert wie man es von C++ gewohnt ist ist sogar bei mir bekannt ;-)
    Wahrscheinlich habe ich mich mit dem Begriff "Scope" etwas ungünstig ausgedrückt.

    Für mich ist die Frage, wieso sich der GC ganz offensichtlich irrt, bei seiner Annahme, das Objekt sei nicht mehr erreichbar; der Source-Code sieht wie folgt aus:

     

    private void buttonEditAdresse_ButtonClick(object sender, DevExpress.XtraEditors.Controls.ButtonPressedEventArgs e)
    {
     CDatensatzlisteCLR f8info = new CDatensatzlisteCLR();
     
    String F8Ergebnis = "";
     
    if (f8info.CallF8(ref F8Ergebnis, "Auswahl Adresse")) buttonEditAdresse.Text = F8Ergebnis;

     // Anmerkung: noch während der CallF8-Aufruf läuft, also der Dialog angezeigt wird, tritt der Absturz auf

     // folgender Dummy-Source führt aber dazu, daß der Absturz noch während CallF8 nicht mehr auftritt:
      

    f8info.Dummy();
    }

    Wie kann es da sein, daß der GC noch während CallF8 ausgeführt wird meint, das Objekt sei nicht mehr erreichbar ?

    Werner

    Donnerstag, 31. März 2011 11:29
  • privatevoid buttonEditAdresse_ButtonClick(object sender, DevExpress.XtraEditors.Controls.ButtonPressedEventArgs e)
    {
      CDatensatzlisteCLR f8info = newCDatensatzlisteCLR();
      String F8Ergebnis = "";
      if (f8info.CallF8(ref F8Ergebnis, "Auswahl Adresse")) buttonEditAdresse.Text = F8Ergebnis;

      /Anmerkung: noch während der CallF8-Aufruf läuft, also der Dialog angezeigt wird, tritt der Absturz auf

      /
    folgender Dummy-Source führt aber dazu, daß der Absturz noch während CallF8 nicht mehr auftritt:
    f8info.Dummy();
    }

    Wie kann es da sein, daß der GC noch während CallF8 ausgeführt wird meint, das Objekt sei nicht mehr erreichbar ?

    Was machst Du intern in "CallF8"?
    Du sagst, dass irgendwo noch eine Unamaned Methode aufgerufen wird... was übergibst Du da? Woher weiß der GC, dass Du das Objekt dort noch brauchst?

    Wenn in "CallF8" das "this" Objekt nach dem Aufruf zu Deine Unamanged-Methode nicht mehr verwendet wird, kann der GC das Objekt ja löschen, da es ja offensichtlich nicht mehr benötigt wird.

    Man müsste also tiefer bohren, was Du genau mit dem Objekt noch machst...

    Die Lösung hast Du ja auch schon gemacht. besser wäre allerdings GC::KeepAlive.

    Für Beispiele, die vielleicht auch bei Dirt zutreffen könnten:
    http://msdn.microsoft.com/de-de/library/system.gc.keepalive.aspx
    http://stackoverflow.com/questions/805769/convenient-way-to-call-gckeepalive-in-c-cli-scenarios

    Siehe auch:
    http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx


    Jochen Kalmbach (MVP VC++)
    • Als Antwort markiert WernerK Donnerstag, 31. März 2011 15:54
    Donnerstag, 31. März 2011 11:55
  • Hallo Jochen,

    da bin ich tatsächlich baff  :o)

    Ich wußte zwar schon bisher über die Finalizer/Destruktor/IDisposable-Problematik im Prinzip Bescheid und hatte die in meinen Wrapper-Klassen auch so berücksichtigt, so daß alle gewrappten unmanaged Objekte korrekt freigegeben werden.

    Aber daß der GC möglicherwiese eine Instanz dann schon als "nicht mehr verwendet" betrachtet und bereinigt, wenn noch die Ausführung einer Member-Funktion dieser Instanz läuft, damit hätte ich in meinen kühnsten Träumen nicht gerechnet ..

    Ich habe jetzt in den Members der Wrapper-Klasse an allen "gefährlichen" Stellen GC::KeepAlive(this) - Aufrufe eingefügt und den Dummy-Source wieder entfernt, und es scheint so jetzt tatsächlich ohne Abstürze zu funktionieren.

    Vielen Dank für deine "Aufklärung" !

    Werner

    Donnerstag, 31. März 2011 16:14
  • Gerne geschehen ;)


    Jochen Kalmbach (MVP VC++)
    Donnerstag, 31. März 2011 16:42