![]() |
Delphi-Version: XE
Generischen Typ "merken" zur späteren Verwendung
Hallo,
ich bekomme Events von einer Telefonanlage. Während die Event-Handler laufen, dürfen keine weiteren Windows-Messages verarbeitet werden, damit sich die Events von der Telefonanlage nicht gegenseitig überholen. Nun kann es natürlich vorkommen, dass ich beispielsweise aufgrund eines Telefonanlagenevents eine Meldung mit ShowMessage ausgeben möchte. Dabei werden Messages verarbeitet (Anzeige eines modalen Fensters). Mechanismus, der das Problem löst: Ich verschicke mit PostMessage eine Windows-Message, sobald diese ankommt, rufe ich ShowMessage auf (oder was immer ich tun möchte). Diesen Mechanismus möchte ich natürlich mit möglichst frei definierbaren Methoden verwenden, was die Signatur betrifft. Deshalb möchte ich mit Generics arbeiten. Mein Entwurf:
Delphi-Quellcode:
Das Problem ist nun, dass an der markierten Stelle (also beim Empfangen der Windows-Message) keine Typinformationen für den generischen Typ mehr habe. Die erste überladene Version von Call läuft dagegen wie gewünscht (wenn ich die zweite Version auskommentiere).
type
TAsync = class public type TAsyncProc = reference to procedure; TAsyncProc1<T1> = reference to procedure(P1: T1); class procedure Call(Proc: TAsyncProc); overload; class procedure Call<T1>(Proc: TAsyncProc1<T1>; P1: T1); overload; private const WM_ASYNCCALLINVOKE = WM_USER + $4df8; type TAsyncProcRec = record Proc: TAsyncProc; end; TAsyncProcRec1<T1> = record Proc: TAsyncProc1<T1>; P1: T1; end; class function AsyncCallInvoke(var m: TMessage): Boolean; end; ... implementation ... class procedure TAsync.Call(Proc: TAsyncProc); var AsyncProcRec: ^TAsyncProcRec; begin MessageDistributor.RegisterMessageHandlerMethod(TAsync.AsyncCallInvoke); New(AsyncProcRec); AsyncProcRec^.Proc := Proc; PostMessage(MessageDistributor.Handle, WM_ASYNCCALLINVOKE, 0, Integer(AsyncProcRec)); end; class procedure TAsync.Call<T1>(Proc: TAsyncProc1<T1>; P1: T1); var AsyncProcRec1: ^TAsyncProcRec1<T1>; begin MessageDistributor.RegisterMessageHandlerMethod(TAsync.AsyncCallInvoke); New(AsyncProcRec1); AsyncProcRec1^.Proc := Proc; AsyncProcRec1^.P1 := P1; PostMessage(MessageDistributor.Handle, WM_ASYNCCALLINVOKE, 1, Integer(AsyncProcRec1)); end; class function TAsync.AsyncCallInvoke(var m: TMessage): Boolean; var AsyncProcRec: ^TAsyncProcRec; Proc: TAsyncProc; AsyncProcRec1: ^TAsyncProcRec1<T1>; // Hier fehlt nun die Typinformation Proc1: TAsyncProc1<T1>; begin case m.Msg of WM_ASYNCCALLINVOKE: begin Result := True; case m.WParam of 0: begin AsyncProcRec := Pointer(m.LParam); try Proc := AsyncProcRec^.Proc; Proc; finally Dispose(AsyncProcRec); end; end; 1: begin AsyncProcRec1 := Pointer(m.LParam); try Proc1 := AsyncProcRec1^.Proc; Proc1(AsyncProcRec1^.P1); // um lediglich diesen Aufruf zu machen finally Dispose(AsyncProcRec1); end; end; end; end; else Result := False; end; end; Gibt es eine Möglichkeit, die generischen Typinfos irgendwie zwischenzuspeichern und später weiterzuverwenden? RTTI könnte ein Ansatz sein, deckt aber nicht alle möglichen Typen ab (z.B. Records), wenn ich mich auf die Schnelle richtig informiert habe. |
AW: Generischen Typ "merken" zur späteren Verwendung
Zitat:
Als Trigger an den Hauptthread, dass sich Daten in der Queue befinden, kann man eine Windows-Message verwenden. Achte darauf, keine softwaretechnische Selbstbefriedigung durchzuführen. Nur weil es Generics gibt heisst das nicht dass man sie auf Biegen und Brechen verwenden soll und dabei die eigentliche Aufgabe aus den Augen lässt. |
AW: Generischen Typ "merken" zur späteren Verwendung
Es geht auch schlicht nicht wie du dir das vorstellst. Der Hintergrund von einem Ausdruck wie einer Variable vom Typ TAsyncProcRec1<T1> ist, dass diese beim Kompilieren z.B. als TAsyncProcRec1<Integer> erkannt und ein Typ TAsyncProcRec1Integer erzeugt wird (nur mit nem anderen Namen :D).
Die Typinformation kann in deinem Fall dort beim Kompilieren noch nicht vorliegen und damit ist das so nicht möglich. Rein logisch schon nicht. Vorschlag z.B.: Nimm eine Queue, in die anonyme Methoden reingeschrieben werden. Die haben dann alle Typinformationen. Du kannst auch z.B. direkt TThread.Queue benutzen, je nachdem was du brauchst. Dann sparst du dir die Messages bei der Verarbeitung und damit auch die Nebeneffekte. |
AW: Generischen Typ "merken" zur späteren Verwendung
Zu meiner Verteidigung: Ich bin gerade von Delphi 5 (endlich!) umgestiegen auf Delphi XE und lerne die ganzen neuen Sprachkonstrukte gerade erst kennen. Daher bin ich momentan etwas experimentierfreudig.
Dass das so mit den Generics gar nicht machbar ist, habe ich schon befürchtet, aber nicht ausgeschlossen, dass es doch einen Weg gibt. Zitat:
Zitat:
Mit Threads möchte ich gar nicht arbeiten, da ich damit eine Einschränkung an die anonymen Methoden stelle: Sie müssen threadsicher programmiert sein. Das kann ich hier nicht sicherstellen, da ich nicht der einzige Programmierer bin, der an dem Projekt arbeitet. Es gibt Programmierer hier, denen ich das einfach nicht zutraue - selbst ich wäre unsicher damit. Deshalb die Lösung des verzögerten Aufrufs über Windows-Messages. Was ich mir als Quintessenz nun vorstelle, ist, auf die Vorzüge von Generics an dieser Stelle zu verzichten und stattdessen lediglich eine überladene Call-Methode anbiete, die als Parameter für die anonyme Methode einen Pointer ermöglicht. Damit lassen sich beliebige Daten an die asynchron aufzurufende anonyme Methode übergeben. Das ist weniger schön, eröffnet aber genauso alle Möglichkeiten. |
AW: Generischen Typ "merken" zur späteren Verwendung
Ein schnelles Beispiel, ungetestet:
Delphi-Quellcode:
Nicht sauber geschrieben, aber sollte so gehen und zeigen wie ich das meinte.
uses
Rtti; type TMyRecord<T> = record Value: T; end; TMyRunner = reference to procedure; TFormXY = class(TForm) public procedure MyCallProc<T>(const Value: TMyRecord<T>); end; var FormXY: TFormXY; implementation procedure AddToQueue1(const AQueue: TQueue<TMyRunner>); var MyRecord: TMyRecord<Integer>; begin MyRecord.Value := 1002; AQueue.Enqueue(procedure begin FormXY.MyCallProc<Integer>(MyRecord); end); end; procedure AddToQueue2(const AQueue: TQueue<TMyRunner>); var MyRecord: TMyRecord<string>; begin MyRecord.Value := 'Oh, ein String!'; AQueue.Enqueue(procedure begin FormXY.MyCallProc<string>(MyRecord); end); end; procedure TFormXY.FormCreate(Sender: TObject); var ExampleQueue: TQueue<TMyRunner>; begin ExampleQueue := TQueue<TMyRunner>.Create; try AddToQueue1(ExampleQueue); AddToQueue2(ExampleQueue); while ExampleQueue.Count > 0 do ExampleQueue.Dequeue.Invoke; finally ExampleQueue.Free; end; end; procedure TFormXY.MyCallProc<T>(const Value: TMyRecord2<T>); begin if TypeInfo(T) = TypeInfo(Integer) then ShowMessage(IntToStr(TValue.From<T>(Value.Value).AsType<Integer>)) else if TypeInfo(T) = TypeInfo(String) then ShowMessage(TValue.From<T>(Value.Value).AsType<String>) else //... end; |
AW: Generischen Typ "merken" zur späteren Verwendung
Ah, der Kern deiner Idee ist also folgender: Die asynchron aufzurufende Methode mit generischen Parametern wird in eine parameterlose anonyme Funktion verpackt, die nichts anderes tut, als die asynchron aufzurufende Methode mit den passenden Parametern aufzurufen. Im Kontext dieser parameterlosen anonymen Funktion sind die tatsächlichen Typen der generischen Parameter der asynchron aufzurufenden Methode bekannt und deshalb kann sie auch aufgerufen werden. Beim Empfang der Windows-Message brauche ich die Infos über die generischen Typen gar nicht, da ich nur die anonyme Funktion aufrufe (im "alten" Kontext).
Mein angepasster Quelltext:
Delphi-Quellcode:
Vielen Dank für diese Idee!
TAsync = class
public type TAsyncProc = reference to procedure; TAsyncProc1<T1> = reference to procedure(P1: T1); class procedure Call(Proc: TAsyncProc); overload; class procedure Call<T1>(Proc: TAsyncProc1<T1>; P1: T1); overload; private const WM_ASYNCCALLINVOKE = WM_USER + $4df8; class function AsyncCallInvoke(var m: TMessage): Boolean; end; ... implementation ... class function TAsync.AsyncCallInvoke(var m: TMessage): Boolean; var PProc: ^TAsyncProc; begin case m.Msg of WM_ASYNCCALLINVOKE: begin Result := True; case m.WParam of 0: begin PProc := Pointer(m.LParam); try PProc^.Invoke; finally Dispose(PProc); end; end; end; end; else Result := False; end; end; class procedure TAsync.Call(Proc: TAsyncProc); var PProc: ^TAsyncProc; begin MessageDistributor.RegisterMessageHandlerMethod(TAsync.AsyncCallInvoke); New(PProc); PProc^ := Proc; PostMessage(MessageDistributor.Handle, WM_ASYNCCALLINVOKE, 0, Integer(PProc)); end; class procedure TAsync.Call<T1>(Proc: TAsyncProc1<T1>; P1: T1); var PProc: ^TAsyncProc; begin MessageDistributor.RegisterMessageHandlerMethod(TAsync.AsyncCallInvoke); New(PProc); PProc^ := procedure begin Proc(P1); end; PostMessage(MessageDistributor.Handle, WM_ASYNCCALLINVOKE, 0, Integer(PProc)); end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:36 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz