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