|
Antwort |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#11
Hm, wenn ich dich richtig verstanden habe, heist das
1.) die Interfaces in der EXE liegen, statt der DLL 2.) die Interfaces über die "virtual abstract" methoden in die DLL einlinken 3.) die Datentypen der ICipher auf denen sie verschlüsseln, eben Delphi abhängig sind, zB. TStream, LongString usw. Ich weiß nich so recht ob das ein gutes Design ist !? Normalerweise sollten NUR die Interface Deklarationen sichtbar sein. Deren Allokation geschieht entweder über den MS-COM Dispatcher per Typlib, oder procedural per export aus der DLL. Mein bisheriges "Denk"-Konzept sieht so aus: Interface Struktur wird in der Root durch ein IDEC Interface gebildet. Dieses Interface wird durch eine einzigste Allokator Funktion aus einer DLL exportiert. D.h. die DLL exportiert nur EINE Function. Das IDEC Interface dient als Verwaltungsobject aller verfügbaren IDECCipher/IDECHash usw. Objecte. Diese Interfaces werden also durch Methoden vom IDEC Interface alloziert. Die einzelnen IDECCipher Interfaces wiederum arbeiten NUR auf Daten-Interfaces. D.h. alle Datentypen wie LongStrings/Streams usw. MÜSSEN in eigene Interface-Objecte gekapselt werden. Somit kann durch eigene Implementationen dieser Daten-Interfaces JEDE beliebige Sprache auf die Funktionen der IDECCipher/IDECHash zugreifen. Desweiteren sind die IDECCipher/IDECHash usw. Interfaces nur Wrapper auf die tatsächlich intern registrierten Algorithmen. Die internen Algorithmen implementieren als Interfaces NUR die absolut notwendigen Funktionen. Im Falle eines Hash-Algos. also zB. nur .Init/.Done/.Calc(). Das nun allozierte IDECHash Interface wird durch IDEC alloziert und mappt alle wichtigen Operationen von Streams/LongStrigns und andere Datentypen auf das intern allozierte und spezifische Hash-Algorithmen Interface. Somit gibt es eine zweistufige Kapselung. Einmal nach Ausen zum Endbenutzer über IDECHash und einmal nach Innen zu den Entwickleren neuer Algorithmen. Der IDECHash spielt also den Vermittler zwischen Endbenutzer und Algo. Entwickler, und wird durch die Basis-Bibliothek die auch IDEC implementiert zur Verfügung gestellt. Dabei impelementiert also diese Basis Bibliothek im IDECHash Interface alle Funktionen die zB. einen IDECStream auflösen in die entsprechenden .Init/.Done und .Calc() Aufrufe. Das Kapseln der eigentlichen Datentypen, sprich Buffer/LongString/Streams wird nötig damit man für die Zukunft auch ohne Änderungen .NET oder eben auch andere Programmiersysteme unterstützen kann. Desweiteren wird es bei der Implelemtierung von Public Key Algos. oder entsprechenden Protocollen immer schwieriger geeignete Datencontainer umzusetzen, wenn man auf native Datentypen wie LongString/TStreams usw. zurückgreift. Gruß Hagen |
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#12
Zitat von negaH:
Die einzelnen IDECCipher Interfaces wiederum arbeiten NUR auf Daten-Interfaces. D.h. alle Datentypen wie LongStrings/Streams usw. MÜSSEN in eigene Interface-Objecte gekapselt werden. [...] Das nun allozierte IDECHash Interface wird durch IDEC alloziert und mappt alle wichtigen Operationen von Streams/LongStrigns und andere Datentypen auf das intern allozierte und spezifische Hash-Algorithmen Interface.
mich würde interessieren, wie Du Dir das Mapping vorstellst, ohne bei einer "kleinsten Schnittmenge" wie "Pointer und Größe" zu enden... Darüber hinaus könnten die abstrakten Datenkontainer zustandsbehaftet sein (zB Streams, die kein Seeking unterstützen), so dass auch ein schlichter Pointer nicht immer funktionieren könnte, ohne dass zunächst eine Kopie der Daten vom IDEC angelegt wird (Performanceeinbruch), was Du zu vermeiden scheinst (anderfalls wäre der Aufwand nach meinem Empfinden ungerechtfertigt). Ein Interface, von dem Lediglich Datenworte "gepoppt" werden können, ist mir hingegen auch nicht wirklich geheuer... Kannst Du hierzu einmal beispielhaft eine Interface-Signatur darstellen?
gruß, choose
|
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#13
Gut, denn genau die angesprochenen Probleme sind auch meine Probleme
Vorweg, generell benötigst du in der Kryptographie Streams die nach Möglichkeit zwischernbuffern, sich überlappende Operationen ermöglichen und meistens nicht-seekable sind. Z.b. wird ein Buffer fester Länge verschlüsselt so entsteht meistens eine Expansion der Daten. Somit wäre ein Buffer als Ein-/Ausgabe sehr schlecht geeignet. Deshalb mein Konzept mit IDECStreams zu arbeiten. Das bedeutet aber das z.B. für Speicherbasierende Daten diese immer auch kopiert werden. Oder zB. das Prozessing von verschl. Dateien. In einer solchen Datei muß der Cipher + Hash + PK-Algo. + Protokoll in der Lage sein im Header und Footer der Datei zusätzliche Daten abzulegen. Diese Daten müssen nun durch die verwendeten Algorithmen wieder lesebar sein, eben per Streams. Oder ein Datenstrom soll nachdem er Komprimiert und Verschlüsselt wurde aus dem Binären Datenformat in ein MIME64 Datenformat konvertiert werden. Es gibt zwei Möglichkeiten. 1.) jeden Schritt für sich ausführen und somit mit vollen zwischenspeicherungen zu arbeiten, oder 2.) per teilgebufferten Streams arbeiten, die immer nur eine exakt austarierte Teilmenge der Daten buffern. Ich würde 2. bervorzugen, da dadurch eben auch Livestreaming möglich wird. D.h. statt eines Datei-Streams an einem Ende des Streams sitzt ein Socket-Stream. Deshalb eben auch das Konzept einen eigenes Daten-Interface zu basteln. Diese Streams besitzen im Grunde keinerlei Operationen für's Seeking, Size, Position etc. sondern arbeiten per EOF/BOF,Read,Write. Meistens sogar wird so ein Stream nur in einer Richtung funktionieren, also Writeonly=Sink oder Readonly. Alle Algorithmen Objecte arbeiten nun nur auf solchen Datenstreams. Damit benötigt man eben in den Ciphern zB. keine mehrfachmethoden wie .EncodeString(), .EncodeBuffer(), .EncodeStream(), .EncodeFile() sondern eben nur .Encode(const Source: IDECReadStream; const Dest: IDECWriteStream); Diese Streams werden nun sequentiell in .Encode() ausgelesen in einen Buffer. Dieser wird durch an das interne Cipher-Algo-Interface weitergeleitet und verschlüsselt. Performance ist schön und gut, aber steht immer im Gegesatz zu einem universellen und flexiblen API Design. Das ganze API bestünde also aus dem IDEC Interface das sozusagen als Manager und Allokator der IDECCipher/IDECHash Interfaces dient. Alle Daten werden in ein einheitliches Interface gekapselt. Dies Kapselung hat im Usercode zu erfolgen. Für Delphi/BCB würde man diese schon vorfertigen. Hier alle Sourcen darzustellen geht aber zu weit, besonders weil ich selber schon x'mal das komplette Design umgeworfen/verworfen und neuangefangen habe. An irgendeiner Ecke hackt es dann immer wieder. Es ist im grunde ineffizient als Einmann-Team, ohne konstruktive Diskussionen, ein solches API und Konzept aufzubauen. Gruß Hagen |
Zitat |
Registriert seit: 23. Mär 2003 Ort: Münster 3.750 Beiträge Delphi 2010 Professional |
#14
Zitat von negaH:
Hm, wenn ich dich richtig verstanden habe, heist das
1.) die Interfaces in der EXE liegen, statt der DLL
Zitat:
2.) die Interfaces über die "virtual abstract" methoden in die DLL einlinken
3.) die Datentypen der ICipher auf denen sie verschlüsseln, eben Delphi abhängig sind, zB. TStream, LongString usw. Ja da hast du natürlich recht. Das mit den Strings hätte man im Notfall auch noch PChar machen können, aber das mit TStream in anderen Sprache wäre ja ein Spass geworden... Ich weiß nich so recht ob das ein gutes Design ist !? Ich hatte mein Verschlüsselungsprogramm am Anfang komplett ohne Plugins geplant. So langsam ging es dann in die Richtung, aber ich immer nur so weit gedacht, dass die Plugins nur auf mein Programm und meine Umgebung zu geschnitten sind. Das hier war schon der erste Schritt in Richtung flexibilität, aber immer noch nicht ganz. Deine Idee bzw. dein "Denk"-Konzept hört sich auf jedenfall interessant an, soweit ich es verstanden habe. Soweit habe ich das verstanden: - Die Alogrithmen sind intern in der Library und werden mit Interfaces gekapselt. Sie bieten aber nur die Grundfunktionen des Algoritmus. (bei dem Hash jetzt .Init, .Calc und .Done) - Das IDEC Interface dient sozusagen als Wrapper für die Algorithmen und ermöglicht den Zugriff... - ... mit dem IDECStreams. Nun kommt der Rest den ich nicht ganz verstanden habe: - Der User muss Teiles der Stream Interfaces kapseln, bzw. sie sind schon vorgefertigt. Wie sieht nun ein solches Stream Interface aus? - Wie kann das IDEC Interface mit dem IDECStreams arbeiten? mfG mirage228
David F.
|
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#15
Hallo Hagen,
nach Deinen Schilderungen und dem wenigen, was ich über dieses Thema weiß, würde ich ebenfalls für die zweite Variante, der von (teilweise) gepufferten Streams, stimmen. Das Kaskadieren von Operationen kann dann sehr elegant über Threads nach dem Producer-Consumer-Pattern umgesetzt werden, so dass einerseits die Puffergröße minimiert und andererseits die Übersicht gesteigert werden kann, weil die Synchronisation vollständig transparent innerhalb der Streams gestaltet werden könnte. Hast Du Dir schon einmal das Stream-Konzept unter Smalltalk oder Java angesehen? Hier wird intensiv vom Decorator-Pattern Gebrauch gemacht und Funktionen wie Pufferung (sinnvoll bei Sockets oder Dateien), Komprimierung (eine "echte" Funktion) und selbstverständlich auch Anbindung an eine Quelle oder eine Senke (Speicher, Dateisystem, StdIn/Out,...) jeweils mit einer Stream-Schnittstelle implementiert (tatsächlich von abstrakter Oberklasse geerbt). Gemäß des Decorator-Patterns aggregieren oder komponieren die einzelnen Streams so andere Exemplare mit identischer Schnittstelle und können gegenseitig ausgetauscht werden (siehe auch GoF). Sogar eine Concatenation von Streams ist so transparent mit einer StreamList erreichbar, die ihrerseits lediglich die einfache Stream-Schnittstelle veröffentlicht und beim Erreichen des Endes des einen mit der Bearbeitung des nächsten fortfährt Bei der Wahl der Stream-Schnittstelle sollte man Größe der lesbaren Datenblöcke an die Erfordernisse anpassen: Gibt es bei den Algorithmen kleinste Datenworte (zB Byte oder DWord) oder sind auch Operationen auf Bit-Basis denkbar? Werden Streams im Zweifelsfall aufgefüllt? Ich würde gerne über geeignete Konzepte diskutieren, aber vielleicht sollte das in einem neuen Thread geschehen?
gruß, choose
|
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#16
Zitat:
Ich würde gerne über geeignete Konzepte diskutieren, aber vielleicht sollte das in einem neuen Thread geschehen?
Deine Vergleiche mit JAVA etc. sind genau die Richtung in die ich gehen würde, und exakt auch so gemeint habe (wohl aber in meinen Postings nicht explizit erwähnt). Das Kaskadieren würde ich aber in zwei Ebenen ermöglichen. 1.) jeder Stream kann intern auf einen einzigsten verlinkten Stream verweisen. 2.) besondere Streams arbeiten als Multiplexer/Verteiler per Listen von mehreren Streams, diese Verteiler/Multiplexer arbeiten im In/Output wie ein einzigster Stream, geben/holen aber ihren In/Output an mehrere Streams weiter. Nun wird logisch ersichtlich warum man horizontal in Punkt 1. nur eine starre 1 zu 1 Verlinkung benötigt, und vertikal die Verteiler baut. Das Problem mit solchen Konstruktionen ist eben der oberste Stream, denn der muß ja von einer ReadOnly Stream-Kette in eine WriteOnly-Stream Kette die Daten pumpen.
Zitat:
Bei der Wahl der Stream-Schnittstelle sollte man Größe der lesbaren Datenblöcke an die Erfordernisse anpassen: Gibt es bei den Algorithmen kleinste Datenworte
Die nötige Berechnung der minimalsten Buffer-Chunk-Größe ist einfach mit dem GCD()/LCM() möglich. Allerdings entsteht nun ein neues Problem: wie wird es fertiggebracht das jeder Stream am Ende einer Transaktion noch zusätzliche Daten anhängen bzw. entfernen kann ? Bisher habe ich das so gelösst das es eine .Begin und .End Methode gibt, die ebenfalls in der Kette durchgereicht wird. @mirage228:
Zitat:
noch PChar machen
1.) Delphi Interfaces mit PASCAL Aufrufkonvention und Delphi Typen wie LongStrings etc. 2.) Delphi Interfaces mit STDCALL und PChars etc. 3.) Delphi Interfaces mit STDCALL und Daten-Access Interfaces Interfaces vom Typ 1. können NUR innerhalb von Delphi/BCB Anwendnungen benutzt werden. Typ 2.) interfaces benutzen zwar den stdcall sind aber durch den Typ PChar inkompatibel zum MS-COM Stylesguides. Typ 3.) sind immer und jederzeit kompatibel, egal ob man COM/ActiveX oder eventuell .NET vorreussetzt. Allerdings muß in jeder Anwendung der eigene Datentyp, eben auch PChar in ein spezielles Interfaces gekapselt werden.
Zitat:
- Der User muss Teiles der Stream Interfaces kapseln, bzw. sie sind schon vorgefertigt. Wie sieht nun ein solches Stream Interface aus?
- Wie kann das IDEC Interface mit dem IDECStreams arbeiten? Die header der DEC Bibliothek deklarieren alle nutzbaren Interfaces. Sie deklarieren auch die Interfaces zum Zugriff auf Daten, eben zB. IDECStream. Diese Interfaces ermöglichen wie in einem TStream das lesen und schreiben von Daten. Ob sich nun hinter so einem IDECStream eine Datei, ein TStream, ein PChar oder LongString verbirgt ist dem DEC egal. Diese Zugriffe und Interfaces müssen in der Anwendung implementiert werden. Ein solches Minimal-Interface könnte so aussehen:
Delphi-Quellcode:
Wichtig ist meiner Meinung nach das nicht jede Interface Klasse ALLE möglichen Operationen veröffentlich, sondern eher über Typcast's andere Interface Klassen unterstützt. Als angenommen ein MIME Base 64 Konvertierer sähe dann so aus:
type
IDECStreamable = interface GUID.... function MinChunkSize: Integer; function Done: Integer; end; IDECWriteable = interface(IDECStreamable) GUID..... function Write(const Data: Pointer; DataSize: Integer): Integer; stdcall; end; IDECReadable = interface(IDECStreamable) GUID..... function Read(out Data: Pointer; DataSize: Integer): Integer; stdcall; end; IDECLinkable = interface GUID.... function SetLink(const Link: IDECStreamable): IDECStreamable; function GetLink: IDECStreamable; end; IDECStream = interface(IDECReadable, IDECWriteable) // read & write GUID... end;
Delphi-Quellcode:
Man erzeugt aber nur einen IDECWriteable(TMIME64) um daten von Binär nach MIME64 zu konvertieren. Das Interface MUSS vorher aber verlinkt werden damit die .Write() Aufrufe auch wissen WOHIN die Daten geschrieben werden müssen. Somit heist das man erzeugt ein TMIME64 Interface und verlinkt es mit .SetLink() zB. mit einem IDECStream der auf ein TFileStream aufsetzt. Alle .Write() Aufrufe von TMIME64 werden also dazu führen das die Daten, zwischengepuffert, konvertiert werden von Binär nach MIME64. Nachdem so 3 Bytes in 4 Bytes MIME konvertiert wurden, ruft TMIME64 vom verlinkten IDECStream(TFilerStream) wiederum .Write() auf, und speichert so die konvertierten Daten.type TMIME64 = class(TInterfacedObject, IDECReadable, IDECWriteable, IDECStreamable, IDECLinkable, IDECStream) .... end; Nachdem ALLE .Write() Operationen beendet sind kann es ja sein das in der Kette der verlinkten Stream noch zischengepufferte Daten vorliegen. Deshalb muß am Ende immer .Done aufgerufen werden. Diese .Done wird durch die komplette Ketter der verlinkten IDECStreams durchgereicht, nachdem jeder einzelene IDECStream sein restlichen Daten gespeichert hat. Exakt diese Operation würde im Falle von MIME64 zB. aus 1 Byte Input 3 bytes gepaddeten Output erzeugen, oder im Falle eines CBC Ciphermodes die Daten um x Bytes expandieren und als letzten vollständig verschlüsselten Datenchunk speichern. Gruß Hagen |
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#17
Eine andere Möglichkeit wäre vom obigen Konzept abzurücken. Man benutzt ein einheitliches IDECStream Objekt, das aber eine Liste der zu verwendenden Filter-Objekte verwaltet. Die Konvertierung nach/vom MIME 64 oder ein Cipher oder eine Komprimierung wären nur Filter Objecte. Somit enthält der IDECStream eine Liste solcher Filterobjete. Deren Reihenfolge bei der Registration im IDECStream bestimmt in welcher Reihenfolge welche Aktionen getätigt werden. Nun, der IDECStream alloziert intern einen Buffer der eine Minimal und Maximale Größe besitzt. Am Anfang stehen in diesem Buffer 3 Bytes und nun müssen diese per inplaced Operationen durch den MIME64 Filter auf 4 Bytes expandiert werden. Usw. usw.
Das Klassendesign könnte dann so aussehen:
Delphi-Quellcode:
Im obigen Beispiel habe ich absichtlich noch mit Pointern gearbeitet, normalerweise müssten diese durch Interface Objecte ersetzt werden.
type
IDECFilter = interface function Encode(Data: Pointer; DataSize: Integer): Integer; stdcall; function Decode(Data: Pointer; DataSize: Integer): Integer; stdcall; function MinChunkSize: Integer; stdcall; end; IDECFilters = interface function Count: Integer; stdcall; function GetFilter(Index: Integer): IDECFilter; stdcall; procedure Add(const Filter: IDECFilter); stdcall; end; IDECStream = interface procedure Write(const Data: Pointer; DataSize: Integer); stdcall; procedure Read(out Data: Pointer; DataSize: Integer); stdcall; procedure Write(const Data: IDECStream; DataSize: Integer); stdcall; procedure Read(const Data: IDECStream; DataSize: Integer); stdcall; function Filters: IDECFilters; end; Desweiteren müsste der IDECStream eben direkte ordinale Typen unterstützen, also WriteByte(), WriteChar(), WriteWord() etc. Tja, was ist aber nun besser ?? Gruß Hagen |
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#18
Dann also kein neuer Thread, aber ordentlich lange Beiträge
Zitat:
1.) jeder Stream kann intern auf einen einzigsten verlinkten Stream verweisen.
2.) besondere Streams arbeiten als Multiplexer/Verteiler per Listen von mehreren Streams, diese Verteiler/Multiplexer arbeiten im In/Output wie ein einzigster Stream, geben/holen aber ihren In/Output an mehrere Streams weiter. Innerhalb einer Hierarchie sollte es deswegen meiner Meinung nach eine Abstract InputStream klasse geben, von Denen zum einen die DecoratorStreams bzw die FilterStreams (ich verwende im Folgenden die letztere Bezeichnung) erben, sowie die Tatsächlichen Eingabeströme (so) und die von Dir vorgeschlagenen Multiplexer etc., im Folgenden als Kompositum bezeichnet, abgeleitet werden. In der beispielhaften Hierarchie (Abb.1) wird der "lesende Zweig" der Stream-Hierarchie mit der abstrakten Klasse Stream beschrieben, die Methoden zum Schließen (Close) und zum überspringen von Datenworten (ich verwende der Einfachheit halber Bytes) mithilfe der Methode Skip(...) anbietet. Von ihr Erbt die abstrakte Klasse TInputStream. Sie beschreibt die Signatur eines Eingabestreams mit den beiden zusätzlichen Methoden Read und IsEOF. Basierend auf dieser Signatur zeigt das UML-Diagram weiterhin die bereits erwähnten beispielhaften Klassen. Anmerken möchte ich weiterhin, dass die Beiden Interfaces IStream und IInputStream den von Dir geforderten Interfaces zur Bearbeitung von abstrakten Datenquellen entsprechen, sie werden von den beiden abstrakten Oberklassen TStream bzw TInputStream implementiert (die Ermittlung der Chunkgröße habe ich aus Verfachungsgründen vernachlässigt). Existiere zu jeder Klasse eine entsprechende Konstruktionsfunktion (auch als jew Klassenmethode denkbar), die statt des Exemplars eine Referenz auf das Interface zurückgibt, könnte man so eine Konstruktion zum einlesen einer vershlüsselten Datei so erreichen: myStream:= DecryptInputStream(BufferedInputStream(FileInputStream('myFile'), 4096)); und mit eine Schleife der Form
Delphi-Quellcode:
gepuffterte Daten aus einer Datei entschlüsseln und an eine Methode DoSth(...) übergeben.
while not myStream.IsEOF do
DoSth(myStream.Read);
Zitat:
Allerdings entsteht nun ein neues Problem: wie wird es fertiggebracht das jeder Stream am Ende einer Transaktion noch zusätzliche Daten anhängen bzw. entfernen kann ?
Um meine Lösungsidee hierzu beschreiben zu können, möchte ich zunächst die Klasse TFiFoStream einführen, die sowohl das bereits dargestellte Interface IInputStream als auch das komplementäre Interface IOutputStream (mit den beiden Methoden Write(...) und Flush) implementiert und als FiFo (First in First Out) Puffer fungiert. Die in ein Exemplar dieser Klasse geschriebenen Daten mithilfe des durch IOutputStream geforderten Methoden können demnach anschließend über die Methoden des Interfaces IInputStream gelesen werden (TFiFoPuffer sollte nicht in die beschriebene Klassen-Hierarchie eingearbeitet werden sondern gesondert mit einer aggregierten Referenzzählung (siehe D7 TAggregatedObject) implementiert werden). Unter Verwendung eines aggregierten Exemplars dieser Klasse kann TInputStream die drei Template-Methoden (GoF) BeforeRead(...), DoRead(...) und AfterRead(...) einführen und das Ermitteln des nächsten Datenabschnitts in Abhängigkeit des internen Status an die jeweilige Methode delegieren:
Delphi-Quellcode:
Wie man erkennen kann, ist die Implementierung der Templatethoden nun relativ einfach in der Form
function TInputStream.Read: Byte;
begin // no more byte available -> exception if IsEOF then raise E... Result:= FiFoStream.Read; end; function TInputStream.IsEOF: Boolean; begin // ensure next byte and State is prepared PrepareRead; Result:= FiFoStream.IsEOF; end; function TInputStream.PrepareRead; begin // only read if no byte available in fifo and stream not closed if FiFoStream.IsEOF and (State<ssClosed) then repeat // do state specific action case State of ssBefore: BeforeRead(FiFoStream); ssReading: DoRead(FiFoStream); ssAfter: AfterRead(FiFoStream); end; // go to next state if (State in [ssBefore, ssAfter]) or (FiFoStream.IsEOF) then FState:= Succ(State); // loop until at least one byte available in fifo or no more byte until not FiFoStream.IsEOF or (State=ssClosed); end;
Delphi-Quellcode:
realisierbar und auch auch das Byteweise lesen oder Verarbeiten in DoRead(...) stellt kein Problem dar, weil der Status in PrepareRead erst dann auf ssAfter gesetzt wird, sobald keine weiteren Bytes in DoRead(...)
function TInputStream.BeforeRead(AnOutputStream: IOutputStream);
begin AnOutputStream.Write(SomeHeaderData); end; an den FiFo-Puffer übergeben worden. Fortsetzung folgt...
gruß, choose
|
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#19
So, erst einmal 'nen neuen Kaffe und nun geht's weiter
Zitat:
Das Problem mit solchen Konstruktionen ist eben der oberste Stream, denn der muß ja von einer ReadOnly Stream-Kette in eine WriteOnly-Stream Kette die Daten pumpen.
myStream:= WrappingInputStream(AnOutputStream); ähnlich, wie es beim FiFoStream geschehen ist, in diesem Fall müsste aber jeder OutputStream intern einen Puffer vorhalten, bzw ein Puffer-Stream zwischengeschaltet werden, was ich für wenig elegant halte. Tatsächlich liese sich doch das gepufferte Kopieren einer Datei wie folgt Realisieren:
Delphi-Quellcode:
(selbstverständlich sollte der Destruktur eines Streams selbstständig Close bzw Close zunächst Flush aufrufen. Auch optionale Parameter sind bei der Erzeugung von Streams, zB bei der Wahl der Puffergröße denkbar).
myInStream:= BufferedInputStream(FileInputStream('InFile'), 4096);
myOutStream:= BufferedOutputStream(FileOutputStream('OutFile'), 4096); while not myInStream.isEOF do myOutStream.Write(myInStream.Read); myOutStream.Flush; myOutStream.Close; Diese Schleife bildet also das Bindeglied der Ein- und Ausgabeströme und kann ihrerseits in einer Hilfsklasse implementiert werden. zB
Delphi-Quellcode:
das Flushen und Schließend der Outputstreams kann dieser Zusteller ebenfalls selbstständig übernehmen, da ihm durch den Eingabestrom bekannt sein sollte, wann keine weiteren Daten vorliegen...
myConsigner:= StreamConsigner(AnInputStream, AnOutputStream);
while myConsigner.HasData do begin myConsigner.ConsignData; Log('Transfered Bytes: %d', [myConsigner.DataCount]); end;
Zitat:
Wichtig ist meiner Meinung nach das nicht jede Interface Klasse ALLE möglichen Operationen veröffentlich, sondern eher über Typcast's andere Interface Klassen unterstützt.
Denkbar wäre noch der Einsatz von Interfaces für jede konkrete Klasse zur Abbildung deren speziellen Fähigkeiten, falls diese Funktionen doch einmal innerhalb eines Clients, im Wissen um diese Schnittstelle, benötgtigt werden sollten. Zu bedenken bleibt, dass bei der Kaskadierung (wrapping) von Streams auch Zyklen innerhalb des so erstellten gerichteten Graphens kreiert werden können, die vermieden werden sollten. Leider lässt sich eine Prüfung auf die Identität nicht ohne weiteres in der Form
Delphi-Quellcode:
durchführen, weil Delphi das Konzept der vererbbaren Interfaces konsequent durchhält, so dass die gezeigte Methode IsSame(...) auch mit einer Referenz auf IInputStream aufgerufen werden kann. Es gilt aber weiterhin:
function TMyStream.IsSame(const AStream: IStream): Boolean;
begin Result:= (Self as IStream)=AStream; end; (Self as IStream)<>(Self as IInputStream) so dass über eine Methode GetIdentity in einem Wurzel-Interface nachgedacht werden sollte...
gruß, choose
|
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#20
@Choose: falls du weitergehendes Interesse hast würde es mich freuen wenn wir Nägel mit Köpfen machen würden. Dein obiges Konzept sieht schon mal gut aus, obwohl ich das Gefühl nicht los werde das die Klassenhirarchie zu kompliziert erscheint.
Es gibt aus meiner Sicht ganz bestimmte Anforderungen die erfüllt sein müssen. Zb. einfach nur Byteweise die Daten auszulesen macht wenig Sinn. Allerdings höherwertige Datentypen bis hin zu Pointer'n, widerspricht zB. einer .NET Minimal-Kompatibilität. Das Durchreichen des Bearbeitungsstatus, bei dir der Status in [ssBefore, ssReading, ssAfter] gefällt mir auch nicht so sehr. Ich fände es besser wenn dieser Status über procedurale Methoden erledigt wird. Das hat mehere Gründe. Der wichtigste ist das man über eventuelle Methoden .BeginEnvelope und .EndEnvelope eine Verschachtelung von einzelnene hierarichen Envelopes erreichen würde. D.h. der Datenstrom enthält einzelne Dokument mit Subdokumenten usw. Jeder spezielle Filter wäre nun in der Lage seine ganz spezifischen Aktionen auch als zwischengespeicherte Datenmengen zu verwalten. Als beipiel ein Dekompressions Filter. Im .BeginEnvelope liest er aus seinem Source-Stream einen bekannten Header und speichert temporär die entkromimierten Daten vollständig zwischen. Alle weiteren .Read Aufrufe lesen nun transparent aus diesem Buffer. Beim .EndEnvelope wird dieser Buffer geschlossen. Somit würde beim nächsten Aufruf von .BeginEnvelope das nächste Dokument aus dem Source Stream entkomprimiert usw. usw. Interface-technisch gesehen möchte ich nach Möglichkeit immer Zustandslose Interfaces erreichen, logisch. Denn so wird die für den Enduser sichtbare Funktionalität stark vereinfacht. Wenn man sich ähnliche Kontrukte in JAVA oder C oder .NET anschaut so wird man feststellen das die abstrakten Basis Interfaces enorme Monster-Interface mit sehr vielen Methoden sind. Aber genau das muß vermieden werden. D.h. pro Interfacetyp sollten nicht mehr als 5-7 Methoden vorhanden sein, will man mehr so muß das aktuelle Interface in ein anderes ge-typcastet werden. Gruß Hagen |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |