![]() |
Callbacks: Interface und/oder Methode? (DEC 6.0)
So, da Einige mit den Interfaces als Callback nicht klar kommen (es ihnen zu umständlich ist),
mal eine Frage, was man nun am Besten machen könnte.
---------- So ist es aktuell
Delphi-Quellcode:
Dieses nun mit einer zusätzlichen EventMethode überladen
type
// das war Einigen zu schwer verständlich zu verstehen {IDECProgress = interface procedure Progress(const Min, Max, Pos: Int64); end;} // aber umbenannt und mit Kommentar ist es nicht wirklich besser geworden IDECProgress = interface /// <summary>... procedure OnProgress(const Min, Max, Pos: Int64); end; IDECHash = interface ... /// <summary>... procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress: IDECProgress = nil); ... end; procedure TMyClass.OnProgress(const Min, Max, Pos: Int64); begin ProgressBar.Min := Min; // 0; ProgressBar.Max := Max; // 100; ProgressBar.Position := Pos; // (Pos - Min) * 100 div (Max - Min); end; und weiter zusätzlich noch ein "optimaleres" Interface. Es gibt es an sehr vielen Stellen nun die einbindenen Methoden somit unschön doppelt/dreifach, wenn man das Alte aus Kompatibilitätsgründen beibehalten möchte, außerdem hat ein Interface auch seine Vorteile.
Delphi-Quellcode:
Vererbung würde das Ausmaß minimieren, aber eine Vererbung ist hier nicht "sinnvoll" möglich
// siehe https://github.com/geheimniswelten/DelphiEncryptionCompendium/compare/development...geheimniswelten:develop/IDECProgress
// funktioniert, aber an vielen Stellen "unschön" mehrfache Implementation, für jeden Callback-Type {$IFDEF FPC} // ich weiß noch nicht ob/wie das mit den Anonymen im Lazarus/FPC geht TDECProgress = procedure(Sender: TObject; const Min, Max, Pos: Int64) of object; {$ELSE} TDECProgress = reference to procedure(Sender: TObject; const Min, Max, Pos: Int64); {$ENDIF} IDECProgress = interface procedure OnProgress(const Min, Max, Pos: Int64); end; IDECProgress2 = interface procedure OnProgressStart(const Min, Max: Int64); procedure OnProgress(const Pos: Int64 {; Percent: Single}); procedure OnProgressStop(const Pos: Int64 {; Percent: Single}); procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string); end; IDECHash = Interface ... /// <summary>... procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress: IDECProgress2 = nil); overload; procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress: IDECProgress); overload; procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress: TDECProgress); overload; ... end; procedure TMyClass.OnProgressStart(const Min, Max: Int64); begin ProgressBar.Min := Min; ProgressBar.Max := Max; ProgressBar.State := pbsNormal; ProgressBar.Position := 0; StatusLabel.Visible := False; end; procedure TMyClass.OnProgress(const Pos: Int64); begin ProgressBar.Position := Pos; end; procedure TMyClass.OnProgressStop(const Pos: Int64); begin ProgressBar.Position := Pos; ProgressBar.State := pbsPaused; end; procedure TMyClass.OnProgressError(const Pos: Int64; const ErrorText: string); begin ProgressBar.Position := Pos; ProgressBar.State := pbsError; StatusLabel.Caption := ErrorText; StatusLabel.Visible := True; end; und an vielen Stellen ist es weiterhin doppelt (Methode+Interface), auch wenn beide Interfaces nun im selben Parameter-Vorfahren übergeben werden könnten.
Delphi-Quellcode:
Die Verwebung zu verschieben wird bestimmt auch niemand verstehen,
// ist keine Lösung
type IDECProgress = interface procedure OnProgress(const Min, Max, Pos: Int64); end; IDECProgress2 = interface(IDECProgress) {weiterhin ist das das alte OnProgress vorhanden procedure OnProgress(const Min, Max, Pos: Int64); } procedure OnProgressStart(const Min, Max: Int64); procedure OnProgress(const Pos: Int64 {; Percent: Single}); overload; procedure OnProgressStop(const Pos: Int64 {; Percent: Single}); procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string); end; also mit einem IDECProgressBase oder gar direkt IInterface als Parameter. Und ins letzte Interface passen die Methoden des Originals nicht rein, bzw. ich würde den doppelten OnProgress-Aufruf unschön finden.
Delphi-Quellcode:
Statt der vielen Überladungen könnte man auch nur das größte Interface (IDECProgress2) implementieren
// unverständliche mögliche Lösung
type IDECProgressBase = interface // ohne was drin end; IDECProgress = interface(IDECProgressBase) procedure OnProgress(const Min, Max, Pos: Int64); end; IDECProgress2 = interface(IDECProgressBase) {weiterhin ist das das alte OnProgress vorhanden procedure OnProgress(const Min, Max, Pos: Int64); } procedure OnProgressStart(const Min, Max: Int64); procedure OnProgress(const Pos: Int64 {; Percent: Single}); overload; procedure OnProgressStop(const Pos: Int64 {; Percent: Single}); procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string); end; und einen "manuellen" Wapper bauen, aber das versteht scheinbar auch niemand, bzw. es ist zu umständlich. ![]() Allerdings ist das von den "möglichen" Implementierung her eigentlich die schönste/schlankeste Variante, auch wenn man bei Übergabe einer Callback-Methode, oder des kleinen Interfaces, einen manuellen Cast einfügen muß.
Delphi-Quellcode:
Als Record-Helper mit Implicit-Class-Operator an dem Interface,
// funktioniert und kompakt (aber findet bzw. versteht vielleicht niemand)
type TDECProgressWrapper = class(TInterfacedObject, IDECProgress2) private ... public class function Create(Sender: TObject; Intf: IDECProgress): IDECProgress2; overload; class function Create(Sender: TObject; Proc: TDECProgress): IDECProgress2; overload; end; Hash := DECHash.CalcStream(Stream, Size, TDECProgressWrapper.Create(OnProgressMethodOrIDECProgress)); das funktioniert leider nicht. :cry:
Delphi-Quellcode:
Aber geil wäre es schon, wenn die Überladungen nur an einer Stelle liegen würden, wie auch beim Wrapper,
// compiliert leider (noch) nicht
type // [dcc32 Fehler] E2474 Record-Typ erforderlich TDECProgressHelper = record helper for IDECProgress2 class operator Implicit(Value: IDECProgress): IDECProgress2; class operator Implicit(Value: TDECProgress): IDECProgress2; end; // [dcc32 Fehler] E2021 Klassentyp erwartet TDECProgressHelper = class helper for IDesignerHook class operator Implicit(Value: IDECProgress): IDECProgress2; class operator Implicit(Value: TDECProgress): IDECProgress2; end; // aber per se kann man scheinbar doch Casts nachträglich anhängen (der Compiler meckert nicht) TIntegerHelper = record helper for Integer class operator Implicit(Value: Byte): Integer; end; TPointHelper = record helper for TPoint class operator Implicit(Value: TRect): TPoint; end; anstatt überall bei jeder Verwendung als overload einbauen zu müssen. Umsetzung siehe ![]() ----- Falls noch jemand andere Lösungen kennt ... bitte immer her damit. Ansonsten fällt mir nur noch eine radikale "übersichtliche" Lösunge ein, also das Interface komplett zu löschen und durch eine Event-Methode zu ersetzen. |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Vielleicht ist das mit den Interfaces nur deshalb ein Problem, weil viele über das "Ich Doppelklicke auf einen Button und schreibe den Code da hin"-Stadium nie hinauskommen. Ich befürchte, dass ca. 50% aller Delphi Entwickler sich noch nie mit Interfaces beschäftigt haben und verstehen, wozu sie gut sind und worauf man achten muss. Sie kommen halt bei der "normalen" Entwicklung eines GUI-Programms gar nicht vor.
Dazu kommt noch, dass die Implementation von Interfaces in Delphi unübersichtlich ist. Es ist nur schwer zu erkennen, welche Methoden einer Klasse ein Interface implementieren (und welches) und welche "ganz normale Methoden" sind. Da könnte die IDE deutlich nachhelfen. Wenn es aber nur um einen Callback für den Fortschritt geht, finde ich ein Interface auch Overkill. Da wäre ein einfacher Event ausreichend. Das ändert sich, sobald es mehrere Callbacks gibt, dann ist ein Interface im Prinzip einfacher, allerdings muss man dann immer alle Methoden implementieren, auch die, die man vielleicht gar nicht braucht. Vielleicht hilft es auch schon, Beispiele zur Verfügung zu stellen? Ich kenne DEC nicht, deshalb weiß ich nicht, wie gut die Doku ist. |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Zitat:
|
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Moin, du hast dir gute Gedanken gemacht und alles sehr schön abgewogen. :thumb:
Auch wenn ich ein großer Fan von Interfaces bin, solltest du auf Callback-Funktionen setzen. Die DEC wird in Konsolen-, VCL, FMX und Diensten eingesetzt. Würdest du die Interfaces wählen, dann müsste zumindest für die Konsole überhaupt erstmal eine Klasse her, welche das Interface implementiert. Du zwingst dann den Entwickler mehr zu machen als Notwendig wäre. Daher würde ich es gut finden, wenn der Entwickler, den Callback als anonyme Funktion übergeben kann. (oder halt auch ein Zeiger auf eine Funktion) Für alle, welche gerade Bahnhof verstehen im CodingBott Youtube-Channel gibt es Videos zu dem Thema: Zurück zum Aufrufer - Strategien zum zurückgeben von Daten ![]() Zeiger auf Methoden und Funktionen in Delphi ![]() Der Weg zum Interface und deren Vorteile ![]() |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Ja, dieses ein-prozedurige Interface ist eh "blöd".
Zitat:
Zitat:
Delphi-Quellcode:
gedacht, damit der Entwickler da ganz frei alles übergeben kann.
reference to procedure
Bezüglich dem FPC muß ich noch nachsehn ob/wie das dort geht. * man könnte das einfache Interface ersetzen oder ein zweites "Besseres" danebenlegen (Abwärtskompatibilität) * man kann einen einfachen Prozedurzeiger zusätzlich daneben hinzufügen oder das Interface komplett ersetzen (rauswerfen) Demos: der aktuelle Hauptthread zum Projekt ![]() Code eigentlich hier (diese Demo steckt noch in der Entwicklung fest, drum nicht im Master) ![]() aber bei mir liegt da grade der aktuellere Code (hab grad erst die ProjekteOptionen aufgeräumt, und Dergleichen) ![]() ![]() und Seite 3 ganz oben, da hat jemand die Demo grade nochmal überarbeitet (hab ich mir noch nicht angesehn) ![]() [edit] Ich denk mal so wäre es doch eine gute Lösung?
Delphi-Quellcode:
Bei Objekt-Methode und anonymer Methode bekommt man eigene Daten in den Callback.
// alt
IDECProgress = interface procedure Progress(const Min, Max, Pos: Int64); end; // neu TDECProgressState = (Start, Progress, Finish, Error); TDECProgressParams = record Sender: TObject; Min, Max: Int64; Pos: Int64; Percent: Single; State: TDECProgressState; function ErrorMessage: string; // die Exception.Message aus dem umgebenden Try-Except-Block (oder als "function Error: Exception;", falls wer nicht selber auf System.ExceptObj zugreifen kann) end; TDECProgress = reference to procedure(const Progress: TDECProgressParams); Und bei einer Prozedur kann man sich über den Sender (die übergeordnete DEC-Klasse) als Vergleichswert zu einer Datenquelle durcharbeiten. Oder sollte man hier vielleicht auch noch eine Data-Variable durchschleifen? (NativeInt, Pointer oder TObject) Eigentlich nicht nötig, denn wer was braucht, der kann ja Methode statt Prozedur benutzen. Nur die neue coole Interface-CallbackDemo muß dann wer umbauen. :stupid: |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Hallo,
soweit ich es derzeit abschätzen kann wäre ich auch für die Variante mit der Anonymen Methode. Dann kann man entweder eine Methode oder eine normale Prozedur übergeben oder was direkt tun (Anonyme Methode "in-place"). Verstehe ich das richtig? Und auf die FPC Kompatibilität lege ich momentan ehrlich gesagt keinen übertriebenen Wert. Wenn wir das dazu kompatibel hinbekommen, ok, aber das soll uns nicht ausbremsen. Grüße TurboMagic |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Wenn du eine Function draus machst, könnte ein FALSE sagen "weiter machen" und ein TRUE als "Abbruch" gewertet werden für die Callbackfunktion bzw. deren Rückgabe.
Also ist das Result die Antwort auf "Soll der Vorgang abgebrochen werden?" Bei dem MIN-Wert bin ich mir auch nicht so sicher, ob der gebraucht wird. Übergibst du jemals was anderes als 0? |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
(wenn ich es richtig gesehen/verstanden habe)
Pos ist "eigentlich" nicht die "absolute" Position der Progess, sondern die Position im Stream. Und per se kann der Stream auch erst ab der Mitte für x Bytes lang ver-/entschlüsselt werden. Aber man könnte sich auch darauf einigen, dass es ab jetzt nur die Position des aktuellen Ver-/Entschlüsselungsproesses ist, dann kann MIN auch weg. Zitat:
Hab grade (seit gestern) das Delphi ein zweites Mal neu installiert. (erstes Mal im Schlaf von 10.4 zu 10.4.1 und dann nochmal iOS/OSX dazu ... brauch es zwar nicht, aber Delphi schmeißt sonst unnetterweise die Einträge aus den Projekten :wall:) Und wollte mir dann mal wieder (nach Jahren) ein Lazarus installieren und es in das TestScript aufnehmen ... dann sieht man erstmal, ob es noch kompatibel ist (Änderungen dann auch kompatibel zu halten, ist eine andere Sache und könnte man später nachholen) |
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
Zitat:
Aber ich denke, dass alle damit übereinstimmen, dass es mit 0 bis Anzahl-1 ganz OK wäre, also Min=0 (nicht vorhanden) und Pos=aktueller Verarbeitungsfortschritt. ![]() Den einzigen Vorteil, denn Min=StartPos statt Min=0 hätte, wäre wenn jemand in einem Stream "stückchenweise" mehrere Teile einzeln verschlüsselt/entschlüsselt/hasht, aber da ist es für den Entwickler bei der Anzeige auch kein Problem, den Offset selbst einzurechnen, womit Min=0 dennoch vollkommen OK wäre. Allerdings gibt es noch einen Bug, denn TDECHash.CalcStream und TDECFormattedCipher.DoEncodeDecodeStream verhalten sich unterschiedlich und das hab ich noch nicht behoben. Erstmal nur die Behandlung für den Callback geändert und beim Rest nochmal warten, falls jemand etwas aus gutem Grund anders sieht. TDECFormattedCipher.DoEncodeDecodeStream (DECCipherFormats) fängt immer mit Min=Stream.Position an und bei DataSize<0 wird nur noch die Länge automatisch berechnet. Während TDECHash.CalcStream (DECHashBase) bei DataSize<0 die Stream.Position auf 0 setzt. Also bei DataSize<0 wird der Stream verschoben und mit Min=0 gearbeitet, während bei DataSize>=0 mit Min=Stream.Position begonnen wird. :wall: Jetzt könnte man sich noch über die Definition von -1 streiten.
|
AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
DataSize sollte tatsächlich die Stream Position nicht beeinflussen!
Der Stream wird ja erzeugt übergeben und nicht von der DEC erzeugt. Grüße TurboMagic |
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:41 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-2025 by Thomas Breitkreuz