![]() |
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:
EDIT:
access violation at 0x401c3c: write of address 0x5b
89 02 89 50 04 5B C3 90 8B 15 75 E4 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 |
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.
|
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 |
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.) |
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:
wird deshalb vom Compiler um Code ergänzt, den man etwa so schreiben könnte
var
myObject: IMyInterface begin myObject:= GetAnObject; myObject.AMethod; myObject:= GetAnotherObject; myObject.AnotherMethod; end;
Delphi-Quellcode:
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.
begin
myObject:= GetAnObject; myObject._AddRef; myObject.AMethod; myObject._Release; myObject:= GetAnotherObject myObject._AddRef; myObject.AnotherObject; myObject._Release; end; Betrachtet man nun die Implementierung von _Release in dem von Dir verwendeten Vorfahren TInterfacedObject:
Delphi-Quellcode:
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
function TInterfacedObject._Release: Integer;
begin Result := InterlockedDecrement(FRefCount); if Result = 0 then Destroy; end;
Delphi-Quellcode:
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).
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; 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... |
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:
|
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.
|
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 |
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 |
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 |
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 |
Re: Zugriffverletzung beim Verwenden von Interfaces
Zitat:
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? |
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 |
Re: Zugriffverletzung beim Verwenden von Interfaces
Zitat:
Zitat:
Delphi-Quellcode:
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...
3.) die Datentypen der ICipher auf denen sie verschlüsseln, eben Delphi abhängig sind, zB. TStream, LongString usw.
Delphi-Quellcode:
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.
Ich weiß nich so recht ob das ein gutes Design ist !?
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 |
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? |
Re: Zugriffverletzung beim Verwenden von Interfaces
Zitat:
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:
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:
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:
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 |
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:
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 |
Re: Zugriffverletzung beim Verwenden von Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
Dann also kein neuer Thread, aber ordentlich lange Beiträge ;)
Zitat:
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:
und mit eine Schleife der Form
myStream:= DecryptInputStream(BufferedInputStream(FileInputStream('myFile'), 4096));
Delphi-Quellcode:
gepuffterte Daten aus einer Datei entschlüsseln und an eine Methode DoSth(...) übergeben.
while not myStream.IsEOF do
DoSth(myStream.Read); Zitat:
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... |
Re: Zugriffverletzung beim Verwenden von Interfaces
So, erst einmal 'nen neuen Kaffe und nun geht's weiter ;)
Zitat:
Delphi-Quellcode:
ä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.
myStream:= WrappingInputStream(AnOutputStream);
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:
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;
Delphi-Quellcode:
so dass über eine Methode GetIdentity in einem Wurzel-Interface nachgedacht werden sollte...
(Self as IStream)<>(Self as IInputStream)
|
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 |
Re: Zugriffverletzung beim Verwenden von Interfaces
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:40 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz