Zugriff auf (Fenster)Objekt aus static Methode heraus
-
Hallo zusammen,
ich habe folgendes C#-Problem:
Mein Programm (ein Formular) ruft eine externe Funktion auf, die wiederum eine definierte Callback-Methode des aufrufenden Porgramms aufruft. Die Callback-Methode muss im Formular darum zwangsläufig als static deklariert sein.
Die Callback-Methode soll nun aber Ausgaben auf das Formular machen.
Da static Methoden jedoch nicht auf Member ihrer Klassen zugreifen dürfen, müsste normalerweise eine Referenz auf das betreffende Formular-Objekt an die Callback-Methode mit übergeben werden. Einschränkung durch den externen Funktionsaufruf ist hierbei allerdings, dass man nur simple Parameter mitliefern kann (int oder IntPtr etc.).Meine Frage ist nun: Wie kann ich über einen solchen simplen Parameter eine Referenz auf ein Objekt (konkret: ein Formular) übergeben und wie kann ich aus diesem simplen Parameter in der static Methode wiederum Zugriff auf das betreffende Objekt bekommen?
Oder alternativ währe auch eine Lösung denkbar, womit ich aus der static Methode heraus Zugriff auf eine Fensterliste (o.ä.) des Programms bekommen kann.Entschuldigt, falls die Frage evtl. zu trivial sein sollte, aber ich arbeite mich gerade erst in C# ein und hab darum noch nicht so den Gesamtüberblik über alle Möglichkeiten dieser Sprache.
Für hilfreiche Tipps im Voraus schon mal vielen Dank!
Wes
-
Servus,
Die Callback-Methode muss im Formular darum zwangsläufig als static deklariert sein.
Wer sagt das?
Zeig mal bitte ein bisschen Quellcode...
Du kannst der Methode einen IntPtr mitgeben, den du später wieder mit Form.FromHandle() zu einer Form abgeleiteten Klasse instanzieren kannst.
mfg
Hellsgore
-
Leider kennt man die Einschränkung nicht von der Externen Methode. Aber vllt. hilft Dir dennoch folgendes kleine Projekt weiter:
-
Die Callback-Methode wird aus einer DLL heraus aufgerufen. Darum muss sie static sein, da die aufrufende Methode keinen Instanz-Context besitzt.
Klar kann ich ein bisschen Quelltext angeben:
namespace DLLTestCs { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { TypeCallback myCallback = new TypeCallback(Callback); // Ist das so richtig??? IntPtr myself = (IntPtr)Handle.ToPointer(); // Problem dabei: Das mit dem Pointer geht nur in unsicherer Umgebung // und ist damit leider keine anwendbare Lösung für mein Problem // (wird hier darum auch vom Compiler abgelehnt). Gibt's Alternativen? // Aufruf einer DLL-Funktion, die die Callback-Methode übernimmt und setzt // myself wird später wiederum als Context an die Callback-Methode weitergegeben MyDll.SetCallback(ref myself, myCallback); : : } public static bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg) { Form1 myself = (Form1)FromHandle(Context); myself.SetInfo("Nachricht ist gekommen"); : : return true; } } }
@Knuddlbaer:
Die externe Methode ist in einer Win32-DLL enthalten (in C++ geschrieben) und kann darum auch nur simple Datentypen handeln.
Danke erst einmal für den Verweis auf das Beispiel. Schau ich mal durch.
-
Schau Dir das mal an:
typedef void (*IntEvent)(int a); extern "C" __declspec(dllexport) void hsp_callback_register_rsi(IntEvent eventt) { if(dialog != NULL) dialog->RSSI = eventt; }
public delegate void Int(int a); public partial class Card : UserControl { [DllImport("<....>.dll")] extern static void hsp_callback_register_rsi(IntPtr point); unsafe public void rsi(int sig) { // Signal setzen wenn die DLL diese Funktion aufgerufen hat0 } void register_rsi() { // Delegat anlegen Int ptr_ServiceType = null; ptr_ServiceType = new Int(this.rsi); // Zeiger festsetzen, damit die Adresse nicht durch den GC zum wandern kommt GCHandle pinX = GCHandle.Alloc(ptr_ServiceType, GCHandleType.Pinned); // Zeiger für die C++ DLL Umsetzen IntPtr nativeptr_ServiceType = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(ptr_ServiceType); // und diesen Zeiger ebenfalls festtackern GCHandle pinY = GCHandle.Alloc(nativeptr_ServiceType, GCHandleType.Pinned); // Funktionspointer übergeben hsp_callback_register_rsi(nativeptr_ServiceType); // Und noch Code einfügen, der pinY.Free und pinx.Free aufruft. Ich habe die Zeiger in ein Array gepackt und rufe Free beim deinistialisieren des Objektes vom Typ Card auf. } }
-
Wesley67 schrieb:
Die Callback-Methode wird aus einer DLL heraus aufgerufen. Darum muss sie static sein, da die aufrufende Methode keinen Instanz-Context besitzt.
Nein. .NET-Delegates stellen einen Wrapper um diesen Instanz-Kontext zur Verfügung. wenn Du also einen Delegate an eine WinAPI-Funktion übergibst, erhält diese Funktion einen „flache“ Callback-Adresse ohne Instanz-Parameter. Das Framework kümmert sich um den Rest.
-
Nach einigem Grübeln und Probieren hab ich nun folgende Lösung:
C# für DLL-Anbindung
public delegate bool TypeCallback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg); class MyDll { [DllImport("MyDll.dll",CallingConvention=CallingConvention.Cdecl)] public static extern bool SetCallback(IntPtr pContext, TypeCallback pFn); }
c# Forumlar
using System.Runtime.InteropServices; namespace DLLTestCs { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { TypeCallback myCallback = Callback; // Handle auf mich selbst besorgen GCHandle myself = GCHandle.Alloc(this); // ...an DLL weiterreichen MyDll.SetCallback((IntPtr)myself, myCallback); // ...und nicht vergessen, wieder freizugeben myself.Free(); : : } public static bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg) { // Handle konvertieren GCHandle gch = (GCHandle)Context; // ...daraus Referenz auf mich selbst besorgen Form1 myself = (Form1)gch.Target; // ...und einfach anwenden :-) myself.SetInfo("Nachricht ist gekommen"); : : return true; } public void SetInfo(string EinText) { : : } } }
Übrigens hab ich tatsächlich mal probiert, ob auch nonstatic Methoden als Callback-Funktion verwendet werden können. Mein Ergebnis: Es ist irgendwie eine instabile Geschichte. Ein Aufruf der Callback-Funktion klappte, aber beim 2. Aufruf der Callback-Funktion schmierte die Anwendung (im Debugger) einfach ohne Meldung ab und beendete das Debuggen. Keine Ahnung, warum - wieso - weshalb...
Ich bleib mal lieber bei der static Methode, denn so funktionierts nun ja.Danke nochmal für eure Mithilfe
Wes
-
Du musst, wie oben gezeigt, die Zeiger fixieren. Die Adressen wandern sonst durch den GC mal gerne durch den Speicher, dann stimmt die Rücksprungadresse nicht mehr.
(Falls das jetzt unsinn war, werde ich bestimmt korregiert. Das Pinnen half mir zumindest bei den CF geschichten auf den Scannern weiter)
-
Wesley67 schrieb:
Übrigens hab ich tatsächlich mal probiert, ob auch nonstatic Methoden als Callback-Funktion verwendet werden können. Mein Ergebnis: Es ist irgendwie eine instabile Geschichte. Ein Aufruf der Callback-Funktion klappte, aber beim 2. Aufruf der Callback-Funktion schmierte die Anwendung (im Debugger) einfach ohne Meldung ab und beendete das Debuggen.
Der Fehler liegt auf jeden Fall ganz woanders. Deine statische Methode ist wirklich Mist. Wenn schon, dann pin den Zeiger wie von Knuddelbaer gezeigt. Aber wie gesagt, das ist ein unnötiger Hack.
-
Das Pinnen ?
-
Knuddlbaer schrieb:
Das Pinnen ?
Das ganze. Es geht doch auch mit einer nicht-statischen Methode, sodass man nicht umständlich die Form-Instanz „erraten“ muss.
-
Na das Beispiel von mir geht von einer nicht statischen Methode aus.
AFAIK muss ich dann aber den Zeiger pinnen weil der GC die Objekte ja beliebig im Speicher bewegen darf. (Auf den Scannern mit 32MB Speicher passiert das sehr schnell).
ublic partial class Card : UserControl { [DllImport("<....>.dll")] extern static void hsp_callback_register_rsi(IntPtr point); unsafe public void rsi(int sig) { // Signal setzen wenn die DLL diese Funktion aufgerufen hat0 } void register_rsi() { // Delegat anlegen Int ptr_ServiceType = null; ptr_ServiceType = new Int(this.rsi); // Zeiger festsetzen, damit die Adresse nicht durch den GC zum wandern kommt GCHandle pinX = GCHandle.Alloc(ptr_ServiceType, GCHandleType.Pinned); // Zeiger für die C++ DLL Umsetzen IntPtr nativeptr_ServiceType = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(ptr_ServiceType); // und diesen Zeiger ebenfalls festtackern GCHandle pinY = GCHandle.Alloc(nativeptr_ServiceType, GCHandleType.Pinned); // Funktionspointer übergeben hsp_callback_register_rsi(nativeptr_ServiceType); // Und noch Code einfügen, der pinY.Free und pinx.Free aufruft. Ich habe die Zeiger in ein Array gepackt und rufe Free beim deinistialisieren des Objektes vom Typ Card auf. } }
Hier wird einmal der Managed Delegat festgenagelt als auch der GetFunctionPointerForDelegate Zeiger. Ich hatte MSDN in diesem Zusammenhang so verstanden, das der GC die Methoden immer bewegen darf und sah das auch durch abstürze der Applikation auf dem Embeddedgerät bestätigt. Nach dem Pinnen gab es keine Probleme mehr mit den Adressen.
Leider bin ich aber nicht vollständig durchgestiegen so das die "Lösung" durchaus mängel hat.
Wenn man das besser lösen kann, ich bin dabei
-
Ich bin mir jetzt nicht sicher, ob du das mit den nicht statischen Methoden nur innerhalb von C#-Code meinst oder generell. In den C#-Büchern werden Event-behandlungsmethoden doch auch überall als static deklariert.
Wie gesagt, wird die Callback-Methode ja von einer Win32-DLL (in C++ geschrieben) aufgerufen. Ist es denn auch dann noch so einfach möglich? Vielleicht ist mir ja die Sache deswegen abgeschmiert, weil ich den Zeiger nicht festgenagelt habe. Mal schaun...@Knuddlbaer:
Das mit dem Pinnen und dem verschieben der GC verstehe ich nicht so ganz. Mir fehlt dazu ganz einfach noch der globale Hintergrund, um zu begreifen, was da intern alles ablaufen kann... Ich schau aber trotzdem mal, ob ich das so umsetzen kann, wie du es geschrieben hast.
-
Ich habe keine Ahnung von Embedded Environments aber ich glaube, dass hier das verhalten dasselbe ist wie sonst auch: Nämlich, dass man sich das Pinnen von Delegaten sparen kann. Abgesehen davon, dass der GC sicher keine *Funktionen* durch den Speicher schieben wird, braucht man bei PInvoke-Aufrufen generell keine Objekte zu pinnen, die werden während der Ausführung des Aufrufs nicht verschoben.
-
Wesley67 schrieb:
Ich bin mir jetzt nicht sicher, ob du das mit den nicht statischen Methoden nur innerhalb von C#-Code meinst oder generell.
Generell.
@Knuddelbaer: Ich habe mich geirrt, so einfach ist es leider nicht: der GC kann schon zuschlagen (allerdings ist das unwahrscheinlich).
Zum Glück gibt es aber eine sehr einfache Lösung: nämlich, indem man für die gesamte Dauer, in der der Delegate aus unverwaltetem Code heraus aufgerufen werden könnte, eine Instanz des Delegates in seinem Code speichert. D.h. es würde z.B. reichen, den Delegate als private Variable der Form abzulegen.
/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.
Quelle: ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_fxinterop/html/e55b3712-b9ea-4453-bd9a-ad5cfa2f6bfa.htm
-
Sie können mit dem GCHandle auch ein fixiertes Objekt erstellen, das eine Speicheradresse zurückgibt und verhindert, dass der Garbage Collector das Objekt im Speicher verschiebt.
Hm, die Idee ist interesannt. Ich finde leider in der Doku nichts dazu. Was passiert wenn der GC das Objekt verschiebt ? Bleibt dann wirklich die Speicheradresse der Methode gleich oder ist es vllt. ein Offset zum Objekt ?
So tief bin ich nicht in die Materie durchgedrungen und die Informationen in der MSDN zusammen zu graben kann schon mühsig sein.
Mit dem als private member machen kann man aber auch auf die Nase fallen. z.B. klappte das hier nicht:
public partial class ListViewDelegateHolder : UserControl { System.Drawing.Graphics gr = new System.Drawing.Graphics(); [....] int width = gr.MeasureString(dat[(int)fields.key] + "X", m_listView.Font).ToSize().Width;
Der GC hat mit das gr Objekt weggeworfen weil er speicher brauchte und gr nirgends verwendet wurde. (Es haute dann bei der 2. Verwendung nach dem Aufräumen durch den GC eine Disposed Exception um die Ohren).
Das gleiche verhalten würde ich jetzt annehmen, wenn man den Delegaten einfach ams Member einsetzt - der GC friemelt drann rum.
(Und die sch... Embedded geräte räumen bei gut 1 MB verbrauch schon auf)
-
Na nun bin ich völlig durch den Wind. Wie ist das denn nun genau?
Mache ich jetzt die Callback-Methode nonstatic, muss sie dafür aber pinnen? Damit erübrigt sich dann ja das Durchschleifen des Contexts - richtig?
Die rsi-Funktion aus dem Besispiel soll jetzt meine Callback-Methode sein - korrekt?
Muss die Callback-Methode jetzt unsafe sein? Wenn ja, warum?
Das mit dem Objekt draufsetzen hab ich auch eben in meinem C#-Buch gefunden...
-
Das unsafe kommt aus nem copy and paste von anderen callbacks die ich vorher verwendet habe.
-
Also das mit dem Pinnen will irgendwie nicht so recht klappen.
Gebe ich meine Callback-Methode an, gibt es beim Pinnen eine Exception, in der gesagt wird, dass die Funktion keine primitiven Pameter hätte. Was genau ist denn damit gemeint? Ich habe doch nur int und IntPtr angegeben... Oder dürfen die nicht als Referenz verwendet werden? Das wäre ja völlig daneben, weil ich die Funktion dann nicht vernünftig verwenden könnte.using System.Runtime.InteropServices; namespace DLLTestCs { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { TypeCallback myCallback = new TypeCallback(this.Callback); GCHandle pinFunc = GCHandle.Alloc(myCallback, GCHandleType.Pinned); // Exception! // Text: // Eine nicht behandelte Ausnahme des Typs "System.ArgumentException" ist in mscorlib.dll aufgetreten. // Zusätzliche Informationen: Das Objekt enthält keine primitiven Daten. IntPtr nativeptr_myCallback = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(myCallback); GCHandle myself = GCHandle.Alloc(this); MyDll.SetCallback((IntPtr)myself, nativeptr_myCallback); myself.Free(); // ...an DLL weiterreichen MyDll.SetCallback((IntPtr)myself, myCallback); // ...und nicht vergessen, wieder freizugeben myself.Free(); : : } public bool Callback(ref IntPtr Context, ref int MsgCode, ref IntPtr hMsg) { SetInfo("Nachricht ist gekommen"); : : return true; } public void SetInfo(string EinText) { : : } } }
Nachtrag:
Ach, mir fällt grad auf, dass es nicht um die Parameter, sondern um die Daten geht... Darf ich da in der Funktion etwa nur mit primitiven Daten arbeiten? Das wäre keine gute Sache...
-
Wesley67 schrieb:
Mache ich jetzt die Callback-Methode nonstatic, muss sie dafür aber pinnen?
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.
Der GC hat mit das gr Objekt weggeworfen weil er speicher brauchte und gr nirgends verwendet wurde.
Hmm … nee. Glaube ich nicht. Mal davon abgesehen, dass Du das Objekt vollkommen falsch initialisierst, musst Du Dir darüber im klaren sein, dass GDI-Objekte (und eben auch GDI+-Objekte) ihre Gültigkeit nicht behalten. Ein GDI-Handle kann ungültig werden, z.B. dann, wenn das Control neu gezeichnet wird und vom System über WM_PAINT einen neuen Handle zugewiesen bekommt. Damit ist das Graphics-Objekt dann auch dahin.
=> Mit dem GC hat das aber nichts zu tun. Bei einer so modernen VM kann man schon damit rechnen, dass die Heuristiken so gut sind, dass nicht häufig irgendwelche Objekte eingesammelt werden, obwohl diese noch verwendet werden. Das sollte wenn überhaupt nur in sehr exotischen Situationen passieren.