Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Methodenaufruf umbiegen? (https://www.delphipraxis.net/143952-methodenaufruf-umbiegen.html)

Neutral General 26. Nov 2009 17:20


Methodenaufruf umbiegen?
 
Hi,

Ich komme einfach direkt zum Punkt:

Delphi-Quellcode:
TTest = class
  function GetTest: String;
  function GetTest2: String;
end;
Ist es möglich einen Aufruf von GetTest auf GetTest2 umzubiegen?

Medium 26. Nov 2009 17:35

Re: Methodenaufruf umbiegen?
 
Mit ein wenig Umweg:

Delphi-Quellcode:
type
  TMyFunc = function: String of object;

  TMyClass
  private
    function FFunc1: String;
    function FFunc2: String;
  public
    GetText: TMyFunc;
    GetText2: TMyFunc;
  end;

implementation

constructor TMyClass(...)
begin
  GetText := FFunc1;
  GetText2 := FFunc2;
end;
Und die Zuweisungen kannst du zur Runtime beliebig umherwürfeln. Ich bin mir grad nur unsicher, ob die Syntax für TMyFunc so geht, bislang hatte ich irgendwie immer nur procedure of object genutzt :)

Neutral General 26. Nov 2009 17:50

Re: Methodenaufruf umbiegen?
 
Hi,

Ich habs wohl zu allgemein ausgedrückt..

Auf die Struktur der Klasse habe ich letztendlich keinen Einfluss. Genauer gesagt habe ich soetwas:

Delphi-Quellcode:
TBeispiel = class
private
  FText: String;
  function GetText: String;
public
  property Text: String read GetText;
end;
Diese Klasse ist jetzt auch fest. Daran darf im Code nichts geändert werden.

Was ich jetzt per RTTI rausfinden kann sind die Properties.
Ich hab den Namen der Property und die Pointer zu der jeweiligen Getter und Setter Methode.

Was ich jetzt prinzipiell will ist GetText zu "hooken".

So in der Art:

Delphi-Quellcode:
function GetTextHook(Self: TBeispiel): String;
begin
  Result := Self.GetText + '_Hooked!';
end;

HookProperty(TBeispiel,'Test',mkGetter,@GetTextHook);

Medium 26. Nov 2009 17:53

Re: Methodenaufruf umbiegen?
 
Wie wäre es mit einer Ableitung der Klasse, die alles wie ihr Vorfahr durchreicht, aber die Property überdeckt und auf decorator-artige Weise erweitert? (Ich weiss nur grad nicht so 100%ig ob sich Properties überhaupt verdecken lassen...)

Apollonius 26. Nov 2009 17:53

Re: Methodenaufruf umbiegen?
 
Wenn du das hooken willst, musst du das ganz klassisch machen, also einen Jump bzw. Call an den Anfang des Methodencodes schreiben.

himitsu 26. Nov 2009 18:01

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Apollonius
Wenn du das hooken willst, musst du das ganz klassisch machen, also einen Jump bzw. Call an den Anfang des Methodencodes schreiben.

Wenn er aber den Anfang überschreibt, dann wird es hiermit schwerer
Code:
result := [color=#ff0000]Self.GetText[/color] + '_Hooked!';
(gut, man darf dieses jetzt nicht wörtlich nehmen, denn es würde eine kleine Endlosschleife entstehen :stupid: )

Neutral General 26. Nov 2009 18:08

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Medium
Wie wäre es mit einer Ableitung der Klasse, die alles wie ihr Vorfahr durchreicht, aber die Property überdeckt und auf decorator-artige Weise erweitert? (Ich weiss nur grad nicht so 100%ig ob sich Properties überhaupt verdecken lassen...)

Das ist aber nicht was ich will..

Zitat:

Zitat von Apollonius
Wenn du das hooken willst, musst du das ganz klassisch machen, also einen Jump bzw. Call an den Anfang des Methodencodes schreiben.

Ja theoretisch wäre das auch mein erster Ansatz gewesen.. Problem ist nur, dass ich blöderweise einfach keinen Platz dafür hab. Ich überschreibe ja direkt wichtigen Code :?

Apollonius 26. Nov 2009 18:11

Re: Methodenaufruf umbiegen?
 
Es geht aber nicht anders. Eigenschaften existieren im Kompilat nur noch als RTTI, die Aufrufe selbst sind nur noch Methodenaufrufe bzw. Feldzugriffe. Zum Hooken musst du dir irgendeine Grenze zwischen Anweisungen suchen. Das ist ein bisschen schwierig, weil du dazu schon fast Maschinencode parsen musst. Quick & Dirty geht es aber meistens, schau dir einfach mal an, was für Code der Delphi-Compiler im Prolog generiert.

Neutral General 26. Nov 2009 18:14

Re: Methodenaufruf umbiegen?
 
Das hier:

Delphi-Quellcode:
push ebx
mov ebx, edx
// Result := 'Test';
mov eax, ebx
mov edx, $004D6A10
call UStrAsg
// end;
pop ebx
ret
allerdings ist da halt wirklich nicht viel Platz... :?
Das sind also 3 Bytes bevor der "Result := 'Test'"-Teil kommt... etwas wenig..

Medium 26. Nov 2009 18:42

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Neutral General
Das ist aber nicht was ich will..

Aber es könnte das tun, was du hier gezeigt hast ;)

Edit: Alles andere liefe auf recht unschönes Gehacke in der Methodentabelle hinaus, was nicht nur extrem unelegant ist, sondern aufgrund der Freiheit die ein Compiler da hat auch praktisch unportierbar - möglicherweise eben auch innerhalb der Delphiversionen. Evtl. können wir besser hier weiter machen, wenn du konkret vorstellst, was du möchtest, und vor allem in welchem Kontext. Die bisherigen Beispiele waren offenbar zu stark minimiert.

Neutral General 26. Nov 2009 18:43

Re: Methodenaufruf umbiegen?
 
Es soll aber mit JEDER Klasse funktionieren. Wie soll ich von ner Klasse ableiten, die jemand der meine Unit benutzen wird gerade erst baut?

brechi 26. Nov 2009 18:47

Re: Methodenaufruf umbiegen?
 
Du brauchs nen codeoverwriting hook, z.b. den von madshi. Der kann ruhig mehr überschreiben und kann im Anschluss trotzdem noch die Funktion ausführen (da die bytes gesichert werden).
Wenn du es dennoch selbst machen willst gibts mehrere möglichkeiten:

1) dubiegst den relativen jump um der dahinspringt
2) du überschreibst (und in deiner hookroutine schreibst die alten bytes zurück -> langsam und net threadsafe)
3) du verwendest Hardwarebreakpoints bzw. einen int 3 hook (also setzt einen int 3 = breakpoint + hooks den exceptionhandler und fängst deine ab) -> ebenfalls net sonderlich schnell

mjustin 26. Nov 2009 18:48

Re: Methodenaufruf umbiegen?
 
Class Helper in Delphi können eine Funktion überdecken (oder hinzufügen), ohne die bestehende Klasse ändern zu müssen:

http://www.delphi-treff.de/sprachen/...helferklassen/

Sie sind damit quasi das GOTO der objektorientierten Programmierung :)

Cheers,

Medium 26. Nov 2009 18:50

Re: Methodenaufruf umbiegen?
 
AHA! Das muss ja nu auch gesagt werden ;) Gekapseltes Verhalten fremder Klassen zu manipulieren fällt definitiv eh schon unter "dirty hack". Was tut deine Unit? Vielleicht fände sich ja generell noch ein Ansatz der mir weniger Kopfweh machen würde :stupid:

Edit: Class-Helper sind auch ein Weg, und werden auch gern zu Decorator-Zwecken genommen. Ich hatte die nur aussen vor gelassen, da ich Gedanklich noch in Delphi 7 stecke, und es sowat da noch nicht gab :angel:

himitsu 26. Nov 2009 18:50

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Neutral General
Es soll aber mit JEDER Klasse funktionieren. Wie soll ich von ner Klasse ableiten, die jemand der meine Unit benutzen wird gerade erst baut?

Indem du ihn zwingst/überredest, eine Schnittstelle zu nutzen/implentieren, welche das gewünschte Verhalten ermöglicht.

Neutral General 26. Nov 2009 18:51

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von mjustin
Class Helper in Delphi können eine Funktion überdecken (oder hinzufügen), ohne die bestehende Klasse ändern zu müssen:

http://www.delphi-treff.de/sprachen/...helferklassen/

Sie sind damit quasi das GOTO der objektorientierten Programmierung :)

Cheers,

x.x

Leute.. Wenn man mein Problem mit OOP lösen könnte, dann hätte ich das schon getan :freak:

Was bringt mir ein Class Helper wenn ich Properties von fremden, mir nicht bekannten Klassen überwachen will? :?

Namenloser 26. Nov 2009 18:51

Re: Methodenaufruf umbiegen?
 
Hat nicht jede jedes Objekt im Speicher eine interene Tabelle, in der Pointer auf die jeweiligen Methoden enthalten sind? Zumindest bei virtuellen bzw. überschriebenen Methoden ist das doch nötig. Eventuell könntest du es darüber irgendwie probieren...

Ansonsten: Könnte man das nicht mit einem Class Helper umsetzen? Ich habe leider keine Erfahrung mit diesen, weil ich keine Delphi-Version besitze, die Class Helper unterstützt, aber so wie ich es verstehe, sind sie ja sozusagen "Patches" für Klassen. Damit wären sie doch eigentlich das, was du brauchst.

Edit: @Beitrag über mir: Natürlich bringen Class Helpers nur etwas, wenn sie die Möglichkeit bieten, Methoden zu überschreiben. Davon bin ich ausgegangen. Ist das nicht der Fall, dann ignoriere meinen Vorschlag einfach.

himitsu 26. Nov 2009 19:04

Re: Methodenaufruf umbiegen?
 
Nein, bei sowas werden die Prozeduren direkt angesprungen.

z.B. bei der WinAPI (DLL-Exports) gibt es sowas
- der Spung zur Tabelle ist hartcodiert und dort ist dann die Tabelle, da die Adressen ja veränderlich sind.

virtuelle und dynamische Methoden stehen auch in Tabellen

Medium 26. Nov 2009 19:16

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Neutral General
Leute.. Wenn man mein Problem mit OOP lösen könnte, dann hätte ich das schon getan :freak:

...weswegen eine Anfrage auch eigentlich immer beinhalten sollte, was man schon so versucht hat. Ist ja nicht so, dass das nicht des öfteren schon in der DP Thema war :cheer:
Und selbst DANN gibt es fast immer noch Wege an die man selbst evtl. dann doch noch nicht gedacht hat - nobody is perfect. Und da uns partout nicht erzählen magst, was du im großen und ganzen vor hast (um evtl. einen anderen Weg zu finden - was ja schon irgendwo in deinem Interesse sein dürfte), klinke ich mich mal aus. Weil dass das so wie von dir erhofft eher nix wird dürfte nun ja klar genug geworden sein - so "tänzelnde" Beiträge der Art "hmmm, was hast'n vor, das geht evtl. anders" deuten da ja eigentlich ganz gut drauf hin.

Namenloser 26. Nov 2009 19:16

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von himitsu
Nein, bei sowas werden die Prozeduren direkt angesprungen.

z.B. bei der WinAPI (DLL-Exports) gibt es sowas
- der Spung zur Tabelle ist hartcodiert und dort ist dann die Tabelle, da die Adressen ja veränderlich sind.

virtuelle und dynamische Methoden stehen auch in Tabellen

Also ich habe mal irgendwo gelesen, dass Delphi es beim Late-Bindig genau so macht. Eigentlich geht es doch auch nicht anders.
Delphi-Quellcode:
type
  TClassA = class(TObject)
  protected
    function GetMesage: string; virtual;
  public
    procedure DoSomething;
  end;

  TClassB = class(TClassA )
  protected
    function GetMesage: string; override;
  end;

implementation

procedure TClassA.DoSomething;
begin
  ShowMessage(GetMessage);
end;

function TClassA.GetMessage: string;
begin
  Result := 'Grüße der Basisklasse';
end;

function TClassB.GetMessage: string;
begin
  Result := 'Hier spricht ClassB';
end;

var
  Something: TClassA;
begin
  Something := TClassA.Create;
  Something.DoSomething; // TClassA.DoSomething wird aufgerufen
  Something.Free;

  Something := TClassB.Create;
  Something.DoSomething; // WIEDER wird TClassA.DoSomething aufgerufen, aber mit anderem Ergebnis
  Something.Free;
end.
Bei beiden Instanzen sollte DoSomething auf die gleiche Adresse zeigen. Trotzdem kommt jeweils ein anderes Ergebnis zurück. Damit das funktioniert, muss irgendwo eine Tabelle existieren, die die Methodenzeiger enthält. Woher sollte die Klasse sonst wissen, welche Methode aufgerufen werden muss? Wenn eine Methode nicht virtuell ist, ist das natürlich etwas anderes, dann kann der Compiler diesen Schritt wegoptimieren.

Edit: Ach verdammt, hab dein Edit übersehen :wall:

himitsu 26. Nov 2009 19:32

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von NamenLozer
Edit: Ach verdammt, hab dein Edit übersehen :wall:

Tschuldschung :oops:

Nja, aber er hat ja anscheinend statische Methoden und die brauchen auch keine Tabellen, da sie sich ja normaler Weise nicht ändern und es somit "sinnlose" Sprünge ergeben würde.

Neutral General 26. Nov 2009 19:36

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von Medium
Zitat:

Zitat von Neutral General
Leute.. Wenn man mein Problem mit OOP lösen könnte, dann hätte ich das schon getan :freak:

...weswegen eine Anfrage auch eigentlich immer beinhalten sollte, was man schon so versucht hat. Ist ja nicht so, dass das nicht des öfteren schon in der DP Thema war :cheer:

Warum sollte ich erwähnen, dass ich es noch nicht mit ner Taschenlampe versucht habe, wenn ich ein Loch graben will?
Mein Vorhaben hat absolut nichts mit OOP zu tun :roll:

Zitat:

Zitat von Medium
Und selbst DANN gibt es fast immer noch Wege an die man selbst evtl. dann doch noch nicht gedacht hat - nobody is perfect. Und da uns partout nicht erzählen magst, was du im großen und ganzen vor hast (um evtl. einen anderen Weg zu finden - was ja schon irgendwo in deinem Interesse sein dürfte), klinke ich mich mal aus. Weil dass das so wie von dir erhofft eher nix wird dürfte nun ja klar genug geworden sein - so "tänzelnde" Beiträge der Art "hmmm, was hast'n vor, das geht evtl. anders" deuten da ja eigentlich ganz gut drauf hin.

Ich habe bereits gesagt was ich vorhabe und außer dir haben mich die meisten auch verstanden glaube ich. Aber ich kann es gerne nochmal erklären.

Angenommen ich baue einen Nachfolger von TEdit, der die Caption eines Buttons anzeigt. Der Programmierer kann dann dem Edit einen Button zuweisen.

Delphi-Quellcode:
TNeutralEdit = class
public
  Button: TButton;
end;
Nun folgender Code:


Delphi-Quellcode:
var Edit: TNeutralEdit;
    Button: TButton;
begin
  Edit.Button := Button;
  Button1.Caption := 'Test123';
  ShowMessage(Edit.Text); // ==> 'Test123'
end;
Dafür muss das Edit aber (automatisch!) mitbekommen, wenn die Caption des Buttons geändert wird.
Und genau DAS ist das Problem.

Dazu allerdings noch eine Anmerkungen:

Das war ein sehr spezielles Beispiel. Ich kann zu diesem Zweck weder Windows Messages abfangen noch einen Class Helper für TButton o.ä. benutzen. Denn der TButton aus dem Beispiel ist durch eine beliebige andere Klasse austauschbar. Die Property, die angezeigt werden soll ebenso.

himitsu 26. Nov 2009 19:39

Re: Methodenaufruf umbiegen?
 
Du kannst ja mal versuchen rauszubekommen, wie z.B. TUpDown das Ändern des {Edit}.Text mitbekommt.

Neutral General 26. Nov 2009 19:43

Re: Methodenaufruf umbiegen?
 
Zitat:

Zitat von himitsu
Du kannst ja mal versuchen rauszubekommen, wie z.B. TUpDown das Ändern des {Edit}.Text mitbekommt.

Gar nicht.. TUpDown ist unabhängig.. Einfach nur 2 Pfeile und einen Value-Wert. TUpDown hat erstmal nichts mit einem TEdit zu tun. Kann man allerdings verbinden wenn man das OnChange des UpDowns (und des Edits) benutzt

brechi 26. Nov 2009 20:06

Re: Methodenaufruf umbiegen?
 
Hast du eigentlich schon meine Lösungen gesehen? Da nicht virtual Methoden keine Adress in irgendeiner LUT haben musst du direkt den Code überschreiben. Anders wist du es nicht hinbekommen.

Medium 26. Nov 2009 20:20

Re: Methodenaufruf umbiegen?
 
Ich kann mich nicht entsinnen, in deinem ersten Beitrag etwas von Properties und deren Binding gelesen zu haben. Da muss ich wohl mal in die Runde fragen, womit ihr so eure Glaskugeln putzt ;) Das einzige wonach du gefragt hast, ist wie man eine Methode an eine vormals anders implemetierte knoten kann, was mit Properties kam dann danach, was ich als Lösungsversuch verstanden habe, bzw. war nicht eindeutig, dass es im Grunde um Properties geht - von Methoden war die Rede, was ja u.U. etwas allgemeiner, im Falle direkter Variablenbindung aber auch ganz anders ausfällt: Stell dir mal vor, es gäbe Properties die keinen Setter haben, sondern direkt in eine private Variable schreiben (oder aus ihr lesen) :shock:.

Eine generelle Lösung scheint es in Delphi nicht zu geben. Sämtliche Ansätze in diese Richtung die mir bisher so begegnet sind, sind Komponentensammlungen die diese Möglichkeit explizit vorsehen, oder es gab ein globales Update-Objekt, dass Komponenten mit Werten versorgt hat die aus einer DB oder sonstwoher stammen (ein OPC-Server ist da mein aktuellster Kandidat). Dir bliebe lediglich zu hoffen, dass es ein (published) OnChange der zu überwachenden Komponente gibt, dem du mit Hilfe der RTTI dynamisch einen Handler zuweisen könntest. Das muss dann zudem natürlich einheitlich ein TNotifyEvent sein. Problematisch ist dabei, dass man u.U. Handler vom anwendenden Programmierer damit abhängt, bzw. umgekehrt. Da ist dann die Frage wer zuerst kommt beim Laden des Formulars - bist du es, hast du schlechte Karten. Ist es die andere Kompo, lässt sich evtl. der zuvor zugewiesene Handler mit einschleifen.
Das OnChange ist dann natürlich allgemein, und du müsstest dir dann noch "von Hand" den betreffenden Wert abholen. Nur muss man dann drauf vertrauen, dass die Kompo auch bei Änderung der gewünschten Property auch wirklich OnChange feuert.

himitsu 26. Nov 2009 20:47

Re: Methodenaufruf umbiegen?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Neutral General
Gar nicht.. TUpDown ist unabhängig.. Einfach nur 2 Pfeile und einen Value-Wert. TUpDown hat erstmal nichts mit einem TEdit zu tun.

Eben nicht.

Die beiden Edit und UpDown sind nur via UpDown.Associate verbunden,
aber egal was man macht (UpDown-Klicken oder in Edit was reinschreiben),
es ändern sich immer Beide.

OK, UpDown weiß beim Klicken über .Associate vom Edit, aber das Edit weiß nichts vom UpDown,
also wie bekommt beim Ändern von Edit.Text das UpDown was davon mit? (.Position ändert sich ja)

Neutral General 26. Nov 2009 21:59

Re: Methodenaufruf umbiegen?
 
Habs den Code von TUpDown kurz überflogen. Sieht danach aus als würde da mit Messages gearbeitet. Auf die muss ich allerdings verzichten. Und OnChange kommt auch nicht in Frage weil die meisten Klassen (!) kein OnChange haben. Die meisten Klassen haben überhaupt keine Events.

@Medium: Ok ich gebe zu mein Ursprungs-Post lässt viel Spielraum für Glaskugeln. Aber schon im 2. Post (der 3. des Threads) habe ich nochmal deutlicher erklärt was ich vorhabe. Und meiner Meinung nach konnte man da schon recht gut rauslesen was ich vorhatte und dass da mit OOP nichts zu machen ist....

Medium 26. Nov 2009 22:40

Re: Methodenaufruf umbiegen?
 
Ich wollte nur noch mal abschließend (aus meiner Sicht, nicht wegen Groll sondern Unmachbarkeit in dieser Form), auf den recht vernichtenden Umstand: "Properties die keinen Setter haben, sondern direkt in eine private Variable schreiben (oder aus ihr lesen)" hinweisen. Dann gibt es nämlich noch nichtmal eine hartgecodete Methodenadresse die man mit Gefrickel bis der Arzt kommt evtl. noch umbiegen könnte, sondern da wird dann einfach direkt irgendwo im Heap gefummelt wie bei "i := 42" (mal angenommen i sei aufm Heap :)).

negaH 27. Nov 2009 00:36

Re: Methodenaufruf umbiegen?
 
Es geht teilweise was du vorhast. Dazu muß die Property

1.) eine Setter Methode haben
2.) das Schreiben in diese Property muß über die RTTI dynamisch erfolgen, zb. beim Laden von Komponenten aus DFMs ist dies immer der Fall.

Schon kompilierter Code wird durch den Kompiler einen statischen Aufruf der Setter Methode benutzen.

Beachte aber das du die Klassen-RTTI im Codesegment manipulieren musst und damit mit einem Hook alle Objekte und deren Nachfahren dieser Klasse beeinflussts.

Eine zweite Alternative gibt es noch. Werden Properties eines Objektes dynamisch gesetzt, zb. beim Laden aus DFMs dann könntest du eine "Spiegelklasse" benutzen. Dazu musst du aber die kompletten privaten Felder und Methoden der zu "hookenden" Klasse kennen. Man deklariert also eine komplett neue Klasse abgeleitet von einem gemeinsammen Vorfahr der Zielklasse, maximal also TObject, die Speichertechnisch identisch ist. Nun reimplemetierst du alle Felder, Properties und virtuellen/dynamischen Methoden deiner Zielklasse in der Spiegelklasse, natürlich mit dem gewünschten veränderten Verhalten.

Zur Laufzeit hat jedes Objekt in seinem "Speicherrecord" als erstes einen Zeiger auf seine VMT, genauer gesagt einen Zeiger mitten in die RTTI seiner Klasse an der die VMT beginnt. Diesen biegst du bei deinen Zielobjekten auf deine Spiegelklasse temporär um. Also solange wie du das geänderte Verhalten wünscht. Alle nicht statischen Zugriffe auf dieses Objekt die über die RTTI gehen werden ab sofort über deine Spiegelklasse "geroutet". Dynamische Zugriffe können auf Properties, Events, virtuelle, dynamische Methoden (und damit auch Message Methoden), Interfaces, TypInfos, Konstruktoren usw. erfolgen. Mit diesem Trick veränderst du zu einem allozierten Objekt zur Laufzeit deren Klassenbeziehung. Damit das funktioniert muß die VMT und DMT beider Klassen pseudoidentisch sein, also gleiche Anzahl an Methoden und auch gleiche Aufrufparameter jeder Methode in gleicher Slot Position.

Nachteil dieser Methode: alle is und as Operatoren funktionieren nicht mehr korrekt. Genauer gesagt: as/is benutzt exakt diesen Zeiger auf die VMT eines Objektes für einen Vergleich beim Typcast bzw. Klassenabfrage. Diese Operatoren vergleichen im Grunde also nur die Codesegement Speicheradresse der RTTI/Klassenstruktur der Objekte.

Gruß Hagen


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:13 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