AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Zugriffverletzung beim Verwenden von Interfaces
Thema durchsuchen
Ansicht
Themen-Optionen

Zugriffverletzung beim Verwenden von Interfaces

Ein Thema von mirage228 · begonnen am 14. Dez 2003 · letzter Beitrag vom 16. Dez 2003
Antwort Antwort
Seite 2 von 3     12 3      
Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#11

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 15. Dez 2003, 13:46
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
  Mit Zitat antworten Zitat
choose

Registriert seit: 2. Nov 2003
Ort: Bei Kiel, SH
729 Beiträge
 
Delphi 2006 Architect
 
#12

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 15. Dez 2003, 14:05
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.
Hallo Hagen,
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
  Mit Zitat antworten Zitat
Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#13

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 15. Dez 2003, 17: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
  Mit Zitat antworten Zitat
Benutzerbild von mirage228
mirage228

Registriert seit: 23. Mär 2003
Ort: Münster
3.750 Beiträge
 
Delphi 2010 Professional
 
#14

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 15. Dez 2003, 18:01
Zitat von negaH:
Hm, wenn ich dich richtig verstanden habe, heist das
1.) die Interfaces in der EXE liegen, statt der DLL
Meinem Programm muss das Interface bekannt sein, damit ich damit arbeiten kann. Zumindest nehme ich das stark an. In der DLL muss auch das Interface verwendet werden. Die Klasse TCipher und ihre Nachfahren sind nur optional. Man kann es auch komplett ohne diese machen, also nur mit der vorliegenden Interface Deklaration, aber dann müsste ich in jede meiner 3 DLLs die ganzen Get und Set Methoden nochmals hinschreiben, bzw. kopieren. Ja gut, dass ist kein Akt, aber ich hatte früher alles auf TCipher basierend, die wurde früher auch in der DLL anstatt ICipher zurückgegeben (in der exportierten Prozedur der DLL)

Zitat:
2.) die Interfaces über die "virtual abstract" methoden in die DLL einlinken
Wie gesagt: Kann man via TCipher machen, muss man aber nicht.

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.
  Mit Zitat antworten Zitat
choose

Registriert seit: 2. Nov 2003
Ort: Bei Kiel, SH
729 Beiträge
 
Delphi 2006 Architect
 
#15

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 00:00
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
  Mit Zitat antworten Zitat
Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#16

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 00:41
Zitat:
Ich würde gerne über geeignete Konzepte diskutieren, aber vielleicht sollte das in einem neuen Thread geschehen?
Dann mach einen auf

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
Das wird immer der Fall sein, und wenn nichts anders explizit angegeben wird kann man von der Annahme ausgehen das 1 Byte = unendlich ist, d.h. keine Berücksichtigungen notwendig sind. Man könnte nun die verlinkten Streams durch iterieren, und über eine Methode jeweils abfragen was das kleinste Ratio ist. Z.b. MIME Base 64 Formatierungen wären 3 zu 4 Bytes bei der Codierung und 4 zu 3 Bytes bei der Decodierung. D.h. der interne Buffer sollte so konstruiert werden das er in Chunks von 12 Bytes arbeitet.
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
Damit handelst du dir Probleme ein. Man kann 3 unterschiedliche Interfaces-Designs unterscheiden
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?
Obige Erklärung sind die Grundlage um diese Fragen zu beantworten.
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:
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;
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:

Delphi-Quellcode:

type
  TMIME64 = class(TInterfacedObject, IDECReadable, IDECWriteable, IDECStreamable, IDECLinkable, IDECStream)
    ....
  end;
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.
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
  Mit Zitat antworten Zitat
Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#17

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 00:57
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:
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;
Im obigen Beispiel habe ich absichtlich noch mit Pointern gearbeitet, normalerweise müssten diese durch Interface Objecte ersetzt werden.
Desweiteren müsste der IDECStream eben direkte ordinale Typen unterstützen, also WriteByte(), WriteChar(), WriteWord() etc.

Tja, was ist aber nun besser ??

Gruß Hagen
  Mit Zitat antworten Zitat
choose

Registriert seit: 2. Nov 2003
Ort: Bei Kiel, SH
729 Beiträge
 
Delphi 2006 Architect
 
#18

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 12:07
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.
Das sind Möglichkeiten, das Decorator-Pattern anzuwenden. Beides geeignet, allerdings stimme ich 1) nicht zu. Schließlich sind FileInputStream, SocketOutputStream oder ArrayInputStream die jeweiligen Enden dieser Ketten und benötigen deshalb keine weiteren Referenzen.
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:
while not myStream.IsEOF do
  DoSth(myStream.Read);
gepuffterte Daten aus einer Datei entschlüsseln und an eine Methode DoSth(...) übergeben.

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 ?
Wenn ich Deine Darstellung richtig verstanden habe, sollten sich diese Daten als "normale Daten des Stroms" vollkommen Transparent einarbeiten lassen, weil die jeweiligen Header- und Footer-Informationen nur für den jeweiligen Gegenpart (InputStream vs. OutputStream) von Bedeutung ist. Ein Pufferstream sollte deshalb ebenfalls diese Daten Puffern und braucht keine Kenntnis über die "Art der Information" zu besitzen.

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:
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;
Wie man erkennen kann, ist die Implementierung der Templatethoden nun relativ einfach in der Form
Delphi-Quellcode:
function TInputStream.BeforeRead(AnOutputStream: IOutputStream);
begin
  AnOutputStream.Write(SomeHeaderData);
end;
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(...)
an den FiFo-Puffer übergeben worden.

Fortsetzung folgt...
Miniaturansicht angehängter Grafiken
abb1inputstreams.png  
gruß, choose
  Mit Zitat antworten Zitat
choose

Registriert seit: 2. Nov 2003
Ort: Bei Kiel, SH
729 Beiträge
 
Delphi 2006 Architect
 
#19

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 12:35
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.
Bei der Erstellung eines InputStreams wäre zwar ein Wrapping denkbar in der Form
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:
myInStream:= BufferedInputStream(FileInputStream('InFile'), 4096);
myOutStream:= BufferedOutputStream(FileOutputStream('OutFile'), 4096);
while not myInStream.isEOF do
  myOutStream.Write(myInStream.Read);
myOutStream.Flush;
myOutStream.Close;
(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).

Diese Schleife bildet also das Bindeglied der Ein- und Ausgabeströme und kann ihrerseits in einer Hilfsklasse implementiert werden. zB
Delphi-Quellcode:
myConsigner:= StreamConsigner(AnInputStream, AnOutputStream);
while myConsigner.HasData do
begin
  myConsigner.ConsignData;
  Log('Transfered Bytes: %d', [myConsigner.DataCount]);
end;
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...

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.
Das sehe ebenfalls als notwendig an, um eine möglichst lose Kopplung zu erreichen. Die Vererbung von Interfaces halte ich auch für eine sinnvolle Variante, obwohl die Signaturen so mitunter zu sog. God-Facades mutieren können...
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:
function TMyStream.IsSame(const AStream: IStream): Boolean;
begin
  Result:= (Self as IStream)=AStream;
end;
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:
(Self as IStream)<>(Self as IInputStream) so dass über eine Methode GetIdentity in einem Wurzel-Interface nachgedacht werden sollte...
gruß, choose
  Mit Zitat antworten Zitat
Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#20

Re: Zugriffverletzung beim Verwenden von Interfaces

  Alt 16. Dez 2003, 12:39
@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
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 3     12 3      


Forumregeln

Es 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

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:31 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz