Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Tastaturhook -> Tasten manupulieren (https://www.delphipraxis.net/4126-tastaturhook-tasten-manupulieren.html)

webby 16. Apr 2003 10:29


Tastaturhook -> Tasten manupulieren
 
Hallo,
vielleicht sollte ich euch erst einmal meine Problemstellung beschreiben:
Da ich ja in Wettberwerben in Tastschreiben (= Tastaturschreiben) teilnehme, bin ich natürlich sehr interessiert in Möglichkeiten wie ich schneller werde.
Ein paar gewürfelte Menschen haben sich schon ausgedacht, in Word die Autokorrektur-Funktion zu benutzen, damit sie häufige Wörter abkürzen können. Ein paar Beispiele:
der -> r
die -> i
das -> s
werden -> wn
unseren -> usn
Es sieht zwar irgenwie aus als würde es nicht viel bringen, doch wenn man Texte genauer ansieht, wird man feststellen, dass viele solche Wörter vorhanden sind und man so in der Minute 50-100 Anschläge mehr bekommen kann.
Nun nochmal zurück, natürlich gibt man sich als Programmierer nicht mit dieser simplen Word-Funktion zufrieden. :) Und es hat natürlich noch einen anderen Grund: Ich kann die Abkürzungen nur in Word benutzen, will ich aber beim Chatten oder bei Fragen in die Delphi-Praxis schreiben muss ich wieder alles ausschreiben. :)

Ich dachte jetzt daran, dass ich vielleicht einen Tataturhook dafür benutzen sollte, aber wie manipulier ich dann die Tasten. Das blöde ist halt z. B. bei "werden" was ja mit "wn" abgekürzt wird, da müsste ich erst wieder das "n" löschen...

Könnt ihr mir helfen?

dadu 1. Mai 2003 18:54

Liste der Anhänge anzeigen (Anzahl: 1)
Ich hoffe das hilft dir, wenn nicht sag bescheid!

webby 1. Mai 2003 19:03

Erst mal Danke für den Quellcode!

Leider hat er aber einen kleinen Bug:
Er ersetzt auch, wenn ich nur "n" eintippe... :-/

Aber die Idee an sich ist nicht schlecht!
Ich glaub ich kann den jetzt auch selber ausbauen!
Thx

S - tefano 18. Jun 2003 19:34

@dadu:
Gibts vielleicht irgendeine Systemmessage auf die man reagieren kann, um die Tastendrücke zu lesen?
Mit nem Timer is das ziemlich ungenau, weil man ja nich in gleichen Intervallen Tasten drückt...

webby 18. Jun 2003 19:49

Naja, ich denke mal man kommt nicht drum rum für jede Taste einen Hook zu schreiben, oder?
Ich selber hab mein kleines Projekt eingestellt, weil ich jetzt eh mehr oder weniger komplett auf Linux umgestiegen bin... :)

S - tefano 18. Jun 2003 20:27

Diesen Code verwendet dadu um rauszufinden, ob und wenn ja welche Taste (in diesem Code erstmal nur Buchstaben) gedrückt wurde:

Delphi-Quellcode:
  //65 - 96 = Chars A- '
  for i := 65 to 96 do
    //Wenn eine Taste gedrückt wird
    if (GetasyncKeyState(word(i)) <> 0) then
      str := str + char(i);
Das Ganze hat er in einen Timer gesetzt, damit man alles mitbekommt.
Gibts halt nur das Problem, dass man wenn man den Timer z.B. auf 10ms stellt, das Programm dann glaubt eine Taste (die man halt nur etwas länger gedrückt hat, aber trotzdem nur einen Buchstaben geschrieben hat) wäre häufiger als 1 Mal gedrückt worden.
Wenn man den Timer dann aber zu langsam einstellt (z.B. 100ms), werden manche Tasten nicht empfangen weil sie außerhalb des Intervalls gedrückt werden.
Da man ja nicht immer in selben Abständen beim Tippen Tasten betätigt, ist es mit einem Timer nicht sicher möglich, jeden Tastendruck zu erwischen.
Und deshalb frag ich mich halt, obs irgendeine Art systemweite Message gibt, die bei einem Tastendruck ausgelöst wird.
Baut man dann oben Code in eine Ereignisbehandlung für so eine Message ein, hat man wohl eine bessere Trefferquote.
Son Programm würd mich nämlich auch mal interessieren, aber wenn das Programm manchmal meine Abkürzungen nich erwischt, dann bringts mir ja auch nix...

Bis dann,

S - tefano

webby 19. Jun 2003 08:13

Ich hab dein Problem verstanden, das hatte ich ja auch...
Naja, aber man braucht wahrscheinlich doch nicht, so wie ich es oben geschrieben habe, für jede Taste einen einzelnen Hook. Ein kompletter Tastaturhook müsste nämlich seine Dienste erfüllen...
Hier im Forum müsste aber schon stehen, wie man einen solchen Tastaturhook bastelt...

S - tefano 19. Jun 2003 09:54

Jo, habs gefunden.
Muss es mir mal genauer ansehen.

Minz 19. Jun 2003 12:24

verstehe dat Problem evtl nicht ABER:

Dass Ihr eine Nachricht bekommt, wenn eine Tate gedrückt wird
setze ich jetzt mal voraus.

Außerdem setze ich voraus, dass Ihr wisst um welche Taste es
sich handelt.

Wo ist dann dat Problem die Tasten in einem String zwischen-
zuspeichern und bei jedem neuen Empfang den String auf eine
Abkürzung zu überprüfen? :freak:

Da brauch man dann auch nix mit Timern und so...
Achso...der String muss natürlich dann gelöscht werden,
wenn eine Textausgabe erfolgte, die endgültig ist, also
eine Abkürzung in einen Text umgewandelt wurde, oder halt
ein Buchstabe als solcher feststeht.

Gruß Minz

S - tefano 19. Jun 2003 12:39

Das Problem liegt nicht da wo dus beschrieben hast, sondern ganz an der "Wurzel", die du auch in deinem Post erwähnt hast.
Das Problem ist halt eine Message zu bekommen wann eine Taste gedrückt worden ist, man will ja darauf reagieren.
Aber das geht halt mit Hooks. Also ist das Problem fast gelöst. Nur, dass es so wie in Assarbads Tutorial beschrieben unter NT- Systemen nich geht.
Zumindest bei mir nich, weil in meiner Windows- Unit die entsprechende Hook- Definition fehlt.

S - tefano 19. Jun 2003 12:42

Achja, wo wir dabei gerade sind.
Könnte mal bitte einer von euch in seiner Windows- Unit die Definition von WH_KEYBOARD_LL nachschlagen und posten?
Wie gesagt, bei mir fehlt sie.
Müsste wie folgt aussehen:

Delphi-Quellcode:
  {$EXTERNALSYM WH_KEYBOARD}
  WH_KEYBOARD = 2;
Nur halt mir KeyBoard_ll anstatt KeyBoard.
Hoffe mal dass es überhaupt geht, einfach die Definition nachzutragen. Aber nen Versuch ist es wert, oder?
Oder sollte man das lieber lassen und nicht riskieren seine WindowsUnit zu zerschießen?

Danke schonmal,

S - tefano

Christian Seehase 19. Jun 2003 12:50

Moin Stefano,

Du musst dafür ja nicht Deine Windows.pas anpassen, es genügt ja schon, wenn Du Dir eine Unit erstellst, in der Du dann solche Ergänzungen einbindest.

Das ExternalSym brauchst Du auch nur, wenn die Unit im C++ Builder benutzt werden soll, ansonsten reicht einfach eine Konstantendeklaration.

Delphi-Quellcode:
const // aus WINUSER.H
  WH_KEYBOARD_LL = 13;
diese Konstante funktioniert aber nur unter NT, W2K und XP.

S - tefano 19. Jun 2003 13:21

Hi,

danke erstmal für die Definition, habs dann mal in die ProgrammUnit eingebunden.
Aber irgendwie gehts immernoch nicht...

Diese Funktion habe ich aus einem anderen Tutorial (irgendwo bei Google gefunden) rausgenommen und so modifiziert, dass ich erstmal zum Gucken was ich da empfange, bevor ich mich an die "Abkürzungsverlängerung" mache, die gedrückten Tasten in eine Stringlist schreibe und später auf Buttonklick in ein Memo übertrage:

Delphi-Quellcode:
function hookproc(ncode: integer; wpara: wparam; lpara: lparam): longint; stdcall;
begin
  Result := 0;
  if ncode=Hc_action then
  begin
    if (Peventmsg(Lpara).message = wm_keydown) or (Peventmsg(Lpara).message = wm_syskeydown) then
    begin
      key:= Peventmsg(Lpara).paraml;
      key:= Peventmsg(lpara).paramH;
      key:= mapvirtualkey(key,0);
      universalkey(key); //gibt String für den jeweiligen key-Integer in keystr aus
      //den keystring in die Stringlist schreiben
      if keystr = '[ENTER]' then list.Add(keystr) //wenn Enter gedrückt wurde, neue Zeile anfangen,
      else list[list.Count - 1]:= list[list.count - 1] + keystr; //sonst einfach anhängen
    end;
    if peventmsg(lpara).message = wm_keyup then
    begin
      key:= Peventmsg(Lpara).paraml;
      key:= mapvirtualkey(key,0);
    end;
  end;
end;
Key ist ein globaler Integer, list ist meine Stringlist die ich zuvor initialisiert habe.
Was ich an dieser Funktion schon die ganze Zeit fragwürdig finde ist, warum key drei mal hintereinander verschiedene Werte zugewiesen werden.

Als ich den Hook noch mit WH_KEYBOARD gesetzt hab, war es noch so, dass das Programm durch die zweite If-Abfrage durchkam (die wo geguckt wird, ob eine Taste oder eine Systemtaste gedrückt wurde) durchkam, dann aber die Funktion mapvirtualkey eine 0 zurückgab (weil das, was key vorher zugewiesen wurde, fast immer größer als 1000 war, aber auf jeden Fall immer größer als die ASCII- Range). Deswegen wurde auch jede Taste als Pause erkannt, weil universalkey der 0 die Taste Pause zuweist.
Mit WH_KEYBOARD_LL geht das Programm aber komplett an der zweiten if- Abfrage vorbei, es wird also durch den Tastendruck über den Hook zwar die Funktion ausgeführt, aber der Tastendruck wird dann als solcher nicht mehr erkannt.

So setze ich den Hook:

Delphi-Quellcode:
  setwindowshookex(WH_KEYBOARD_LL, hookproc, Hinstance, 0);
Ist irgendwo irgendwas falsch? Oder gibts in der hookproc auch noch Teile, die so nur unter 9x funktionieren können?
Hoffe ihr könnt was finden.

Bis dann,

S - tefano

S - tefano 19. Jun 2003 18:13

Kann doch nich sein, dass das keiner weiß...

Christian Seehase 19. Jun 2003 19:51

Moin Stefano,

das Problem dürfte schlicht sein, dass wParam und lParam bei WH_KEYBOARD_LL ganz andere Daten enthalten als bei WH_KEYBOARD.
So wie die Hookproc aussieht könnte das zwar berücksichtigt sein, aber wie ist z.B. PEventMsg deklariert?

S - tefano 19. Jun 2003 21:42

Hi,

is das jetz ne direkte Frage an mich, oder eher ne r(h)et(h)orische Frage?
PEventMsg steht in meiner Windows- Unit als Pointer auf TEventMsg, das wiederum der Datenstruktur der EvenMsg, die im Windows SDK nachzulesen ist entspricht.
Und diese Strutkur hat die Eigenschaften lParam, hParam und message (wenn ich mich nich vertue).
Ich hab mir jetzt mal zum Probieren nen Breakpoint gesetzt um mal zu gucken was da während der Hook sich meldet in der Funktion so passiert.
Und irgendwie sind jetzt die Messages, die in der If- Abfrage geprüft werden weder wm_keydown noch wm_syskeydown. Sind wieder irgendwelche Zahlen, meistens zwischen 9000 und 10000, und wenn ich die Überprüfung weglasse wird jede Taste als "Pfeilnachoben" erkannt.
Kann denn die Datenstruktur dieses lParams oder von was auch immer denn unter NT- Systemen wirklich so anders sein?
Und wenn ja... warum wird sowas nirgendwo (weder in (zumindest meiner) Delphi Hilfe, noch im Netz (jedenfalls auf den 23 Schrott-Seiten die Google mir heute reingewürgt hat)) anständig dokumentiert?
grrrr. So langsam bin ich am Verzweifeln. Hätte so tolle Sachen heute machen können, aber nein, ich Schlaubi versuche ausgerechnet heute meinen Einstieg in diese verfluchten Hooks. :evil: :cry: :evil: :cry: :evil: :cry: :evil: :cry: :evil:

Hoffe von Herzen, dass einem von euch was einfällt, was mich weiterbringt. Denn nachdem ich heute durch das Niemandsland der Prono- zugemüllten Seiten, vermient mit Dialern und furchtbaren Designs und noch schlimmeren Usern, die absolut nicht in der Lage sind sich verständlich auszudrücken bzw. auf Fragen zu reagieren, bin ich dieses Mal zu dem absolut ernsthaften Schluss gekommen, dass AUQ und die Praxis die absolut unangefochten besten Delphi- Communities in deutscher Sprache sind.
Ich hoffe, dass (wenn meine oben erwähnte Verzweiflung das nicht schon bewirkt hat) spätestens diese Liebeserklärung an dieses Forum einen Ansporn bietet, einem armen unschuldigen Kerl der im Glauben ist einen ganzen Tag verschwendet zu haben, zu helfen.

Danke im Voraus,

S - tefano

Christian Seehase 19. Jun 2003 22:04

Moin Stefano,

also in meinem PSDK heist die Struktur für WH_KEYBOARD_LL aber KBDLLHOOKSTRUCT

OK, da die Länge der Struktur, und die Aufteilung gleich sind, dürfte das nichts ausmachen.

Sollte dann so deklariert werden:
( mit // das entsprechende Feld von TEventMsg)

Delphi-Quellcode:
type
  PKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;

  KBDLLHOOKSTRUCT = packed record
    vkCode     : DWORD; // message
    scanCode   : DWORD; // ParamL
    flags      : DWORD; // ParamH
    time       : DWORD; // time
    dwExtraInfo : DWORD; // hwnd
  end;
Vergleich doch mal, ob auch alles überall dort steht, wo's soll.

ggf. könntest Du ja vielleicht mal die Sourcen anhängen.

S - tefano 20. Jun 2003 00:03

Hi,

hey, das is cool.
Also erstmal ist kurz zu erwähnen dass der obige Code (also der von "meiner" HookProc) unter 9x prima läuft, das also alles tatsächlich daran liegt, dass XP NT basierend ist.
Jetz hab ich durch Rumprobieren rausgefunden, dass bei dieser LL-Struktur die Eigenschaft vkCode den ASCII-Code des gedrückten Zeichens enthält.
Irgendwie scheints zwar so zu sein, dass die Eigenschaften dieser Struktur unter der 9x- Struktur so heißen würden wie in deinen Kommentaren, aber sie enthalten andere Daten.
Unter 9x enthielt message ja ganz normal die Message, ob keyup, keydown oder sonstwas, und hier wie gesagt den ASCII- Code.
Deshalb hats auch zuerst nich geklappt, einfach den Code von Oben zu nehmen und einfach nur die Eigenschaften die angesprochen werden umzubenennen.
Ich frag mich nur, aus welcher Eigenschaft man jetz die Message lesen kann, um spezifisch auf Drücken und Loslassen reagieren zu können. Meine Hilfe kann mir dazu ja schlecht was sagen, kannte sie ja nichtmal diese Datenstruktur.
Und zu time:
Kann man daraus auslesen, wie lange eine Taste gedrückt gehalten wird? Könnt mir nicht vorstellen wie, weil doch nach diesem Wiederholungsintervall von Windows (also wenn man einen Buchstaben länger gedrückt hält, erscheint er im Endeffekt so oft wie der Intervall in die Zeit des Gedrückthaltens reinpasst) der Hook wieder aufgerufen wird.
Aber wichtig wäre mir erstmal rauszufinden welche Eigenschaft ich für die Message auslesen muss.
Wenn man nochn bissken mit den vkCodes rumprobiert, findet man auch die Werte raus, die so Sachen wie Shift und so wiedergeben.
160 lShift, 161 rShift, 162 lStrg, 163 rStrg, 164 Alt, 165 AltGr (wobei vor 165 immer ein 162 mitkommt).
Is halt nur wie gesagt ohne die Unterscheidung für keyup und keydown blöd, weil man sonst für jeden Tastendruck zwei identische Werte bekommt.

Aber nichts desto Trotz:
VIELEN DANK!!!!! :bouncing4: :firejump: :bounce1: :bounce2: :hello: :bounce2: :bounce1: :firejump: :bouncing4:

Hast mir ganz schön weitergeholfen mit der Datenstruktur.
Insgeheim machts mich ja schon ein Bisschen stolz den größten Teil (halt bis auf wm_keyup und wm_keydown) selbst rausgefunden zu haben...
Würd mich freuen, wenn sich auch dieses Problem aus dem Weg schaffen ließe.

Bis dann und nochmal danke,

S - tefano

Christian Seehase 20. Jun 2003 00:57

Moin Stefano,

am besten schaust Du Dir die Struktur mal im MSDN oder im PSDK an. Da sind die Felder alle erklärt.

Das mit dem AltGr ist auch leicht zu erklären:
Es werden unmittelbar nacheinander die Codes für CTRL und ALT generiert AltGr selber hat keinen.
Warum man trotzdem mit AltGr+Del keinen CTRL+ALT+DEL simulieren kann ich mir nicht so ganz erklären, es scheint da noch mehr mitzuspielen.

S - tefano 20. Jun 2003 10:53

Hi,

also dieses MSDN is schon ne feine Institution...

Zitat:

flags
Specifies the extended-key flag, event-injected flag, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke flags. Value Purpose
LLKHF_EXTENDED Test the extended-key flag.
LLKHF_INJECTED Test the event-injected flag.
LLKHF_ALTDOWN Test the context code.
LLKHF_UP Test the transition-state flag.

0
Specifies whether the key is an extended key, such as a function key or a key on the numeric keypad. The value is 1 if the key is an extended key; otherwise, it is 0.
1-3
Reserved.
4
Specifies whether the event was injected. The value is 1 if the event was injected; otherwise, it is 0.
5
Specifies the context code. The value is 1 if the ALT key is pressed; otherwise, it is 0.
6
Reserved.
7
Specifies the transition state. The value is 0 if the key is pressed and 1 if it is being released.
Heißt also dass ich gucken muss ob dieses LLKHF_UP 0 oder 1 ist.
Kann ich das einfach über flags.LLKHF_UP ansprechen, oder muss ich sowas machen wie

Delphi-Quellcode:
if flags[7] = 0 then ...
? Also schonmal vorweg, durch Rumprobieren hab ich rausgefunden dass die obigen zwei Sachen nich gehen.
Ich kann allerdings einfach die flag an sich abfragen. Wenn die Taste gedrückt wird, kommt ganz normal 0, aber wenn man sie loslässt 128. Warum?
Und. Wie kann ich denn überhaupt die verschiedenen Flags voneinander unterscheiden?

Bis dann,

S - tefano

Christian Seehase 20. Jun 2003 11:10

Moin Stefano,

Zitat:

Zitat von Stefano
Wenn die Taste gedrückt wird, kommt ganz normal 0, aber wenn man sie loslässt 128. Warum?

Bei dem Feld Flags handelt es sich um ein Bitfield, dass heisst:
Um an die verschiedenen enthaltenen Informationen zu gelangen, muss man die jeweils interessanten Bits ausmaskieren.
Ausmaskieren heisst: Alle Bits bis auf das zu prüfende (oder die zu prüfenden) werden auf 0 gesetzt.

Zitat:

Zitat von MSDN
7
Specifies the transition state. The value is 0 if the key is pressed and 1 if it is being released.

Bit 7 (die Bits werden von rechts nach links, nullbasiert, durchnummeriert) gibt den Transistion State wieder.
Um nur dieses zu erhalten, und die anderen auf 0 zu setzen, muss das Feld Flags mit einem Wert logisch und (AND) verknüpft werden, der nur an dieser Stelle eine 1 enthält (Hex: 00000080).
Da LLKHF_UP dieses tun soll, gehe ich mal davon aus, dass es den genannte Wert enthält.

Delphi-Quellcode:
if (flag and LLKHF_UP) = LLKHF_UP then // Taste wurde losgelassen
                                  else // Taste wurde gedrückt
Jetzt klärt sich wohl auch das pendeln zwischen 0 und 128. 80 Hex ist 128 Dezimal (16*8)

Dieses ausmaskieren von Bits findest Du an vielen Stellen, z.B. auch bei den FileAttributes wenn Du FindFirst verwendest (faDirectory wäre beispielsweise so eine Maske).

S - tefano 20. Jun 2003 12:37

Hi,

ahsooo :idea: , ich glaub so langsam komme ich dahinter.
Allerdings gibts damit 2 Probleme.
Zum einen ist meinem Delphi das Flag LLKHF_UP nicht bekannt. Ich habs dann in meinem Projekt als Konstante mit Wert 7 deklariert (so wegen 7tes Bit).
Wenn ich mir dann zum Testen mal den Wert dieses Flags ausgeben lasse

Delphi-Quellcode:
showmessage(inttostr(PKBDLLHOOKSTRUCT(lparam).flags and LLKHF_UP));
(was hoffentlich richtig ist), wird mir bei beiden Ereignissen (beim Drücken und Loslassen) eine Null angezeigt.
Hab ich bei der Flag- Deklaration irgendwas falsch gemacht, oder Frage ich das Flag noch falsch ab?

Bis dann,

S - tefano

Christian Seehase 20. Jun 2003 12:48

Moin Stefano,

wie ich es auch schon geschrieben habe:

Das Bit 7 hat den dezimalen Wert 2 hoch 7 also 128 oder $80
Daher kommt doch auch der Wechsel zwischen 0 und 128 den Du beobachtet hast.

(Bit 6 dann 2 hoch 6 usw.)

Was die Deklaration derartiger Konstanten angeht:
Wenn Du über eine entsprechende Anbindung verfügst, lade Dir mal das PSDK runter. Da sind dann auch Header Dateien dabei, in denen solche Konstanten deklariert sind.

In diesem speziellen falle wäre das allerdings nicht mal notwendig, da die Doku die Wert indirekt schon angibt.

Verbinde einfach mal die Information welches Bit was angibt, mit der Information welche Konstante welche Bits anspricht. Daraus ergibt sich dann zwangsläufig, welche Konstante welchen Wert haben muss.

Die Aktion mit dem ausmaskieren noch mal etwas sichtbarer (alle Angaben jetzt im Binärformat, auf 8 Bit reduziert)

Code:
Der eingelesene Wert (Beispiel) : 10111001
Die Konstante LLKHF_UP         : 10000000
Beide mit AND verknüpft        : 10000000
                                  !      !--> Bit 0
                                  !---------> Bit 7

S - tefano 20. Jun 2003 15:33

Hi,

hmmmmhmmmmhmmmm :? :? :? :? :?
Also ich sag mal so. Wo da jetz der Fehler lag, hab ich nach einigen Überlegungen begriffen.
Funktioniert jetz auch, und ich weiß auch warum.
Aber irgendwie find ichs ja schon schade, dass ich mir ziemlich sicher bin dass ich auf sowas niemals selber kommen werde...
Aber mit der Konstante als $80 läufts jetz.

Delphi-Quellcode:
          if (PKBDLLHOOKSTRUCT(lparam).flags and LLKHF_UP) = LLKHF_UP then
            showmessage(keystr + ' UP')
          else
            showmessage(keystr);
Und obiger Code funktioniert endlich wunderbar.

Also nochmal vielen Dank für die Hilfe,

S - tefano

Christian Seehase 20. Jun 2003 16:06

Moin Stefano,

bitte, gerne.

Zitat:

Zitat von S - tefano
Aber irgendwie find ichs ja schon schade, dass ich mir ziemlich sicher bin dass ich auf sowas niemals selber kommen werde...

das ist natürlich auch Übungssache.

Was dabei helfen kann ist die Beschäftigung mit dem Binärsystem, und logischen Verknüpfungen/Operatoren (and, or, xor, not, shl, shr).

Da dieses Verfahren in der API öfter eingesetzt wird, kann's nicht schaden sich mal damit zu beschäftigen.


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