AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Kreuzreferenz von Interfaces

Offene Frage von "sirius"
Ein Thema von sirius · begonnen am 13. Feb 2009 · letzter Beitrag vom 13. Feb 2009
Antwort Antwort
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, 12: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
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.033 Beiträge
 
Delphi 12 Athens
 
#2

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 12:30
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 Interface + (Record)Operatoren auch eine eigene Refferenzzähling verpaßt ... gab zwar keine Kreuzbezüge, aber die Original-Zählung zickte etwas rum.
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

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

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 12:33
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) auch mit TXMLDocument, wenn man als AOwner nil angibt.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.033 Beiträge
 
Delphi 12 Athens
 
#4

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 12:39
Was wär, wenn du die Variable FXMLHttpRequest:IDispatch; nicht als Interface definierst, sondern nur als Object-Referenz (ohne Referezzählung) ?
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

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

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 12:43
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:
Hier mal eine "kleine" Frage an die Theoretiker.
...
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?
Es ging mir wie gesagt, vorwiegend um das Beispiel mit IIntf1 und IIntf2 am Anfang des Posts. Danach habe ich nur noch das Beispiel gepostet, worauf ich auf die theoretische Frage kam.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Assertor

Registriert seit: 4. Feb 2006
Ort: Hamburg
1.296 Beiträge
 
Turbo C++
 
#6

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 13:12
[offtopic]

Hi sirius,
Zitat von sirius:
Hauptsächlich wegen dem https, was Indy nicht so kann (oder nur umständlich,...)
Warum Umständlich?

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:
 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;
*1: HandleRedirects werden zur Sicherheit häufig in Verbindung mit SSL nicht unterstützt

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]
Frederik
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

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

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 13:31
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 .

Edit: Aber danke für den Hinweis. Falls ich mal mehr damit zu tun habe....
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Assertor

Registriert seit: 4. Feb 2006
Ort: Hamburg
1.296 Beiträge
 
Turbo C++
 
#8

Re: Kreuzreferenz von Interfaces

  Alt 13. Feb 2009, 14:02
Zitat von sirius:
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 .

Edit: Aber danke für den Hinweis. Falls ich mal mehr damit zu tun habe....
Kein Problem. Will nur nicht, daß das als Fakt stehenbleibt wenn das unerfahrene Neulinge lesen. Aber mit XML/RSS ist es sicherlich ohne Umwege leichter.

Gruß Assertor
Frederik
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:06 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