Zugriff auf (Fenster)Objekt aus static Methode heraus
-
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!