![]() |
Kreuzreferenz von Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
![]() Hier mal eine "kleine" Frage an die Theoretiker. Objekte hinter Interfaces sind ja referenzbezogen. Das heißt man gibt sie nicht explizit frei, sondern wenn ihr Referenzzähler auf 0 zurückgeht. Dadurch entsteht ja der Aufwand (vom Compiler bzw. während der Laufzeit), dass man bei jeder Zuweisung von Interface zu Interface den Referenzzähler erhöhen muss. Und wenn ein Interfacezeiger seine Gütligkeit verliert (z.B. am Ende einer Methode) wird der Referenzzähler dekrementiert. Alles ähnlich wie bei dynamischen Strings/Arrays. (Soweit ist es ja auch bekannt) Im Gegensatz zu Strings kann man aber bei Interfaces Kreuzreferenzen (Fachbegriff?) basteln. Das bedeutet, dass es zwei Objekte gibt, welche Felder haben, die auf das jeweils andere Objekt zeigen. Wenn man das mit den Referenzählern macht, werden diese Objekte nie gelöscht und es bleibt ein Memoryleak. Eine "abstrakte" Variante sieht so aus:
Delphi-Quellcode:
Am Ende von Button1Click werden die Referenzzähler von Intf1 und Intf2 dekrementiert. Normalerweise sollten sie jetzt 0 sein, da aber jeder noch eine Referenz von dem jeweils anderen Interface auf sich hat, bleiben die Objekte hinter den Interfaces bestehen. Ich kann auch nicht mehr auf die Objekte zugreifen, da ich jede Referenz auf sie verloren habe.
type
IIntf2=interface; IIntf1=interface ['{81A6E8F6-B5E6-4AF9-86C4-BEDCE3369F73}'] function getIntf2:IIntf2; procedure setIntf2(value:IIntf2); property Intf2:IIntf2 read getIntf2 write setIntf2; end; IIntf2=interface ['{484490E2-9E0E-4C3C-95CD-5586B9FB6155}'] function getIntf1:IIntf1; procedure setIntf1(value:IIntf1); property Intf1:IIntf1 read getIntf1 write setIntf1; end; TIntf1=class(TInterfacedobject,IIntf1) function getIntf2:IIntf2; procedure setIntf2(value:IIntf2); private FIntf2:IIntf2; end; TIntf2=class(TInterfacedobject,IIntf2) function getIntf1:IIntf1; procedure setIntf1(value:IIntf1); private FIntf1:IIntf1; end; //der Implementationsteil dürfte klar sein //jetzt noch kurz die Anwendung: procedure TForm1.Button1Click(Sender: TObject); var Intf1:IIntf1; Intf2:IIntf2; begin Intf1:=TIntf1.create as IIntf1; Intf2:=TIntf2.create as IIntf2; Intf1.Intf2:=Intf2; Intf2.Intf1:=Intf1; end; Was ist damit in der Theorie? Ist sowas verboten? Nun denkt sich vielleicht der geneigte Leser, was will der sirius damit? Sowas macht man ja auch nicht. Welcher Idiot würde sowas real programmieren? Nunja, ICH! Und ich habe eine Weile gesucht, bis ich den Fehler gefunden habe. Jetzt will ich die reale Umsetzung hier nicht vorenthalten. Was habe ich gemacht? Meine Lieblingslandesbibliothek (das Ding, wo man Bücher aus Papier ausleihen kann), welche seit einiger Zeit keine Mails mehr verschickt, wenn meine Bücher ablaufen (weswegen ich des öfteren schon gespendet habe), hat aber einen RSS-Feed. Und zwar kann ich den zu meinem Account haben. Das ganze geht über https und bereits in der Adresse ist Benutzernummer und ein generiertes Passwort enthalten. So komme ich zum Feed und kann ihn mir ansehen (die Adresse bekomme ich in meinem Nutzeraccount). Da ich aber nicht immer da reinsehe und mein Feed auch keinerlei Anstalten macht mich an irgendetwas zu erinnern, dachte ich, ich schreib mal kurz ein Programm, welches den Feed ausliest und dann nette Hinweise auf den Bildschirm schiebt. Hauptsächlich wegen dem https, was Indy nicht so kann (oder nur umständlich,...) habe ich mich für die MSXML-Bibliothek des IE entschieden. Das klappt auch soweit. Also ich bekomme den XML-Text des RSS-Feeds. Analysieren klappte auf Anhieb nicht mit der MSXML, aber dafür haben wir ja in Delphi eigene Sachen. Alles schön und gut. Das ganze sind drei Befehle und sieht etwa so aus:
Delphi-Quellcode:
Da ich den Parameter aAsyncLoad auf False gesetzt habe, ist die Methode send blockierend und ich kann hinterher bspw. mit httpRequest.responsetext das Ergebnis in einem Widestring abfragen.
var httpRequest:Variant
.. //1: Objekt initialisieren GUID:=ProgIDToClassID('Msxml2.XMLHTTP'); httpRequest:=CreateComObject(GUID) as IDispatch; //hier mal IDispatch, da ich ohne Typelib arbeite //2: http-Request vorbereiten //open(aRequestMethod, aRequestUrl, aAsyncLoad) httpRequest.open('Get','http://www.delphipraxis.net',false); //mal eine "normale" url //3: Request starten //send(aRequestBody) httpRequest.send(null); (Warum ich hier die Dispatch-Schnittstelle genommen habe, soll jetzt weiter nicht interessieren und hat mit dem Thema nichts zu tun.) Http-Requests können natürlich ne ganze Weile dauern, solange wäre mein Programm blockiert. Um das zu verhindern gibts den Parameter aAsyncload. Den kann man auch auf True setzen. Dann darf man natürlich nicht gleich nach Send Responsetext abfragen, sondern muss in einem Thread oder in einem Timer den Status des Objektes (httpRequest.readystate + httpRequest.status) abfragen. Also pollen. Das ist nun nicht so in meinem Sinne. Jetzt gibt es noch ein Event, was man zuordnen kann: "httpRequest.onreadystatechange". Dazu ist zu sagen, dass in der Doku verboten wird, dieses Ereignis in nativen Code zu benutzen. Warum? Keine Ahnung. Wer mich kennt, weis, sowas hält mich nicht auf. Ums kurz zu machen, dort wird eine IDispatchschnittstelle erwartet, von der wird Invoke aufgerufen (alle Parameter sind 0). In das Objekt hinter der Schnittstelle kann ich meine Eventmethode reinbringen. Nun hilft ja so ein Event meist nix, wenn man keinen "Sender" hat. Gerade hier kann man mehrere asynchrone Requests parallel abschicken. Also muss ich dem Objekt auch eine Referenz auf mein benutztes httpRequest geben. Ich will ja den richtigen responsetext. Und auch Fehler beim Request werden in dem Objekt hinter httpRequest gespeichert. Und schon habe ich meine Kreuzreferenz. Hier nochmal in Delphi:
Delphi-Quellcode:
Und die Nutzung ist wie oben (synchron) nur eben asynchron und deswegen noch ein, zwei Zeilen mehr:
type TXMLHttpEventProc=procedure(XMLHttpReuqest:IDispatch) of object;
IXMLHttpEvent=interface(IDispatch) ['{9226D052-376E-4A46-8022-9C8DC7FCA696}'] procedure setXMLHttpRequest(value:IDispatch); function getXMLHttpRequest:IDispatch; procedure setEvent(value:TXMLHttpEventProc); function getEvent:TXMLHttpEventProc; property XMLHttpRequest:IDispatch read getXMLHttpRequest write setXMLHttpRequest; property OnEvent:TXMLHttpEventPRoc read getEvent write setEvent; end; TXMLHttpEvent=class(TInterfacedObject,IDispatch,IXMLHttpEvent) private FonEvent:TXMLHttpEventPRoc; FXMLHttpRequest:IDispatch; protected procedure setXMLHttpRequest(value:IDispatch); function getXMLHttpRequest:IDispatch; procedure setEvent(value:TXMLHttpEventPRoc); function getEvent:TXMLHttpEventProc; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; virtual; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; virtual; stdcall; function GetTypeInfoCount(out Count: Integer): HResult; virtual; stdcall; function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; virtual; stdcall; end; implementation function TXMLHttpEvent.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; const IID_Null:TGUID='{00000000-0000-0000-0000-000000000000}'; begin if (not IsEqualIID(iid, IID_NULL))or(DispID<>0) then Result := DISP_E_UNKNOWNINTERFACE else begin //wenn DispID und IID 0 sind, ist das Ereignis eingetreten //(anders wird das Interface auch nicht aufgerufen; Parameter gibt es keine) Result:=S_ok; try if Assigned(FonEvent) and (assigned(FXMLHttpRequest)) then FOnEvent(FXMLHTTPRequest); //hier könnte man einen Kreuzverweis löschen (*) except Result := DISP_E_EXCEPTION; end; end; end; //die anderen Methode lasse ich mal wieder raus. Sind ja blos Zuweisungen.
Delphi-Quellcode:
Wie man sieht habe ich den Kreuzverweis gelöscht. Dazu muss aber meine Eventmethode annehmen, dass das httpRequest nicht mehr gebraucht wird. Das gehört nich in ihren Aufgabenbereich. Ich ziehe auch damit dem Objekt quasi noch während des Betriebes die Instanz unter den Füßen weg. (Komischerweise funktioniert die Zuweisung auch bei der Klasse httpRequest nicht, naja).
procedure TForm1.Button2Click(Sender: TObject);
var GUID:TGUID; httpRequest:oleVariant; tempvariant:oleVariant; httpEvent:IXMLHttpEvent; begin //COM-Object erstellen //auf variant, damit fällt die Interfacedeklaration weg GUID:=ProgIDToClassID('Msxml2.XMLHTTP'); httpRequest:=CreateComObject(GUID) as IDispatch; //http-Request vorbereiten //open(aRequestMethod, aRequestUrl, aAsyncLoad) httpRequest.open('Get','http://www.delphipraxis.net',true); //bei asynchronen Aufruf hätte ich gern ein Ereignis httpEvent:=TXMLHttpEvent.create as IXMLhttpEvent; httpEvent.OnEvent:=onhttpEvent; //Ereignis festlegen httpEvent.XMLHttpRequest:=httpRequest; // !!! Kreuzverweis !!! tempvariant:=httpEvent as IDispatch; //onreadystatechange verlangt variant httpRequest.onreadystatechange:=tempvariant; //Request starten //send(aRequestBody) httpRequest.send(null); //bei einem synchronen Request (3. Parameter in open = False) //würde Methode send blockieren, bis die Seite heruntergeladen ist end; procedure TForm1.onhttpEvent(XMLHttpReuqest: IDispatch); var varhttpRequest:Variant; //den Request nur als Variant, damit ich die Methode aufrufen kann temp:widestring; tempvar:oleVariant; begin varhttpRequest:=XMLHttpReuqest; if varhttpRequest.readystate=4 then //if Request beendet begin if varhttpRequest.status=200 then //kein Fehler begin temp:=varhttpRequest.responsetext; setlength(temp,20); showmessage(temp); end else showmessage('http-Request Fehler - '+inttostr(varhttpRequest.state)); varhttpRequest.onreadystatechange:=null; //Kreuzverweis evtl. hier löschen end; end; (*) In meinem Programm zum RSS-Feed habe ich in der Invoke-Methode des TXMLhttpEvent-Objekt die Referenz auf den httpRequest gelöscht (also den anderen Link). Aber dazu muss ja auch das Objekt wissen, wann ich den Request nicht mehr benötige. Das geht in dem Fall zwar (Readystate=4), aber das muss ja nicht so sein. Soviel zur Erklärung. Eine richtige Frage fällt mir dazu auch nicht mehr ein (außer die oben). Mich wundert halt nur, dass man dadurch auch bei Interfaces auf die Freigabe achten muss. |
Re: Kreuzreferenz von Interfaces
du könntest die Refferenzzählung Manipulieren (also z.B. um eines rabsetzen, bzw bei der Freibabe schon bei RefCount=1 freigeben)
Oder, wenn beide Interfaces eh immer zusammen freigegeben werden sollen, dann könnte man doch eintweder versuchen beide in ein Object zu quetschen, oder beiden eine gemeinsame Refferenzzählung verpassen? [add] aus so ähnlichen gründen hatte ich meinen Interfaces dort ![]() |
Re: Kreuzreferenz von Interfaces
Danke dir,
Ja, umgehen kann man hier einiges. Und, wie ich bereits schrieb, habe ich das Problem an sich auch gelöst. Nur: Ich musste bewusst auf die Referenzzählung achten. Das war doch nicht der Sinn der Referenzzählung bei Interfaces, oder? [zu add]Probleme mit der Refernzzählung gibt es (unter D7) ![]() |
Re: Kreuzreferenz von Interfaces
Was wär, wenn du die Variable FXMLHttpRequest:IDispatch; nicht als Interface definierst, sondern nur als Object-Referenz (ohne Referezzählung) ?
|
Re: Kreuzreferenz von Interfaces
Dann geht nach Button2Click der Referenzzähler auf 0 und mein Objekt wird gelöscht (mit Referenzzählung).
Und Ohne RZ ist es immernoch eine COM-Schnittstelle. Da habe ich keine Klassendefinition zu und kann schon gar nicht in die Referenzzählung eingreifen. Objekte und Interfaces sollte man auch nicht vermischen (also konsequent nur eins von beiden einsetzen), dass sieht man an TXMLDocument. Edit: Danke himitsu für die praktischen Vorschläge. Deine ersten Tipps würden auch im konkreten Fall funktionieren. Aber es geht, wie gesagt um den theoretischen Hintergund. Zitat:
|
Re: Kreuzreferenz von Interfaces
[offtopic]
Hi sirius, Zitat:
1) Man brauch doch nur TIdHTTP und TIdSSLIOHandlerSocketOpenSSL auf der Form. 2) Die aktuellen OpenSSL dlls ins Verzeichnis 3) ~ 3 Zeilen Code oder per OI setzen Dann etwas in Richtung:
Delphi-Quellcode:
*1: HandleRedirects werden zur Sicherheit häufig in Verbindung mit SSL nicht unterstützt
with IdSSLOpenSSL do
begin SSLOptions.Method := sslvTLSv1; // 1. Zeile SSLOptions.Mode := sslmUnassigned; // 2. Zeile end; with IdHTTP do begin IOHandler := IdSSLOpenSSL; // 3. Zeile HandleRedirects := False; // *1 Request.UserAgent := 'Mein Browser'; // z.B. auf MS IE string falls die Webseite gemein ist end; Den Rest macht Indy dann automatisch. Einfach alles so anwenden, als wenn es normales HTTP wäre. Alles läuft über SSL, sobald die URL mit https:// anfängt... Gruß Assertor [/offtopic] |
Re: Kreuzreferenz von Interfaces
Sorry, dass ich Indy so heruntergespielt habe.
Ich hatte damals nur schnell etwas gesucht um einen https-Request abzusetzen. Und da fand ich diese Aussage und dass es mit der XML-Bibliothek ganz einfach geht. Außerdem ist ja RSS sowieso XML, deswegen dachte ich auch, dass das hinterher zusammenpasst.... Ist eine längere Geschichte. Und im synchronen Modus sind meine 3 Befehle auch schneller getippt :zwinker: . Edit: Aber danke für den Hinweis. Falls ich mal mehr damit zu tun habe.... |
Re: Kreuzreferenz von Interfaces
Zitat:
Gruß Assertor |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:01 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