Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Zugriffverletzung beim Verwenden von Interfaces (https://www.delphipraxis.net/13244-zugriffverletzung-beim-verwenden-von-interfaces.html)

mirage228 14. Dez 2003 17:30


Zugriffverletzung beim Verwenden von Interfaces
 
Hallo,

ich habe mich die letzten Tage mal mit Interfaces beschäftigt und habe sie auch in mein Programm eingebaut.

Leider bin ich da auf ein Problem gestoßen...
In meiner Anwendung ist folgende Umgebung gegeben:

Ich hab das Interface ICipher, dass ein paar Eigenschaften (+ die dazugehörigen Get und Set Methoden) und ein paar Prozeduren (als stdcall deklariert) beinhaltet.

Ich leite davon die Klasse TCipher zusammen mit TInterfacedObject ab, die die Methoden des Interfaces definiert. Die Methoden ändern Felder im private Bereich von TCipher, die Get und Sets selbst im protected bereich. Hierbei werden die Prozeduren, welche im ICipher Interface als stdcall deklariert sind, zusätzlich mit den Direktiven virtual und abstract versehen.

Nun habe ich DLLs die eine Prozedur exportieren, die einen VAR Parameter des Typs ICipher verlangt.
Diese DLL exportiert einen Nachfahren von TCipher, welche die oben genannten Prozeduren mit override überschreibt. Diese Nachfahren haben zusätzlich noch ein paar private Felder, die intern für die Verwaltung von diversen Einstellungen verwendet werden. (Es gibt auch noch ein bis zwei, die keine zusätzlichen privaten Felder besitzen).

In meinem Programm habe ich noch zusätzlich die Klasse TCustomCipher, die ein ICipher Objekt sowie ein Feld für den Namen und das Handle einer DLL hat. Beim erstellen (Create) von TCustomCipher wird die exportierte Prozedur aus der DLL aufgerufen und das ICipher Objekt als Parameter übergeben. In der DLL wird dann eine Instanz erstellt (z.B. Cipher := TMyCipher.Create() ).

Nun habe ich da noch eine Klasse TCipherList, die eine Liste für die TCustomCiphers darstellt, u.A. mit Suchfunktion.

Nun habe ich folgendes Problem:
Ich hab in einer Combobox die Namen der Verschlüsselungen stehen. Beim onChange werden Labels mit den Eigenschaften (Beschreibung, Entwickler, etc.) gefüllt. Es wird SearchCipher aufgerufen, um den Index in der Liste zu ermitteln (ich prüfe vor dem fortfahren ob der Index gültig ist!).
Bei den Klassen ohne zusätzliche, interne PRIVATE Felder, tritt kein Fehler auf. Bei den anderen nach 1-2 Aufrufen. Die onChange-Prozedur läuft korrekt zu Ende, aber danach wird eine Access Violation ausgelöst. Das seltsame ist, dass diese nicht der MessageBox meines Application.OnException Handlers entspricht.
Diese Meldung wird anscheinend irgendwo anders ausgelöst und ich weiss nicht wo...

Code:
access violation at 0x401c3c: write of address 0x5b
89 02 89 50 04 5B C3 90 8B 15 75 E4
EDIT:
Weitere Fehler die auftauchen (vom OnException Handler abgefangen):
- Ungültige Zeigeroperation (Zugriff auf eine Variable der Cipher: ICipher von TCustomCipher)
- Zugriffsverletzung, auch in DelphiMM.DLL (Beim Aufruf von SearchCipher)

Wenn ich den Combox mit dem Mausrad scrolle tritt sofort die zuerst genannte Access Violation auf. Wenn ich die Items manuell auswähle kriege ich nach einer Zeit einer der unteren Fehler. Und ich weiss nicht worans liegt...

Ich kann danach die Anwendung in Delphi nicht mehr weiterlaufen lassen (der RUN Button ist disabled). Wenn ich das Programm aus Windows heraus starte, wird die Anwendung nach dem Fehler ohne Warnung beendet.

Kann mir jemand helfen?

mfG
mirage228

Kamil 14. Dez 2003 18:37

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Ich hatte mal so ein ähnliches Problem: ich habe ein Interface an ein anderes Objekt übergeben. Sobald dann das Objekt mit dem Interface arbeiten wollte gab es Fehler. Die Lösung bei mir war ein explizites Aufrufen von _AddRef nach dem Übergeben des Interface und wenn es nicht mehr gebraucht wurde ein Aufruf von _Release.

mirage228 14. Dez 2003 20:17

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Hi,

danke für deine Antwort.

Wo muss ich jetzt genau das _AddRef und _Release aufrufen. Bei mir dann in der Ladeprozedur von TCustomCipher?

Aufjeden fall konnte ich jetzt durch ein setzen von _AddRef bzw. _Release ein paar mal mehr darauf zugreifen, bevor wieder alles versagte...

EDIT: Durch Herumprobieren habe ich es solange geschaft durchzuhalten bis mein Programm mir den Fehler ausgab, dass die gewählte Verschlüsselung nicht in der Liste gefunden wurde. Im Evaluator hatten die Einträge alle keinen Namen, bis auf einen der den Namen Schriftart "Fixedsys" trug?!
Alles sehr seltsam...
EDIT2: Also bei einem Rekonstruierungsversuch ging es dann wieder nicht. Also da stimmt was wirklich ernsthaftes nicht...

mfG
mirage228

Kamil 14. Dez 2003 23:39

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Zeig mal ein wenig Code.

Ein paar Tipps:
-überschreibe _AddRef und _Release (mit dem selben Code wie TInterfacedObject) und schau dir den Wert von FRefCount an. Sobald er 0 wird, wird dein Objekt (Interface) zertört.
-beim Zugriff auf dein Objekt/Interface immer mit Assigned prüfen ob das Objekt noch existiert.

Ich weiß nicht wie gut du dich mit Interfaces auskennst:
Delphi-Quellcode:
//Dieser Code ist richtig:
procedure TForm1.Button8Click(Sender: TObject);
var
  MyInterface: IMyInterface;
  MyObject: TMyInterfacedObject;
begin
  MyObject:=TMyInterfacedObject.Create;
  MyInterface:=MyObject;
  MyObject.DoSomething;
  MyInterface.DoSomething;
end; //der Destruktor TMyInterfacedObject.Destroy wird automatisch aufgerufen!!! 

//Dieser Code ist FALSCH!!!!!!
procedure TForm1.Button8Click(Sender: TObject);
var
  MyInterface: IMyInterface;
  MyObject: TMyInterfacedObject;
begin
  MyObject:=TMyInterfacedObject.Create;
  MyInterface:=MyObject;
  MyObject.DoSomething;
  MyInterface.DoSomething;
  MyObject.Free;
end; //Der Destruktor wird zwei mal aufgerufen -> Exception (Invalid pointer operation.)

choose 15. Dez 2003 00:04

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Hallo mirage228,

wenn ich Deine Ausführungen richtig verstanden habe, liegt die Ursache des Problems in der heterogenen Verwendung von Klassen(-Objekten) und (Objekt-)Interfaces.
Bedingt durch das in Delphi verwendete Interface-Konzept wird bei der Arbeit mit Interfaces eine implizite Referenzzählung vorgenommen.

Der folgende Code
Delphi-Quellcode:
var
  myObject: IMyInterface
begin
  myObject:= GetAnObject;
  myObject.AMethod;
  myObject:= GetAnotherObject;
  myObject.AnotherMethod;
end;
wird deshalb vom Compiler um Code ergänzt, den man etwa so schreiben könnte
Delphi-Quellcode:
begin
  myObject:= GetAnObject;
  myObject._AddRef;
  myObject.AMethod;
  myObject._Release;
  myObject:= GetAnotherObject
  myObject._AddRef;
  myObject.AnotherObject;
  myObject._Release;
end;
Tatsächlich variiert der Aufruf von _Release ein wenig, so dass in diesem Beispiel die Methode erst nach dem Aufruf von GetAnotherObject aufgerufen wird, darüber hinaus sollte man sich die Referenzzählung von try..finally-Blöcken umschlossen vorstellen, der Einfachheit halber habe ich das aber vernachlässigt.

Betrachtet man nun die Implementierung von _Release in dem von Dir verwendeten Vorfahren TInterfacedObject:
Delphi-Quellcode:
function TInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;
Erkennt man, dass Objekte dieses Typs freigegeben werden, sobald der Referenzzähler null erreicht hat. Weil ein solches Objekt nach dem Verlassen des Konstruktors mit null belegt ist führt dieser Code
Delphi-Quellcode:
var
  myClassicalObject: TInterfacedObject;
  myInterfacedObject: IInterface;
begin
  myClassicalObject:= TInterfacedObject.Create;
  myInterfacedObject:= myClassicalObject; // implicit call of _AddRef
  myInterfacedObject:= nil; // implicit call of _Release -> Free;

  // !myClassicalObject contains an invalid reference, now
  Showmessage(IntToStr(myClassicalObject.RefCount));
end;
zu Problemen (dass der Code fehlerfrei funktionieren kann, liegt an der Speicherverwaltung von Delphi, führt aber spätestens bei mehreren parallelen Verarbeitungssträngen zu Problemen).

Wenn Du Dich mit diesem Phänomen eingehender beschäftigen möchtest, empfehle ich Dir, eine Testklasse zu implementieren, die die Methoden _AddRef und _Release sowie den Aufruf des Destruktors protokolliert, bzw den Code im integrierten Debugger mit Debug-DCUs und einem Nachfahren von TInterfacedObject mit Breakpoints in den entsprechenden Zeilen der Unit System zu analysieren.

Lösen lassen sollte sich das Problem relativ einfach, indem Du entweder ausschließlich "klassische Objekte" oder Interfaces verwendest. Listen für den letzteren Fall lassen sich dann zB mithilfe von TInterfaceList realisieren...

choose 15. Dez 2003 00:11

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Hallo Kamil, netter Beitrag von Dir (auch die Bezeichnungen der Typen und Variablen gefallen mir gut ;)). Widersprechen muss ich Dir in diesem Punkt
Zitat:

Zitat von Kamil
beim Zugriff auf dein Objekt/Interface immer mit Assigned prüfen ob das Objekt noch existiert.

Der Aufruf von Assigned prüft lediglich, ob eine Variable ungleich nil ist, so dass "verlorene Referenzen" mit dieser Funktion nicht aufgespürt werden können. Auch kann mit dieser schlichten Überprüfung nicht unterschieden werden, ob an einer bestimmten Speicherstelle tatsächlich einmal ein "Objekt (gewesen) ist" oder es sich bei dem als Referenz interpretierten Wert um "Müll" vom Stack handelt...

Kamil 15. Dez 2003 00:21

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Ich gehe davon aus, dass Objekte nach dem Freigeben brav auf nil gestetzt werden oder FreeAndNil verwendet wird wenn Objekte mehrmals erstellt und gelöscht werden oder nicht unbedingt existieren müssen. Wenn das Objekt am Anfang erstellt und am Ende gelöscht wird hat es natürlich keinen Sinn. Ich glaube es gab erst letztens einen Beitrag über das verwenden von FreeAndNil.

negaH 15. Dez 2003 10:39

Re: Zugriffverletzung beim Verwenden von Interfaces
 
@Kamil, aber auch das nützt nicht viel, bei Mehrfachreferenzen auf dieses Object. Denn mit FreeAndNil() würde ja nur die Original Reference gesäubert. Alle anderen Referenzen zeigen denoch auf die ehemeals gültige Speicheradresse. Es bleibt technisch gesehen nur eine einzigste korrekte Annahme übrig, der Programmierer sollte über Reference Counting sicherstellen das zu jeder Zeit ein gültiges Object vorliegt.

Gruß Hagen

negaH 15. Dez 2003 10:42

Re: Zugriffverletzung beim Verwenden von Interfaces
 
@Mirage: ich würde gerne noch mehr über deine Library erfahren !? Soviel wie ich erahnen konnte willste Verschlüsselungsalgorithmen per Interfaces kapseln. Da ich selber schon sehr viel damit rumgebastelt habe würde ich gerne über das eigentliche Klassen-/Interface Konzept diskutieren. Bisher habe ich nämlich noch kein absolut sauberes Konzept gefunden, das alle wichtigen Interfaces unterstützen könnte.

Gruß Hagen

mirage228 15. Dez 2003 12:15

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Hallo,

danke für eure zahlreichen Antworten!

Mittlerweile bin ich mir schon sicher, dass eines (oder mehrere) meiner Objekte freigegeben wird und ich deshalb nicht mehr darauf zu greifen kann (bzw. nur mit oben genannten Fehlern).

Meine Annahme ist derzeit, das nach dem Benutzen in einer Prozedur im Hauptprogramm (Abrufen der Eigenschaften etc.) der Destructor aufgerufen und mein Objekt freigegeben wird und das die Fehler verursacht.

Ich werde heute nachmittag das ganze ausführlich debuggen, um hoffentlich die Fehlerquelle zu finden und diese zu beseitigen.

@negaH:

Ich hatte mein Konzept folgendermaßen geplant:
Ich habe das Interface ICipher, welche einige Get und Set Methoden für Eigenschaften, wie Namen und Beschreibung der Verschlüsselung hat, sowie Prozeduren, zum Ver- und Entschlüsseln von Texten und Dateien (insgesamt also 4). Sowie noch 2 Prozeduren der ich einen Stream übergebe, damit die Verschlüsselung ihre Einstellungen (falls erforderlich) darin speichern bzw. daraus lesen kann.

Jetzt müsste eigentlich jede Verschlüsselung DLL diese ganzen Get und Set Methoden implementieren. Da habe ich TCipher für geschrieben. Es ist abgeleitet von TInterfacedObject und ICipher implementiert diese Methoden und versieht zu dem die 6 anderen Prozeduren mit virtual; und abstract; damit diese von den Klassen der Verschlüsselung DLL überschrieben werden könnte. Das ganze ist jedoch optional. Wer möchte, kann auch trotzdem seine eigene Implementierung schreiben. TCipher vereinfacht das ganze jedoch. In meinen Verschlüsselungs-DLLs sind die Klassen von TCipher abgeleitet und überschreiben die 6 Prozeduren (Manche haben noch ein Paar private Variablen, z.B. zum Speichern der Schlüssellänge etc.) Die DLL exportiert diese Klasse mit eine Funktion die einen VAR Parameters des Typs ICipher erwartet.

Im Hauptprogramm habe ich dann die Klasse TCustomCipher, die dein ICipher Objekt und DLL Handle und Namen speichert. Beim Constructor wird eine DLL geladen und die Verschlüsselung aus der DLL importiert.

TCipherList rundet das ganze ab, in dem es alle .dll aus einem angegebenen Verzeichnis holt und dafür dann die TCustomCipher Objekte erstellt.

Im Programm suche ich mir dann die Verschlüsselung der Liste, zeige ihre Eigenschaften an oder Ver/Entschlüsse damit.

Ich hoffe das war das, was du wissen wolltest.

mfG
mirage228

negaH 15. Dez 2003 12:46

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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

choose 15. Dez 2003 13:05

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Zitat:

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... :gruebel:
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?

negaH 15. Dez 2003 16:13

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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

mirage228 15. Dez 2003 17:01

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Zitat:

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.

Delphi-Quellcode:
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...

Delphi-Quellcode:
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

choose 15. Dez 2003 23:00

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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?

negaH 15. Dez 2003 23:41

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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

negaH 15. Dez 2003 23:57

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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

choose 16. Dez 2003 11:07

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Liste der Anhänge anzeigen (Anzahl: 1)
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:
Delphi-Quellcode:
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...

choose 16. Dez 2003 11:35

Re: Zugriffverletzung beim Verwenden von Interfaces
 
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
Delphi-Quellcode:
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:
Delphi-Quellcode:
(Self as IStream)<>(Self as IInputStream)
so dass über eine Methode GetIdentity in einem Wurzel-Interface nachgedacht werden sollte...

negaH 16. Dez 2003 11:39

Re: Zugriffverletzung beim Verwenden von Interfaces
 
@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

choose 16. Dez 2003 12:09

Re: Zugriffverletzung beim Verwenden von Interfaces
 
Zitat:

es gibt aus meiner Sicht ganz bestimmte Anforderungen die erfüllt sein müssen.
Leider hast Du nach Deiner Ausführung lediglich zwei Alternativen (Schnittmenge vs. Vereinigungsmenge) dargestellt, nicht aber deine "ganz besteimmten Anforderungen". Welche der Strategien möchtest Du nun tatsächlich verfolgen, wo liegt der Schwerpunkt?

Zitat:

Das Durchreichen des Bearbeitungsstatus, [...] gefällt mir auch nicht so sehr.
Das ist eine lediglich innerhalb der Klasse verwendete Information. Sie wird nicht über eine Klasse hinaus verwendet, sondern dient lediglich der Vereinfachung der Implementierung.

Zitat:

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.
Ich Denke, dass die Verwaltung von Dokumentenhierachien oder -Verzeichnissen über die Standardaufgaben eines Streams hinausgehen und entweder über Hilfsklassen oder zusätzliche Schnittstellen gelöst werden sollten.

Zitat:

Interface-technisch gesehen möchte ich nach Möglichkeit immer Zustandslose Interfaces erreichen, logisch.
Der Ansatz mit IsEOF beschreibt doch bereits ein zustandsbehaftetes Interfaces. Wie möchtest Du jede Operation atomar halten, wenn die größe eines Streams nocht nicht bekannt ist, bzw es sich um einen Live-Stream handelt?

Zitat:

Wenn man sich ähnliche Kontrukte [...] anschaut so wird man feststellen das die [...] Basis Interfaces enorme Monster-Interface mit sehr vielen Methoden sind. Aber genau das muß vermieden werden.
Von Zeit zu Zeit ist der Einsatz sog. Fassaden sinnvoll, um beim Umgang mit Teilsystemen eine grobere Granularität zu erreichen, für diesen Fall halte ich aber wenige Methoden pro Typ auch für sinnvoll, sofern die Funktionalität nicht weiter aufgebläht wird (Bruch mit dem Zuständigkeitsprinzip).

Zitat:

falls du weitergehendes Interesse hast würde es mich freuen wenn wir Nägel mit Köpfen machen würden.
Klar, im Rahmen meiner Zeit gerne. Vielleicht könntest Du Deine genaue Anforderung anhand von zwei bis drei voneinander verschiedenen Beispielen genauer skizzieren?


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:47 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