Zugriff auf (Fenster)Objekt aus static Methode heraus



  • Na ich denke ein CF mit 2MB freien Speicher dürfte exotisch genug sein.

    Die Initialisierung des Objektes ist im Beispielcode nicht vollkommen richtig dargestellt (wurde mit einem Bitmapobjekt initialisiert). Ich habe den Quellcode aus dem Projekt jetzt nicht vollständig zusammenkopieren wollen.
    Das Objekt war an keinem Control sondern an einem Bitmap gebunden (das auch seine Gültigkeit auf gleiche weise verlor).

    Das Objekt war immer so lange gültig bis der GC aufräumte.

    Nein, Du musst sie nicht pinnen, das sollte so klappen. Problematisch wird es dann, wenn das Callback erst *nach* dem eigentlichen API-Aufruf verwendet wird (also wenn die API-Funktion Multithreading verwendet). In diesem Fall (und *nur* in diesem) muss man eine Instanz des Delegates im Speicher behalten. Pinnen muss man aber trotzdem nichts.

    Hier würde ich mich mit einem Beleg aus der MSDN wohler fühlen, sagt die MSDN ja nun aus, das der GC Objekte verschieben darf.

    Callback erst *nach* dem eigentlichen API-Aufruf verwendet wird (also wenn die API-Funktion Multithreading verwendet

    Hmm... Ok, das ist vermutlich das schlagende Argument. Es muss nicht unbedingt Multithreading sein, aber man kann die Callback ja bei einer anderen Gelegenheit nutzen, die ausserhalb des Lebenszyklus liegt.

    z.B. Trägt man den Callback ein der einen später bei einem weiteren Ereginis an etwas erinnert. Da muss IMHO kein Multighreading aktiv sein. (Ganz ganz blödes Beispiel: Man trägt z.B. ein globales OnError Handle ein um von der native DLL bei Fehlern benachrichtigt zu werden. Das ganze ist dann ausserhalb des Lebenszykluses des API Aufrufes, und das ist ja nicht so ungewöhnlich bei einem Callback 🤡

    Ein pauschales "Da muss nichts gepinnt werden" halt ich so für falsch und wöllte es , wenn überhaupt, Einschränken auf ein:

    Pinnen muss nur dann nicht sein, wenn die Callback ausschliesslich im Lebenszyklus des API Aufrufes liegt. (Denn an dieser Stelle hält das Marshalzeugs ja die Finger drauf).

    Wird die Callback nach dem Aufruf der API Funktion, die den CB übergeben bekommen aufgerufen sollte man IMHO pinnen. Man läuft sonst gefahr, das bei einer GC Aktion der Zeiger nicht mehr stimmt. Auf ein "Unwarscheinlich weil PC hat massig Speicher" wöllte ich mich da nicht verlassen.

    (Thx für den Denkanstoss mit der DLL und dem innerhalb der API)



  • Knuddlbaer schrieb:

    Hier würde ich mich mit einem Beleg aus der MSDN wohler fühlen, sagt die MSDN ja nun aus, das der GC Objekte verschieben darf.

    Jupp, daher hatte ich ja den ms-help-URI gepostet. Ich weiß leider nicht, wie man den in die Online-MSDN konvertiert.



  • Baaaaa mitten in der Diskussion einen Beitrag weit oben editieren.... Tzzzzz =o)

    Hm, der Link geht bei mir nicht, hab die EN nicht installiert. Öffne das bitte noch mal bei Dir und Poste mal den Topic der Seite, damit lässt sich das dann per Google in der online MSDN finden. Danke Dir!

    Ist die MSDN nicht was feines ? So eindeutig =o)

    Noch mal für die, die auch Probleme mit Editierten Posts haben das Fazit der Diskussion:

    /EDIT: GRRRR, diese besch±%6&#ene MSDN widerspricht sich selbst! Also: ich hatte *doch* recht. Man muss sich *nicht* um den Delegate kümmern, der GC lässt ihn während des Aufrufs in Ruhe. Nur dann, wenn die unverwaltete DLL intern den Delegate speichert und später nochmal aufruft, muss man besagte Instanz anlegen.



  • Ok, das werde ich dann morgen nochmal überarbeiten.

    Bleibt jetzt noch die Frage, wo die Exception (s. in meinem letzten Post) herkommt und was sie bedeutet. Da rätsle ich nun echt noch dran. Weiss da jemand Rat?

    Das mit dem Pinnen tut meines Erachtens tatsählich Not. Mit meiner bisherigen Lösung bin ich nämlich schon abgeschmiert, weil der Aufruf ins Leere ging. War bei der Aufräumaktion der DLL der Fall, was beim Beenden des Programms erfolgt. Die DLL nutzt die Callback-Funktion ausserdem auch aus einem separatem Thread heraus. Kann also insgesmt nur gut sein.

    Ansonsten erst einmal vielen Dank für eure Mithilfe bisher...
    Wes 🙂



  • TypeCallback ist noch immer unbekannt. Poste mal bitte die Signatur. Ist ein wenig untergegangen in der Diskussion (die uns aber insgesammt weitergebracht haben dürfte 🤡



  • Ja, wir nähern uns irgendwie dem Ziel 😉

    Hier noch die fehlende die Deklaration:

    public delegate bool TypeCallback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg);
    


  • Ok, meine Callbackfunktion läuft nun non-static und wird auch ordnungsgemäß abgearbeitet. Doch das Ziel ist nicht erreicht, weil sich eine weitere Hürde auftürmt. Eigentzlich wollte ich die ganze Show ja nur implementieren, damit ich auf die Member des Objekts zugreifen kann.

    Dazu gehört eine Ausgabe in eine ListView namens LstReport. Doch dabei kommt nun folgende Exception:

    Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement 
    LstReport erfolgte von einem anderen Thread als dem Thread, für den es erstellt 
    wurde.
    

    Stimmt natürlich, denn die Callbackfunktion wird ja von einem in der DLL unabhängig gestarteten Thread aufgerufen.
    Wie kann ich das denn nun wieder umgehen?

    ("Sicherheit" ist manchmal echt ein Fluch... 😞 )



  • Control.BeginInvoke



  • Also ich hab das mal ausprobiert mit dem BeginInvoke. Allerdings löst es das Problem nicht. Auch wenn ich das Control über BeginInvoke mit einem Delegaten anspreche, bleibt die Exception die gleich.

    Vielleicht noch mal eine kurze Erklärung dazu:

    Mein Form lädt die DLL und übergibt eine Callback-Methode (in der Form definiert). In der DLL wird ein Thread gestartet, der bei Ereignissen die Callback-Methode aus der Form aufruft. Wir befinden uns also innerhalb der Callback-Methode in einem unabhängigen Thread.
    Jetzt möchte ich dort aber trotzdem mit den Controls der Form arbeiten. Das wird mir aber eben mit der benannten Exception verweigert.

    Darum hatte ich die BeginInvoke-Variante implementiert:

    namespace ACLibCs
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
          // Callback-Methode an DLL übergeben etc.
          :
          :
        }
    
        // Delegaten für ListView-Methoden definieren
        public delegate ListViewItem lvItemAddDeleg(ListViewItem value);
        public delegate void lvEnsureVisibleDeleg(int index);
    
        // Callback-Methode
        public bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg)
        {
          // LstReport ist eine ListView von Form1
          int cnt = LstReport.Items.Count;
          string colm1 = "für Spalte 1";
          string colm2 = "für Spalte 2";
    
          lvItemAddDeleg callerIA = new lvItemAddDeleg(LstReport.Items.Add);
          IAsyncResult threadResult1 = callerIA.BeginInvoke(new ListViewItem(new String[] { colm1, colm2 }), null, null);
          callerIA.EndInvoke(threadResult1);    //  Exception!!!
          //  Text:
          //    Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement
          //    LstReport erfolgte von einem anderen Thread als dem Thread, für den es erstellt
          //    wurde.
    
          lvEnsureVisibleDeleg callerEV = new lvEnsureVisible(LstReport.EnsureVisible);
          IAsyncResult threadResult2 = callerEV.BeginInvoke(cnt, null, null);
          callerEV.EndInvoke(threadResult2);
            :
            :
        }
      }
    }
    

    Abgesehen davon finde ich diese Verfahrensweise unglaublich umständlich. Das immer so zu handlen ist ein ganz schöner Mehraufwand.

    Wahrscheinlich werde ich wohl doch nicht umhin kommen, die Form als Objekt direkt mitzuliefern... Ob das allerdings die Exception wirklich umgeht??



  • Nachtrag:

    Mal noch eine Frage - gibt es unter C# nicht die Möglichkeit, Nachrichten an die Steuerelemente zu schicken, so wie das auch unter C++ möglich ist? Ich habe dazu bisher leider nix in meinem C#-Buch gefunden... Währe ja auch eine Lösung.



  • Deine Verwendung von BeginInvoke ist einfach falsch. Mal ein einfaches Beispiel:

    void Foo() {
        if (InvokeRequired) {
            BeginInvoke(new MethodInvoker(Foo));
            return;
        }
    
        int cnt = LstReport.Items.Count;
        string colm1 = "für Spalte 1";
        string colm2 = "für Spalte 2";
        LstReport.Items.Add(new ListViewItem(new String[] { colm1, colm2 }));
        LstReport.EnsureVisible(cnt);
    }
    
    public bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg) {
        Foo();
    }
    


  • Wesley67 schrieb:

    Mal noch eine Frage - gibt es unter C# nicht die Möglichkeit, Nachrichten an die Steuerelemente zu schicken, so wie das auch unter C++ möglich ist? Ich habe dazu bisher leider nix in meinem C#-Buch gefunden... Währe ja auch eine Lösung.

    Äh … da hast Du das Konzept wohl nicht verstanden. Das Senden von Nachrichten an Controls in C ist ein *Workaround*, weil C nunmal keine Klassen unterstützt. Das Senden von Nachrichten ist daher äquivalent zum Aufrufen einer Methde – und in C# braucht man das einfach nicht, weil man ja Methoden hat (die in der Tat aber nur ein Wrapper sind; in den Klassen intern werden natürlich WMs versandt. Wenn man z.B. schreibt 'Control.Text = "Hallo";' dann wird intern ein 'SendMessage(EM_SETTEXT, "Hallo", …)' ausgeführt).



  • Oh ja... Danke! Das sieht schon wesentlich besser aus 🙂
    Allerdings habe ich gesehen, dass man den MethodInvoker wohl nur auf Funktionen ohne Parameterliste ansetzen kann. Was mache ich denn, wenn ich nun der Funktion Foo() zum Beispiel einen string mitgeben will? Gibt es auch dafür ein Equivalent?

    Du hast Recht, so ganz habe ich das Konzept noch nicht intus - bin ja noch am lernen. Mein Denken ist darum halt noch etwas zu C++ lastig. Aber das wird schon noch...



  • Wesley67 schrieb:

    Allerdings habe ich gesehen, dass man den MethodInvoker wohl nur auf Funktionen ohne Parameterliste ansetzen kann. Was mache ich denn, wenn ich nun der Funktion Foo() zum Beispiel einen string mitgeben will? Gibt es auch dafür ein Equivalent?

    Für solche Fälle schreibt man sich einfach seinen eigenen Delegate. Für Dein Beispiel ('Foo' bekommt einen String) kann man auch den Delegate 'System.Action<T>' verwenden:

    BeginInvoke(new Action<string>(Foo));
    

    Ansonsten definiert man sich einfach einen eigenen Delegate, wie in folgendem Beispiel:

    // Wir wollen einen Delegate, der ein 'int' und einen 'string' erhält:
    delegate void BinaryFunctionIntString(int a, string b);
    
    void MyFunction(int number, string str) {
        MessageBox.Show(str + " " + number.ToString());
    }
    
    // …
    BeginInvoke(new BinaryFunctionIntString(MyFunction));
    


  • Ah ja... hab es schon gefunden.

    Nun sieht es so aus:

    public delegate void FooDeleg(string Item);
    
    void Foo(string Item) {
        if (InvokeRequired) {
            BeginInvoke(new FooDeleg(Foo), Item);
            return;
        }
    
        int cnt = LstReport.Items.Count;
        string colm1 = "für Spalte 1";
        string colm2 = Item;
        LstReport.Items.Add(new ListViewItem(new String[] { colm1, colm2 }));
        LstReport.EnsureVisible(cnt);
    }
    
    public bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg) {
        Foo("für Spalte 2");
    }
    

    Und es funktioniert! 🙂
    Hab vielen Dank!


Anmelden zum Antworten