Einzelnen Beitrag anzeigen

Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#1

Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 11:14
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.
Angehängte Dateien
Dateityp: zip msxml_136.zip (9,9 KB, 7x aufgerufen)
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat