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 Problem mit CreateProcess (https://www.delphipraxis.net/52192-problem-mit-createprocess.html)

Patrick 25. Aug 2005 09:45


Problem mit CreateProcess
 
Hallo,

Ich verwende die folgende Funktion, um einen CMD-Befehl auszuführen und die Rückmeldung abzufangen.

Delphi-Quellcode:
function GetConsoleOutput(const Command: String; var Output, Errors: TStringList): Boolean;
var StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  SecurityAttr: TSecurityAttributes;
  PipeOutputRead: THandle;
  PipeOutputWrite: THandle;
  PipeErrorsRead: THandle;
  PipeErrorsWrite: THandle;
  Succeed: Boolean;
  Buffer: array [0..255] of Char;
  NumberOfBytesRead: DWORD;
  Stream: TMemoryStream;
begin
  //ShowMessage(command);
  //Initialisierung ProcessInfo
  FillChar(ProcessInfo, SizeOf(TProcessInformation), 0);
  //Initialisierung SecurityAttr
  FillChar(SecurityAttr, SizeOf(TSecurityAttributes), 0);
  SecurityAttr.nLength := SizeOf(SecurityAttr);
  SecurityAttr.bInheritHandle := true;
  SecurityAttr.lpSecurityDescriptor := nil;
  //Pipes erzeugen CreatePipe(PipeOutputRead, PipeOutputWrite, @SecurityAttr, 0);
  CreatePipe(PipeErrorsRead, PipeErrorsWrite, @SecurityAttr, 0);
  //Initialisierung StartupInfo
  FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
  StartupInfo.cb:=SizeOf(StartupInfo);
  StartupInfo.hStdInput := 0;
  StartupInfo.hStdOutput := PipeOutputWrite;
  StartupInfo.hStdError := PipeErrorsWrite;
  StartupInfo.wShowWindow := sw_Hide;
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  if CreateProcess(nil, PChar(command), nil, nil, true,
  CREATE_DEFAULT_ERROR_MODE or CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil,
  StartupInfo, ProcessInfo) then
  begin
    result:=true;
    //Write-Pipes schließen
    CloseHandle(PipeOutputWrite);
    CloseHandle(PipeErrorsWrite);
    //Ausgabe Read-Pipe auslesen
    Stream := TMemoryStream.Create;
    try
      while true do
      begin
        succeed := ReadFile(PipeOutputRead, Buffer, 255, NumberOfBytesRead, nil);
        if not succeed then break;
        Stream.Write(Buffer, NumberOfBytesRead);
      end;
      Stream.Position := 0;
      Output.LoadFromStream(Stream);
    finally
      Stream.Free;
    end;
    CloseHandle(PipeOutputRead);
    //Fehler Read-Pipe auslesen
    Stream := TMemoryStream.Create;
    try
      while true do
      begin
        succeed := ReadFile(PipeErrorsRead, Buffer, 255, NumberOfBytesRead, nil);
        if not succeed then break;
        Stream.Write(Buffer, NumberOfBytesRead);
      end;
      Stream.Position := 0;
      Errors.LoadFromStream(Stream);
    finally
      Stream.Free;
    end;
    CloseHandle(PipeErrorsRead);
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    CloseHandle(ProcessInfo.hProcess);
  end
  else
  begin
    result:=false;
    CloseHandle(PipeOutputRead);
    CloseHandle(PipeOutputWrite);
    CloseHandle(PipeErrorsRead);
    CloseHandle(PipeErrorsWrite);
  end;
end:
Diesen Befehl gebe in in Command ein:
cmd /c "C:\Programme\MySQL\MySQL Server 4.1\bin\mysqldump.exe" -hlocalhost -ubenutzer -ppasswort --opt "web1db1" > "D:\download\2005-08-24 13-27\web1db1.sql"

Allerdings ist die Rückmeldung weniger schön:
Der Befehl "C:\Programme\MySQL\MySQL" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.

Er rafft nicht, dass er die ganze Zeile bearbeiten soll. Wenn ich exact diese Zeile (Ohne das "cmd /c ") in die Eingabeaufforderung eingebe funktioniert es.

Lasse ich im Command meines Programmes das "cmd /c " weg gibt's diese Meldung:
mysqldump: Couldn't find table: ">"
Was auch schwachsinn ist, denn der Befehl ansich ist richtig!

Also wo liegt der Fehler? Bin ich es oder die Api CreateProcess?

[edit=SirThornberry]Neu abgespeichert um Higlighting zu korrigieren. Mfg, SirThornberry[/edit]

Olli 25. Aug 2005 09:49

Re: Problem mit CreateProcess
 
lpCommandLine und lpApplicationName beachten!

Sorry, wenn's etwas unverständlich war:
Delphi-Quellcode:
CreateProcess(nil, PChar(command), ...
... es ist gewagt einerseits das Modul wegzulassen und andererseits alles zusammenzupappen. Doku lesen bildet :zwinker:

Ach ja und bei lpApplicationName heißt es dann '<Pfad zur EXE>\cmd.exe' ...

Patrick 25. Aug 2005 11:20

Re: Problem mit CreateProcess
 
Also langsam gehen mir die Optionen aus...

Delphi-Quellcode:
CreateProcess('C:\Windows\System32\cmd.exe',' /c "C:\Programme\MySQL\MySQL Server 4.1\bin\mysqldump.exe" -hlocalhost -ubenutzer -ppasswort --opt "web1db1" > "D:\download\2005-08-24 13-27\web1db1.sql"',...
Dabei haben wir wieder diese Meldung:
Der Befehl "C:\Programme\MySQL\MySQL" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.

Ich habe es auch direkt probiert:
Delphi-Quellcode:
CreateProcess('C:\Programme\MySQL\MySQL Server 4.1\bin\mysqldump.exe',' -hlocalhost -ubenutzer -ppasswort --opt "web1db1" > "D:\download\2005-08-24 13-27\web1db1.sql"',...
Und hier gibt's diese:
mysqldump: Couldn't find table: ">"

Olli 25. Aug 2005 13:06

Re: Problem mit CreateProcess
 
Okay, die erste Variante müßte eigentlich funktionieren. Keine Ahnung warum sie es nicht tut. Ist das Programm ein Konsolenprogramm? Wenn ja, kannst du den Output statt über ">" (welches ja nur im Kontext vom CMD funktioniert) auch die Konsolenhandles benutzen, die du in der Struktur zurückgeliefert bekommst. Dazu würde ich den Prozess suspended starten und dann asynchron auslesen. Danach wieder aufwecken und die asynchronen Callbacks sollten den Output erhalten können.

Patrick 25. Aug 2005 17:25

Re: Problem mit CreateProcess
 
Erstmal vielen Dank für deine Ratschläge, aber ... ich verstehe nur Bahnhof. Ich habe die obrige Funktion von Delphi-Source.de geklaut und gebetet, dass sie das tut, was ich will. Ich habe ein wenig Ahnung vom Programmieren und ich weis, es gibt noch viel zu lernen für mich ,aber dies scheint mir ein wenig zu kompliziert zu sein.

Olli 25. Aug 2005 17:36

Re: Problem mit CreateProcess
 
Okay, paß auf. Ich werde das mal eben austesten ... es geht dir ja wohl insbesondere um die Ausgabe dieses Konsolenprogramms, richtig?

turboPASCAL 25. Aug 2005 18:56

Re: Problem mit CreateProcess
 
Ich verwende diese Methode da sie auch "zwischendurch" die Ausgaben umleitet.

Delphi-Quellcode:
procedure RunConsoleApp(const CommandLine: string; AStrings: TStrings);
type
  TCharBuffer = array[0..MaxInt - 1] of Char;
const
  MaxBufSize = 1024;
var
  I: Longword;
  SI: TStartupInfo;
  PI: TProcessInformation;
  SA: PSecurityAttributes;
  SD: PSECURITY_DESCRIPTOR;
  NewStdIn: THandle;
  NewStdOut: THandle;
  ReadStdOut: THandle;
  WriteStdIn: THandle;
  Buffer: ^TCharBuffer;
  BufferSize: Cardinal;
  Last: WideString;
  Str: WideString;
  ExitCode: DWORD;
  Bread: DWORD;
  Avail: DWORD;
begin
  GetMem(SA, SizeOf(TSecurityAttributes));
  case Win32Platform of
    VER_PLATFORM_WIN32_NT:
      begin
        GetMem(SD, SizeOf(SECURITY_DESCRIPTOR));
        SysUtils.Win32Check(InitializeSecurityDescriptor(SD, SECURITY_DESCRIPTOR_REVISION));
        SysUtils.Win32Check(SetSecurityDescriptorDacl(SD, True, nil, False));
        SA.lpSecurityDescriptor := SD;
      end; {end VER_PLATFORM_WIN32_NT}
  else
    SA.lpSecurityDescriptor := nil;
  end; {end case}
  SA.nLength := SizeOf(SECURITY_ATTRIBUTES);
  SA.bInheritHandle := True;
  SysUtils.Win32Check(CreatePipe(NewStdIn, WriteStdIn, SA, 0));
  if not CreatePipe(ReadStdOut, NewStdOut, SA, 0) then
  begin
    CloseHandle(NewStdIn);
    CloseHandle(WriteStdIn);
    SysUtils.RaiseLastWin32Error;
  end; {end if}
  GetStartupInfo(SI);
  SI.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
  SI.wShowWindow := {SW_SHOWNORMAL} SW_HIDE;
  SI.hStdOutput := NewStdOut;
  SI.hStdError := NewStdOut;
  SI.hStdInput := NewStdIn;
  if not CreateProcess(nil, PChar(CommandLine), nil, nil, True,
    CREATE_NEW_CONSOLE, nil, nil, SI, PI) then
  begin
    CloseHandle(NewStdIn);
    CloseHandle(NewStdOut);
    CloseHandle(ReadStdOut);
    CloseHandle(WriteStdIn);
    SysUtils.RaiseLastWin32Error;
  end; {end if}
  Last := '';
  BufferSize := MaxBufSize;
  Buffer := AllocMem(BufferSize);
  try
    repeat
      SysUtils.Win32Check(GetExitCodeProcess(PI.hProcess, ExitCode));
      PeekNamedPipe(ReadStdOut, Buffer, BufferSize, @Bread, @Avail, nil);
      if (Bread <> 0) then
      begin
        if (BufferSize < Avail) then
        begin
          BufferSize := Avail;
          ReallocMem(Buffer, BufferSize);
        end; {end if}
        FillChar(Buffer^, BufferSize, #0);
        ReadFile(ReadStdOut, Buffer^, BufferSize, Bread, nil);
        Str := Last;
        I := 0;
        while (I < Bread) do
        begin
          case Buffer^[I] of
            #0: inc(I);
            #10:
              begin
                inc(I);
                AStrings.Add(Str);
                Str := '';
              end; {end #10}
            #13:
              begin
                inc(I);
                if (I < Bread) and (Buffer^[I] = #10) then
                  inc(I);
                AStrings.Add(Str);
                Str := '';
              end; {end #13}
          else
            begin
              Str := Str + Buffer^[I];
              inc(I);
            end; {end else}
          end; {end case}
        end; {end while}
        Last := Str;
      end; {end if}
      Sleep(1);
      Application.ProcessMessages;
    until (ExitCode <> STILL_ACTIVE);
    if Last <> '' then
      AStrings.Add(Last);
  finally
    FreeMem(Buffer);
  end; {end try/finally}
  CloseHandle(PI.hThread);
  CloseHandle(PI.hProcess);
  CloseHandle(NewStdIn);
  CloseHandle(NewStdOut);
  CloseHandle(ReadStdOut);
  CloseHandle(WriteStdIn);
end; {end procedure}
:gruebel: .. wo hab ich den jetzt her?

Aufruf z.B.:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear;
  RunConsoleApp('cmd.exe /c dir c:\windows\*.*', Memo1.Lines);
end.
...bei dem Umleiten mit ">" konnte ich noch keine Probleme feststellen.

[edit=SirThornberry]Neu abgespeichert um Higlighting zu korrigieren. Mfg, SirThornberry[/edit]

Olli 25. Aug 2005 19:00

Re: Problem mit CreateProcess
 
@turboPASCAL: Danke. Wieder etwas Zeit gespart :thumb:

Patrick 26. Aug 2005 14:10

Re: Problem mit CreateProcess
 
Ich hab's jetzt so aufgerufen
Delphi-Quellcode:
RunConsoleApp('"C:\Programme\MySQL\MySQL Server 4.1\bin\mysqldump.exe" -hlocalhost -uuser -ppasswort --opt "web1db1" > "D:\download\2005-08-26 14-59\web1db1.sql"',Memo1.Lines);
Ergebnis:

-- MySQL dump 10.9
--
-- Host: localhost Database: test
-- ------------------------------------------------------
-- Server version 4.1.13a-nt

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
mysqldump: Couldn't find table: ">"

Wenn ich es mit "cmd \c" aufrufe gabs wieder ein bekannten Fehler:
Der Befehl "C:\Programme\MySQL\MySQL" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.

turboPASCAL 26. Aug 2005 14:23

Re: Problem mit CreateProcess
 
Was passiert wen Du den cmd /c Befehl weglässt ?

Patrick 26. Aug 2005 14:26

Re: Problem mit CreateProcess
 
Na das hier:
Zitat:

-- MySQL dump 10.9
--
-- Host: localhost Database: test
-- ------------------------------------------------------
-- Server version 4.1.13a-nt

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
mysqldump: Couldn't find table: ">"

Olli 26. Aug 2005 15:09

Re: Problem mit CreateProcess
 
Zitat:

Zitat von Patrick
Na das hier:[...]

...aber dann ist doch der Aufruf schon falsch, nicht der Code von turboPASCAL!

turboPASCAL 26. Aug 2005 15:28

Re: Problem mit CreateProcess
 
Hast Du mal geschaut ob dein Aufruf auf einer Console (Eingabeaufforderung) mit der Pipe Umleitung ">" funktioniert ?

@Olli das der Code von turboPASCAL falsch kann ich nicht sagen, habe da gerade eine kleine Merkwürdigkeit festgestellt.

// Edit:
Zitat:

Zitat von turboPASCAL
@Olli das der Code von turboPASCAL falsch kann ich nicht sagen, habe da gerade eine kleine Merkwürdigkeit festgestellt.

Wenn man ein Readln; in sein Code einbaut und sich Wundert warum sich die Anwendung nicht selbst Beendet... :wall:

Patrick 26. Aug 2005 17:41

Re: Problem mit CreateProcess
 
Ich habe nie behauptet, dass ein Code von euch nicht funktioniert. Ich verstehe eure Funktionen und Prozeduren vielleicht nicht komplett, ich kann aber sehr wohl erkennen, dass ihr mit dem Befehl ansich nichts macht. Ich sage doch die ganze Zeit, dass es an CreateProcess liegen muss. Dahin übergebt ihr schließlich die "CommandLine"!

Und ja, mit der Befehl funktioniert in der Eingabeaufforderun (CMD) perfekt
Zitat:

"C:\Programme\MySQL\MySQL Server 4.1\bin\mysqldump.exe" -hlocalhost -uuser -ppasswort --opt "web1db1" > "D:\download\2005-08-26 14-59\web1db1.sql"
Ich habe eben diesen Befehl in eine Standard Verknüpfung integriert, und auch hier funktioniert es nicht, allerdings gelingt es mir hierbei nicht an die Fehlermeldung zu kommen.

Eigentlich weis ich, wie ich den Fehler umgehen kann, allerdings möchte ich das nicht. Die Lösung liegt ganz einfach darin die Gänsefüsschen an "web1db1" und "D:\download\2005-08-26 14-59\web1db1.sql" wegzulassen. Allerdings kann ich das nicht, da in dem Pfad eine Leerzeile ist, die da auch bleiben soll. Und schließlich funktioniert der Befehl mit Gänsefüsschen in der CMD!

turboPASCAL 26. Aug 2005 18:04

Re: Problem mit CreateProcess
 
Hm... :gruebel: warum machst du nicht eine Batchfile mit folgendem Aufruf:
Code:
@echo off
"C:\Programm[b][...][/b]\mysqldump.exe" -hlocalhost -uuser -ppasswort --opt web1db1" > "D:\download[b][...][/b]\web1db1.sql"
pause
Ich hab k.A. von MySQL was soll den das ausgeben bzw. Umleiten ?

Wenn Du das ">" weglässt bekommst du doch deine Ausgabe in das Memo oder so. Oder gibt es dan eine extra "Fertig" Meldung ?

Olli 26. Aug 2005 18:46

Re: Problem mit CreateProcess
 
@Patrick: Wenn du es wie in deinem Beitrag von 15:10 machst, kann es nicht funktionieren, weil das Pipen (mit >) nur von CMD.EXE unterstützt wird. Ansonsten müßte mysqldump expliziten Support dafür mitbringen. Es wird also nur gehen über STDOUT, STDERR usw. die Daten auszulesen (siehe turboPASCALs Code).

@turboPASCAL: Verstehe ich auch nicht, dein Code sieht absolut plausibel aus ... wieso da jetzt nicht die Konsolenausgabe enthalten sein soll? :gruebel:

turboPASCAL 26. Aug 2005 19:12

Re: Problem mit CreateProcess
 
Hm.... :gruebel:

@Olli Also bei meinen Tests mit der Pipe-Umleitung (mit CMD.EXE) bekam ich noch keine Probleme es muss also event. an der mysqldump.exe liegen.

Leider hab ich kein SQL und kann es nicht austesten woran es liegt.

Aus Testzwecken würde ich auch mal kurze Dateinamen (8.3) benutzen und die Ausfhürungszeichnen nicht verwenden.

Oder alles in die Batchfile setzen und diese dann ausführen, wenn wie gesagt es auf der CMD funktionieren soll. Dann kann man ja immer noch die Ausgabe umleiten die von der Batch kommen.

Was ich mich Frage ist warum die Fehlermeldung (mysqldump: Couldn't find table: ">") kommt. Das weist darauf hin das er als Parameter eine Tabelle erwartet aber " > " findet.

Kann man noch in den Code einbauen... :gruebel: :zwinker: http://people.freenet.de/mgsdh/image..._Smiley_02.gif
Delphi-Quellcode:
[...] while (I < Bread) do
        begin

          case Buffer^[I] of
            #0: inc(I);
            #7: begin {PC-Beep ^G}
                  inc(I);
                  Windows.Beep(800, 150);
                  Str := Str + '^';
                end;
            #10: [...]

r2c2 26. Aug 2005 19:51

Re: Problem mit CreateProcess
 
Nur mal so ne Vermutung:
Das hängt irgendwie an den '"'. Versuch mal
Code:
'"cmd \c [...]" > "..."'
Also alles vor und nach dem '>' in '"' setzten.

Und wenns mit der Pipe trotzdem noch Probleme gibt: mysqldump hat IMHO ne "eingebaute Pipe". D.H. über irgend n Parameter(bitte selbst nachschlagen) kann man die Ausgabe in ne Datei umleiten. Man braucht also kein cmd dazu...

mfg

Christian

P.S.:
Es wäre vielleicht besser cmd per EnvironmentVariablen zu bestimmen. cmd gibts nämlich nur in der NT-Schiene. Ansonsten musst du Command.com nehmen...

Patrick 27. Aug 2005 09:31

Re: Problem mit CreateProcess
 
Ja, natürlich hängt es an den "", aber ich kann diese nicht weglassen, da in den Pfaden Leerstellen drin sind.

Also mit
Delphi-Quellcode:
'"cmd \c [...]" > "..."'
geht es nicht: "System Kann die angegebene Datei nicht finden"

und mit
Delphi-Quellcode:
'cmd \c "[...]" > "..."'
gibt's wiedermal die selbe Fehlermeldung: Der Befehl "C:\Programme\MySQL\MySQL" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.

Jelly 27. Aug 2005 10:21

Re: Problem mit CreateProcess
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Patrick,

also wenn es dir nur darum geht, ein Dump zu erstellen, kannst due gerne meine Komonente im Anhang benutzen. Da benutzte ich die gleiche Methode wie du, eben über mysqldump.exe. Hat auch ganz gut geklappt. Allerdings werte ich auch keine Rückmeldung von mysqldump.exe, das mit Dateinamen und Leerzeichen klappt aber. Am besten du schaust dir mal die Methode RunProcess in der Komponente an. Ist nicht von, allerdings ist mir die Quelle unbekannt.

Aber mein Tipp: nutze die MyDAC Komponenten, da hast du eine Komponente dabei, um direkt aus Delphi heraus ein Dump zu machen. Da brauchst du keine mysqldump.exe und kriegst auch keine lizenzrechtliche Probleme, da diese Datei nicht einfach mit deiner Anwendung weitergegeben werden darf (genausowenig wie die libmysql.dll übrigens).

edit: Hatt ich doch glatt den Anhang vergessen

Olli 27. Aug 2005 10:25

Re: Problem mit CreateProcess
 
Zitat:

Zitat von Patrick
Ja, natürlich hängt es an den "", aber ich kann diese nicht weglassen, da in den Pfaden Leerstellen drin sind.

Laß doch bitte, wenn du die Routine von turboPASCAL benutzt, mal alles ab dem ">" weg. Alles!

Was kommt raus?

mashutu 25. Jan 2008 10:05

Re: Problem mit CreateProcess
 
Zitat:

Zitat von turboPASCAL
Ich verwende diese Methode da sie auch "zwischendurch" die Ausgaben umleitet.

Du solltest am Ende den allozierten Speicher noch freigeben.

Delphi-Quellcode:
    if SA.lpSecurityDescriptor <> nil then
        freeMem(SD, SizeOf(SECURITY_DESCRIPTOR));
   
    freeMem(SA, SizeOf(TSecurityAttributes));
Gruss


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