![]() |
Einführung in Pointer
Hallo,
ich habe zu meiner Frage schon das Forum durchsucht aber wie auch bei google ohne nennenswerten Erfolg. Deswegen hoffe ich, dass ihr mir helfen könnt. Es geht um Pointer und zwar ganz allgemein. Ich kann mit dem Begriff schon etwas anfangen und ich hab auch ne ungefähre Ahnung wozu Pointer nützlich sind. Aber ich bräuchte eine kleine Anleitung wie ich Pointer benutze. Einige Befehle kenne ich schon, ich weiß nur nicht wie ich die anwenden soll und ich bin mir auch nicht sicher ob die so richtig sind. Wenn "wurzel" ein Zeiger ist dann gilt:
Delphi-Quellcode:
Zeiger "erden" oder "ins Nichts Zeigen lassen"
wurzel:=NIL;
Delphi-Quellcode:
Speicherplatz für "wurzel" reservieren.
new(wurzel);
Delphi-Quellcode:
dem Ziel der Addresse "wurzel" wird ein Wert zugeweisen.
wurzel:=123;
Delphi-Quellcode:
gibt den Speicherplatz wieder frei.
dispose(wurzel);
Wenn "wurzel" ein typisierter Zeiger ist, dann wird er folgendermaßen deklariert:
Delphi-Quellcode:
der Abgespeicherte Wert lässt sich auch so darstellen (?)
type PInt=^integer;
var wurzel: PInt;
Delphi-Quellcode:
Stimmt das soweit? Ich kann damit nicht sehr viel anfangen. Wie setze ich diese Befehle ein? Ich habe leider auch kaum Quelltexte gefunden und erst recht keine erläuterten. Ich wäre also sehr dankbar wenn mir jemand eine kleine Einführung in Sachen Pointer geben könnten. Vielleicht auch nen Link wo das anschaulich beschrieben wird.
wurzel^
|
Re: Einführung in Pointer
Wenn der Zeiger "wurzel" auf etwas zeigt...
if (wurzel <> nil) then Zeiger "ins Nichts Zeigen lassen" wurzel := nil; Speicherplatz für "wurzel" reservieren. GetMem(wurzel); dem Ziel der Addresse "wurzel" wird ein Wert zugeweisen. wurzel := ^MeinObjekt; gibt den Speicherplatz wieder frei. FreeMem(wurzel); Wenn "wurzel" ein typisierter Zeiger ist, dann wird er folgendermaßen deklariert: type PInt = ^Integer; var wurzel: PInt; der Abgespeicherte Wert lässt sich auch so darstellen wurzel^ |
Re: Einführung in Pointer
Schau mal beim Luckie nach.
![]() dort gibt es viele Programme (natürlich mit Quellcode) Und viele Tuturials mit und sogar einige speziel über Pointer. |
Re: Einführung in Pointer
@ste_ett: Anstelle von GetMem und FreeMem ist New und Dispose zu bevorzugen (steht auch in der Delphihilfe wenn du diese mal zu GetMem aufrufst)
|
Re: Einführung in Pointer
hmmm?
"GetMem und FreeMem" Ja, da steht was in der OH, aber ich verwende auch noch diese ... hab irgendwie nicht rausbekommen, wie man bei New die Speichergröße angibt, welche man haben möchte. Außerdem arbeitet Delphi selber auch damit und nicht mit new/dispose ... jedenfalls kommt getmem/freemem ziehmlich oft iim Source der Delphi-Komponenten vor :shock: |
Re: Einführung in Pointer
Zitat:
|
Re: Einführung in Pointer
Ok, die Befehle sind mir dann also klar. Aber wenn ich damit etwas sinnvolles machen will, zum Beispiel eine Kette, dann hab ich schon wieder Schwierigkeiten. Dann sieht das zum Beispiel so aus:
Delphi-Quellcode:
Bis hierhin hätte man also einen Zeiger "lauf" mit "lauf^"=1, einen Zeiger "wurzel", der ins Leere zeigt und einen Zeiger "lauf^.rechts", der von "lauf^" aus ins Leere zeigt.
type Zeiger=^Element
Element:record rechst:zeiger; inhalt:integer; end; var wurzel, lauf: zeiger; begin wurzel:=NIL; new(lauf); lauf^.rechts:=NIL; //"lauf^.rechts" ist also ein Zeiger, der vom Ziel von Zeiger "lauf" ausgeht und erstmal ins Leere zeigt?// lauf^.inhalt:=1; //ist "lauf^.inhalt" das selbe wie "lauf^"?// Nun gehts weiter:
Delphi-Quellcode:
der Zeiger "wurzel" zeigt jetzt auf einen Speicherplatz in dem der selbe Wert wie "lauf^" (1) gespeichert ist. der Zeiger "lauf" bekommt einen neuen Speicherplatz reserviert. der Zeiger "lauf^.rechts", der vom Ziel von "lauf" ausgeht zeigt jetzt auf den selben Speicherplatz wie "wurzel". "lauf^.inhalt" wird überschrieben. Der Inhalt ist nun ein anderer, "lauf" stellt aber immer noch die selbe Addresse dar.
wurzel:=lauf^;
new(lauf); lauf^.rechts:=wurzel; lauf^.inhalt:=2; wurzel:=lauf; Wenn das so alles richtig war, dann hätte ich noch ne Frage. Wollte man die Kette um eine 3 erweitern, müsste man dann mit einem "lauf^.rechts^.rechts"-Zeiger arbeiten? gibt es so einen Zeiger überhaupt? Danke für den link, ich denke der hilft mir auch weiter. Ich denke für mich ist das noch nicht so relevant ob ich new und dispose oder Getmem und freemem benutze. So komplex sind meine Programme nicht gerade *g*... |
Re: Einführung in Pointer
@El Cattivo
OK, das mag ja nicht schlecht sein, aber was macht dann New bei dem Typen Pointer und PChar, die haben ja keine feste Größe und für sowas brauchte ich diese Funktionen ja :gruebel: |
Re: Einführung in Pointer
für PChars gibt es "StrAlloc"
|
Re: Einführung in Pointer
Also das mit dem new hab ich so verstanden.
Wenn p nen Pointer ist und n ne gespeicherte variable, dann weist man mit p:=@n p die Addresse von n zu. Wenn p noch auf keinen Wert zeigt, man aber bereits auf den Raum zugreifen will, auf den der Pointer zeigt, dann schreibt man new(p). Ich schätze mal, wenn p:=NIL wäre, dann könnte man nicht p^.rechts schreiben. Aber eigentlich wollte ich hier die Fragen stellen :lol:, ich weiß ja nichtmal was ein PChar ist und mit " für PChars gibt es "StrAlloc"" kann ich erst recht nichts anfangen... :shock: Ich wollte eigentlich nur wissen wie man Pointer anwendet, dass man ne Kette erstellt, in die man Elemente einfügen und auslesen kann und die man sortieren kann. Die Kette unten hat aber erst zwei Eltemente (1 und 2) und ich hab keine Ahnung wie man die erweitet und ich habe nur ne leichte Ahnung wie man die sortieren könnte... |
Re: Einführung in Pointer
Zitat:
|
Re: Einführung in Pointer
Ja, ok... Aber darum gings mit jetzt eigentlich nicht haupsächlich.
|
Re: Einführung in Pointer
was genau hast du denn jetzt noch nicht verstanden? also welchen Operator(@ ^ ...) oder welche funktion (new, dispose...)
|
Re: Einführung in Pointer
Ich glaube er versteht nicht, wo und wann man pointer (verkettete listen hier ausgenommen) benutzen sollte bzw wann das von Vorteil ist. Ich verstehs naemlich auch nicht, ich bin immer ohne ausgekommen.
|
Re: Einführung in Pointer
Mal als Beispiel:
Du willst Kundendaten speichern, und weißt nicht genau wieviele das werden. Ein super Fall um Listen zu erklären ;) Als erstes mal muss man den Typ deklarieren:
Delphi-Quellcode:
Als nächstes braucht man den Anker/Wurzel.
PMyDaten=^TMyDaten;
TMyDaten=record name:string; alter:integer; . . . next:PMyDaten; end; Dieser wird beim Start des Progs einmal erstellt und danach NIE mehr angerühert, ausser beim beenden!
Delphi-Quellcode:
Damit haste alles schonmal gut vorbereitet um die Liste nun zu füllen...
var MyDaten:PMyDaten;
new(MyDaten); MyDaten.next:=nil; Wir stellen uns einfach mal vor wir haben ein Formular, wo die Daten in edits und so richtig eingegeben wurden. Dann ist unten noch nen Knopf "Daten hinzufügen"
Delphi-Quellcode:
procedure Datenhinzufügen;
var help:PMyDaten // den Pointer nehmen wir um uns durch die Liste zu hangeln begin help:=MyDaten; // jetzt zeigen help und mydaten auf den selben Speicherbereich. while help.next<>nil do //um zum letzten Element der Liste zu kommen gehen wir solange eins weiter bis das nachfolgende NIL ist. help:=help.next // Jetzt haben wir mit help also einen Pointer der auf den Speicherpplatz des letzten Elements der Liste zeigt. // Jetzt können wir das neue Element anhängen new(help.next) // New stellt Speicherbereich für ein neues Element her und help.next zeigt automatisch auf dieses. // Jetzt müssen wir noch auf das neue Element draufzeigen, da wir ja noch beim vorletzten hängen help:=help.next; // Na jetzt kommt nur noch Schreibarbeit: Werte eintragen und next auf nil setzten help.Name:=Ich; help.... help.next:=nil; end; // Das wars dann auch schon! |
Re: Einführung in Pointer
Liste der Anhänge anzeigen (Anzahl: 1)
Beispiel für Pointer.
Du hast eine Musiksoftware und möchtest natürlich zu jedem Lied das ID3-Tag anzeigen. Jetzt wäre es speicherverschwendung wenn du 10 Listen hast in denen jeweils ein paar Lieder gleich sind das du in all diesen Listen das ID3Tag speicherst. Lösung ist das man eine Große Liste im Speicher hat wo alle Informationen zu der Datei gespeichert werden und die Playlisten sind dann nur noch Listen mit Pointer welche auf die Informationen zeigen (Bild zu diesem beispiel im Anhang, die Zahlen 1 bis 14 stellen dabei die Pointer(adressen) dar) |
Re: Einführung in Pointer
Ok, danke! Ich habt mir sehr geholfen. Mein Denkfehler war glaub ich, dass ich dachte jeder gespeicherte Wert bräuchte einen eigenen Pointer um die einzelenen Werte aufrufen zu können. Aber man lässt dann wohl in dem Bsp. von atreju2oo0 die zwei Zeiger durchhangeln und an der Stelle stoppen wo der Wert auf den gezeigt wird ein bestimmter ist. Ich dachte durch den Befehl "help:=help.next;" würde die ehemals in help gespeicherte Information zur Speicherleiche werden. Das erstellen einer solchen Kette könnte man ja so darstellen:
![]() Hier beim zweiten Element sind noch für alle Elemente Adressen vorhanden. Aber MyDaten zeigt doch immer auf das erste Element und help und help^.next gehen immer weiter. Wie kann man dann (wenn man jetzt nur die Namen der Kunden speichert) bei dem fünfen, oder sechsten Element angekommen, wieder auf den ersten help^.name zugreifen? einfach durch
Delphi-Quellcode:
Ist dann das erste help das gleiche wie beim erstellen der Kette? Wenn das so wäre, dann hätte ichs endlich verstanden :-D
help:=MyDaten;
while help.next<>help^.name do help:=help.next Und das Pointer Speicherplatz spaaren hab ich jetzt auch begriffen :wink: . |
Re: Einführung in Pointer
*räusper*
da ich auf luckies seite nichts zu pointern gefunden habe, frag ich hier:
Delphi-Quellcode:
wieso wirft mir das ne AV??
//globale vars
var currentbox:^TListBox; procedure TForm1.LBbeforeMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin currentbox:=@sender; end; procedure TForm1.deleteClick(Sender: TObject); begin currentbox^.DeleteSelected; end; |
Re: Einführung in Pointer
Weil du "currentbox" keinen Speicher zugewiesen hast (New())
|
Re: Einführung in Pointer
oh * mein * gott * so * ein * dummer * anfängerfehler *
(sich bei jedem stern das geräusch vorstellen, das ein auf eine hölzerne tischplatte aufprallender kopf erzeugt) EDIT: es geht immer noch nicht.... ich rufe new(currentbox) im formcreate und im mousemove( wo der pointer ne adresse bekommt, siehe oben) auf. :pale: EDIT2: prinzipiell müsste das aber funktinieren, oder? ich missverstehe da nicht die funktion von pointern?! |
Re: Einführung in Pointer
Zeig mal den code her, bei mir gibts kein Fehler. Btw das "@" kannst du weglassen, klassen werden immer als referenz übergeben
|
Re: Einführung in Pointer
Mal eine Frage:
Seid ihr Geschichtsforscher? Oder programmiert ihr mit Turbo Pascal 5.0? Zeiger braucht man heutzutage in Delphi so gut wie gar nicht mehr. Records auch nicht. Das wurde alles extrem vereinfacht und hier scheint es niemand mitbekommen zu haben. Benutzt einfach Klassen und macht euch über Pointer keine Gedanken. Natürlich ist es hilfreich zu wissen, wie das funktioniert, aber wie gesagt: Borland hat da in den letzten Generationen viel vereinfacht. Ihr benutzt ja auch schon die IDE und nicht den vi, oder? Und die Icons unter Windows und nicht die Kommandozeile, oder? Will heissen: Manchmal sind Kommandozeile, vi und Pointer sehr mächtig, aber in der Regel kommt man auch ohne aus. Und fährt besser damit (die Programme werden lesbarer und die eigene Fehlerquote verringert sich). |
Re: Einführung in Pointer
Und gleich im Anschluß eine Antwort hierauf:
Zitat:
Wie gesagt: Borland hat das alles vereinfacht (schon vor Jahren) und seitdem geht's so:
Delphi-Quellcode:
//globale vars
var currentbox: TListBox; procedure TForm1.LBbeforeMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin currentbox := sender; end; procedure TForm1.deleteClick(Sender: TObject); begin currentbox.DeleteSelected; end; |
Re: Einführung in Pointer
Das weiss er selbst. Er möchte es aber mit Pointern versuchen um Selbige zu kapieren. Zum üben ist das ganz gut. Ich denke kaum das er das in _diesem_ Fall wirklich so am Ende macht.
|
Re: Einführung in Pointer
doch ich hatte genau das so vor.
ich habe nämlich drei listboxen, ein popup und ein deletclick. -> nur eine zeile code für drei sachen. ist doch schön, oder?(eingentlich sinds ja vier zeilen.... :D) und warum macht borland solche verienfachungen? damit ein armer noob in AVs ertrinkt.... EDIT: wieso erzählt ihr mir hier jetzt schwachsinn? die version von bttb930 geht nämlich kein stück..... hab jetzt nämlich seinen code, und currentbox und sender sind absoult inkmpatibel. dacht ich mirs doch....([Fehler] Unit1.pas(59): E2010 Inkompatible Typen: 'TListBox' und 'TObject') ... werd ichs wohl doch selber rausfinden müssen. also das enttäuscht mich jetzt wirklich.... |
Re: Einführung in Pointer
naja, getestet hab ich den code nicht, aber dann schreib einfach
Delphi-Quellcode:
if Sender is TListBox then
CurrentBox := Sender as TListBox; |
Re: Einführung in Pointer
Liste der Anhänge anzeigen (Anzahl: 1)
dir ist aber schon klar, dass ich nen pointer habe, also mit den "echten" listboxen auf meinem formular arbeiten will udn nicht mit der kopie davon, die dann in meinem currentbox gespeichert ist?
ich poste mal das projekt hier(nur source; sollte aber mit D2005 sofort kompilieren; selbsterklärend; kann man mit D2005 abwärtskompatibel projekte speichern?) |
Re: Einführung in Pointer
Ja, Du hast dann einen Pointer und keine Kopie.
Übrigens ist dies ein gutes Beispiel dafür, dass durch die Änderung von Borland Fehler schon beim Compilieren gefunden werden. Denn den Zeiger hättest Du einfach zuweisen können, ohne Typprüfung, d.h. Du hättest auch den Zeiger der eigentlich auf eine TListBox zeigen soll auf einen TButton zeigen lassen können - und dann krachts. So wie ich's gemacht hab geht das aber nicht - da meckert der Compiler spätestens bei der Zuweisung. Wichtig ist natürlich das "if Sender is TListBox" vor dem "... := Sender as TListBox", denn ohne diese Prüfung kracht's doch wenn Sender mal keine TListBox ist. |
Re: Einführung in Pointer
Zitat:
könntest du dann bitte den source oben zum laufen bringen? wäre dir sehr dankbar. ps: im moment ist es nur so implementiert, dass es nur für eine listbox funktioniert. ich bin kein noob, und kriege das dann schon hin, auf auf die anderen listboxen das onclick zu setzen.... EDIT: es funktioniert wirklich...... omg sry dass ich so misstrauisch war. aber das ist doch nicht normal dass eine ganz normale globale variable einfach so zum pointer wird.... |
Re: Einführung in Pointer
Zitat:
|
Re: Einführung in Pointer
Zitat:
Aber wenn ich nochmal auf das Beispiel von atreju2oo0 zurückkommen darf... wäre das dann "fifo" (first in first out)? Ist der Quelltext mit "lifo" (last in first out) nicht kürzer? Das Problem ist, ich weiß selbst nicht genau wie man fifo und lifo unterscheidet. Ich weiß nur, beim auslesen ließt man bei lifo rückwärts aus und bei fifo vorwärts, weil man beim einlesen bei fifo hinten dran hängt und bei lifo eben vorne. |
Re: Einführung in Pointer
...ok
Ich hab noch eine letzte Frage. Ich habe eine Kette von Zahlen erstellt. Die sieht so aus: das sind globale deklarationen:
Delphi-Quellcode:
Nun kommt das Program an sich:
type
Zeiger=^Element; Element= record rechts: Zeiger; inhalt: integer; end; var Form1: TForm1; a: integer; wurzel,lauf,help: zeiger;
Delphi-Quellcode:
Und die Ausgabe in ein label:
procedure TForm1.FormCreate(Sender: TObject);
begin a:=1; wurzel:=NIL; end; procedure TForm1.Button1Click(Sender: TObject); var b: integer; begin b:=strtoint(edit1.text); if a=1 then begin new(lauf); lauf^.rechts:=NIL; lauf^.inhalt:=b; wurzel:=lauf; a:=2; end else begin while lauf^.rechts<>NIL do lauf:=lauf^.rechts; new(lauf^.rechts); lauf:=lauf^.rechts; lauf^.inhalt:=b; lauf^.rechts:=NIL; end; edit1.text:=''; end;
Delphi-Quellcode:
Für das sortieren habe ich folgenden Ansatz:
procedure TForm1.Button2Click(Sender: TObject);
begin lauf:=wurzel; label1.Caption:=inttostr(lauf^.inhalt) + ','; while lauf^.rechts<>NIL do begin lauf:=lauf^.rechts; label1.Caption:=label1.caption + inttostr(lauf^.inhalt) + ','; end; end;
Delphi-Quellcode:
Achso ja. Und meine Frage ist, wie ich diese Pointerkette sortiert bekomme. Wohlgemerkt ohne die gespeicherten Elemente zu ändern sondern nur durch "Verbiegen" der Pointer..?
procedure TForm1.Button3Click(Sender: TObject);
begin lauf:=wurzel; help:=lauf^.rechts; while lauf.rechts.rechts<>NIL do if lauf^.rechts^.inhalt<help^.rechts^.inhalt then begin lauf^.rechts:=help^.rechts; lauf^.rechts^.rechts^.rechts:=help; lauf^.rechts^.rechts:=lauf^.rechts^.rechts^.rechts; lauf^.rechts^.rechts^.rechts:=help^.rechts^.rechts; end else begin lauf:=help; help:=lauf^.rechts; end; label2.Caption:=label1.caption + inttostr(lauf^.inhalt) + ','; end; |
Re: Einführung in Pointer
...Oder vielleicht kann mir wenigstens jemand sagen wie man ein Glied einer Pointerkette löscht... :duck:
Ich kann Pointer ja auch nicht mehr sehen. Ich habe auch nen Ansatz, der funktioniert aber nicht.
Delphi-Quellcode:
r:=strtoint(edit5.text);
lauf:=wurzel; for n:=1 to r-1 do lauf:=lauf.rechts; help:=lauf.rechts; dispose(lauf); lauf:=help; |
Re: Einführung in Pointer
Wenn ich den Thread schon früher gesehen hätte, hätte ich schon früher auf mein Tutorial verweisen - naja, dann hol ich das halt mal nach.. ;)
Auf ![]() Ich denke da findest du auch fast alle Fragen eine Antwort..! :) |
Re: Einführung in Pointer
Vielen Dank! Das Tutorial ist mir wirklich eine große Hilfe! :thumb: Super!
|
Re: Einführung in Pointer
Ich sag nur
Pointerfun with BINKY ![]() ![]() Viel Spass damit Greetz Boombuler |
Re: Einführung in Pointer
Muss das Thema nochmal aufkochen :wink:
warum gibt folgendes eine AV?
Delphi-Quellcode:
Gruß Richard
type
PBitmap = ^TBitmap; var Bitmap: PBitmap; {...} new(PBitmap); Bitmap := @image1.picture.bitmap; Bitmap^.Canvas.Brush.Color := clred; Bitmap^.Canvas.Rectangle(0,0,10,10); // Hier krachts! |
Re: Einführung in Pointer
TBitmap ist intern ein Zeiger auf das Objekt. Durch deinen PBitmap Typ hast du dann einen Zeiger auf den Zeiger auf das Objekt.
Aber wie gesagt (wenn du den Thread gelesen hättest), wird auf Seite 2 schon erklärt, dass Instanzen intern Zeiger sind und du somit keinen solchen Zeigertyp brauchst. Eine Variable vom Typ TBitmap ist ein Zeiger. Beispiel:
Delphi-Quellcode:
Schmeiss alles mit deinem PBitmap raus und nutze statt dessen einfach nur TBitmap.
ShowMessage(IntToStr(SizeOf(TBitmap)) + ' bytes gross');
Dein Code wie es reichen würde:
Delphi-Quellcode:
Dein Code korrigiert:
var
Bitmap: TBitmap; {...} Bitmap := image1.picture.bitmap; Bitmap.Canvas.Brush.Color := clred; Bitmap.Canvas.Rectangle(0,0,10,10); // Hier krachts!
Delphi-Quellcode:
Dein Problem im Code ist folgender: Du reservierst Speicher für den Typen PBitmap. Danach zeigt PBitmap auf den reservierten Speicherbereich. Danach setzt du den Zeiger aber um und lässt ihn auf die Property von TPicture zeigen. Somit nutzt den alloziierten Speicher nicht und zum anderen zeigst du auf eine Property und kein richtiges Objekt. Bei der Dereferenzierung versucht er dann die Property als TBitmap zu benutzen. Dadurch wird der Getter der Property nicht ausgelöst und es gibt an der Stelle kein TBitmap -> AV
type
PBitmap = ^TBitmap; var Bitmap: PBitmap; {...} new(PBitmap); Bitmap^ := image1.picture.bitmap; Bitmap^.Canvas.Brush.Color := clred; Bitmap^.Canvas.Rectangle(0,0,10,10); // Hier krachts! |
Re: Einführung in Pointer
Okay danke! Ich hatte den obrigen Thread gelesen, hatte es aber nicht richtig verstanden... Jedenfalls funktioniert es jetzt!
Aber das ist nicht bei einem Record so oder? Wenn ich ein
Delphi-Quellcode:
habe, dann kann ich mit meinem Zeiger auf ein Record den Aufruf
TMyArray = Array[0..19,0..19] of TMyRecord
Delphi-Quellcode:
machen und dann über
MyZeiger := @MyArray[x,y]
Delphi-Quellcode:
darauf zugreifen und die entsprechenden Werte modifizieren oder?
MyZeiger^.ValueX
Edit: Juhu 100 Beiträge |
Re: Einführung in Pointer
Ja, kannst du. In dem Falle erstellst du direkt einen Zeiger auf das Element x des Arrays - das funktioniert entsprechend.
Aber: Ein Hinweis gleich dazu: Ein dynamisches Arrays ist intern ein Zeiger auf die Elemente des Arrays. Also auch hier aufpassen - ABER: ein Zeiger auf ein Element (wie dein Beispiel eben mit einem statischen Array) des Arrays funktioniert entsprechend dem statischen Array. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:32 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-2025 by Thomas Breitkreuz