Hier habe ich mal vor einiger Zeit folgende (bis jetzt unbeantwortete) Frage gestellt:
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:
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;
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.
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:
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);
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.
(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:
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.
Und die Nutzung ist wie oben (synchron) nur eben asynchron und deswegen noch ein, zwei Zeilen mehr:
Delphi-Quellcode:
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;
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).
(*) 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.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.