Viele VFP-Anwendungen haben in einer Ecke einen PC mit einer offenen exe und einem Timer darin: Er importiert die Bestellungen aus dem Webshop, verschickt die ausstehenden E-Mails, macht jede Nacht den Abschluss. Das funktioniert, solange niemand ihn schließt, sich niemand abmeldet und Windows nicht für ein Update neu startet.
In diesem Tutorial bauen wir pedidos (spanisch für „Bestellungen“), einen Windows-Dienst in Visual FoxPro, der diese Arbeit erledigt, ohne dass jemand davor sitzt. Er importiert die Bestellungen, die ein Webshop in einem Ordner ablegt, zählt alle 30 Sekunden die wartenden, schreibt jede Nacht eine Zusammenfassung und schickt jeden Morgen die offenen ans Lager. Er startet mit dem Rechner, läuft weiter, auch wenn sich niemand anmeldet, und startet von selbst neu, wenn er abstürzt.
Fast den ganzen Code bringt das Projekt schon mit: FoxService legt jeden neuen Dienst mit diesem Import an, und er funktioniert beim ersten Start. Wir gehen ihn durch, fügen einen eigenen Job hinzu und installieren ihn.
Ich habe es mit FoxForge auf Spanisch gebaut, deshalb sind die Screenshots auf Spanisch. FoxForge selbst spricht Ihre Sprache: Die Menünamen und die Schaltflächen gebe ich auf Deutsch an.
Was Sie brauchen
- Visual FoxPro 9 (oder VFP Advanced 10.2 in 32 Bit).
- FoxCli installiert. Es bringt FoxForge mit, die Werkstatt, die sich in die VFP-IDE einklinkt, und FoxService. Ohne Lizenz funktioniert alles genauso; den Unterschied sehen wir am Ende.
- Windows 10 oder 11. Fehlt die 32-Bit-Laufzeit von .NET 10, bietet der Installer von FoxCli an, sie herunterzuladen und zu installieren: Der Dienst braucht sie zum Starten.
Öffnen Sie VFP wie immer, ohne „Als Administrator ausführen“. Beim Kompilieren als Administrator registriert VFP die Komponente in HKLM, und diese Registrierung stört die folgenden Kompilierungen. Den Dienst zu installieren braucht dagegen Administratorrechte, aber die fragt Windows im richtigen Moment ab, mit seinem üblichen Fenster.
Schritt 1. FoxForge laden
Im VFP-Befehlsfenster:
DO "C:\Program Files (x86)\FoxForge\FoxForge.app"Passen Sie den Pfad an, wenn Sie FoxForge in einen anderen Ordner installiert haben. In der VFP-Menüleiste erscheint ein neues Menü, FoxForge, und von dort aus geschieht alles Weitere.
Schritt 2. Das Projekt anlegen
FoxForge > Neues Projekt > Windows-Dienst (FoxService). Im Fenster „Neuer Windows-Dienst“ braucht es nur zwei Dinge:
- Dienstname:
pedidos. Das ist auch der Name der.exeund der DLL, und der, den Sie in den Windows-Diensten sehen. - Ordner: Ihr Projektordner. Ich habe
C:\a1\tutorial-svcgenommen.
Lassen Sie das voreingestellte Windows-Konto, „Das System (LocalSystem)“: Es darf auf diesem Rechner alles, wie das Programm mit dem Timer, das es ersetzt. Die beiden anderen sind für den Fall, dass der Dienst einen Netzwerkordner erreichen muss, und Was wähle ich? öffnet das Handbuch, das erklärt, wann welches passt. Was unter Erweiterte Optionen… steht, bringt einen Wert mit, der für den Anfang taugt.

Klicken Sie auf Erstellen. FoxForge legt das Projekt an, öffnet es im Projekt-Manager und begrüßt Sie mit einem Fenster, das sagt, was ein Dienst ist und wo man anfängt: Erste Schritte öffnet das Handbuch und main.prg öffnen die einzige Datei, die Sie anfassen werden.

pedidos\
├── pedidos.pjx das VFP-Projekt
├── foxforge.json die Kennung als Dienstprojekt
├── foxservice.json die Konfiguration des Dienstes
├── src\
│ ├── main.prg Ihre Jobs: das Einzige, was Sie bearbeiten
│ ├── foxservice.prg die Basisklasse, die zu FoxService gehört
│ ├── config.fpw reist in der DLL mit
│ └── orders.frx der Bericht des Beispiels
└── dist\ hier landet der Dienst beim Kompilieren
├── data\ die Daten
└── inbox\ der Ordner, den das Beispiel überwacht, mit einer CSV mit zwei BestellungenSchritt 3. Es laufen sehen
Bevor Sie eine Zeile Code lesen, probieren Sie es aus. Strg+F7 kompiliert (FoxForge > Aktuelles Projekt > Dienst kompilieren) und legt den Dienst in dist\ ab. Ohne FoxCli-Lizenz fügt der Hinweis „Dienst kompiliert“ hinzu, dass er im Evaluierungsmodus läuft.
Strg+F8 (In der Konsole ausführen) startet ihn in einer Konsole, mit seinem Log auf dem Bildschirm, ohne etwas zu installieren:
INFO job PrepareData: orders.dbf created
INFO job PrepareData: orders.dbf ready: 0 order(s)
INFO job ImportOrders: orders_0001.csv: 2 new, 0 repeated
INFO job CountPending: Pending orders: 2
Der Dienst hat die Tabelle orders.dbf angelegt, die CSV importiert, die in inbox\ wartete, und sie nach inbox\processed\ verschoben. Nach 30 Sekunden zählt er die offenen Bestellungen, und danach alle 30 Sekunden wieder. Strg+C hält ihn an.
Schritt 4. Was main.prg mitbringt
Öffnen Sie src\main.prg. Unter einem langen Kommentar, der die Annotationen erklärt, steht die Klasse; ohne den Rumpf der Methoden ist es das hier:
DEFINE CLASS PedidosService AS FoxServiceBase OF foxservice.prg OLEPUBLIC
*!* @startup
PROCEDURE PrepareData && beim Start: die Tabelle, falls es sie nicht gibt
*!* @every 30s
PROCEDURE CountPending && alle 30 Sekunden: die offenen
*!* @watch inbox *.csv
PROCEDURE ImportOrders(tcPath, tcChange) && jede CSV, die in inbox\ ankommt
*!* @daily 23:55
PROCEDURE DailySummary && jede Nacht: die Zusammenfassung des Tages
PROCEDURE RebuildSummary && ohne *!*-Zeile: nur, wenn Sie ihn starten
PROTECTED PROCEDURE OpenOrders && Hilfsmethode: öffnet die Tabelle und wählt sie aus
PROTECTED PROCEDURE WriteSummary && Hilfsmethode: was beide Zusammenfassungen tun
ENDDEFINEDamit sieht man schon, wie alles funktioniert:
- Jede öffentliche Methode der Klasse ist ein Job, und die
*!*-Zeile darüber sagt, wann er läuft: in festen Abständen (@every), zu einer Uhrzeit (@daily), beim Start (@startup) oder wenn eine Datei in einem Ordner ankommt (@watch). Ohne*!*-Zeile läuft der Job nicht von selbst: Sie starten ihn. - Was kein Job ist, wird
PROTECTED PROCEDURE.OpenOrdersöffnet die Tabelle, legt sie beim ersten Mal mit ihrem Index an und lässt sie ausgewählt; alle Jobs fangen damit an, also ist es egal, welcher zuerst läuft. - Die Pfade kommen aus
THIS.Path("data"), dem Ordnerdataausfoxservice.json, und nie ausSET DEFAULT: Das aktuelle Verzeichnis gehört dem ganzen Prozess. THIS.Log()schreibt ins Log des Dienstes, dasselbe, das Sie in der Konsole gesehen haben.- Es gibt kein
TRY ... CATCH. Ein VFP-Fehler kommt von selbst ins Log, mit seiner Nummer und seiner Zeile, der Job zählt als fehlgeschlagen, und der Dienst läuft weiter.
Die Ersten Schritte im Handbuch (FoxForge > Hilfe > FoxService-Handbuch) gehen den Code jedes Jobs Zeile für Zeile durch: warum die CSV durch STRCONV() geht, warum das Datum mit EVALUATE("{^...}") gelesen wird und wie das SEEK verhindert, dass dieselbe Bestellung zweimal importiert wird.
Schritt 5. Von Hand ausprobieren
Mit dem Dienst wieder in der Konsole (Strg+F8) kopieren Sie die CSV aus dist\inbox\processed\ nach dist\inbox\. Das Log sagt:
INFO job ImportOrders: orders_0001_20261012143307.csv: 0 new, 2 repeatedDer Job ist idempotent: Beide Bestellungen waren schon drin, und das SEEK nach id überspringt sie. Die Datei geht zurück nach processed\, mit der Uhrzeit im Namen, damit die vorige nicht überschrieben wird.
Die Zusammenfassung läuft um 23:55, aber Sie müssen nicht warten. FoxForge > Aktuelles Projekt > Einen Job jetzt ausführen… zeigt die Jobs mit dem Zeitpunkt, zu dem jeder läuft, die manuellen zuerst:

Wählen Sie RebuildSummary und klicken Sie auf Ausführen. Läuft der Dienst, läuft der Job in ihm; sonst in einer Konsole, die mit dem Ergebnis offen bleibt. In dist\data\ erscheint summary_20261012.txt:
Orders: 2, total: 173.40Und wenn etwas schiefgeht und Sie hineinschauen wollen, führt Aktuelles Projekt > Prüfstand… einen Job direkt aus dem Quelltext aus, ohne zu kompilieren, mit dem VFP-Debugger zur Hand: Ein SET STEP ON funktioniert.
Schritt 6. Einen Job hinzufügen: das Lager
Jetzt ein eigener. Jeden Morgen möchte das Lager eine CSV mit den offenen Bestellungen, und diese Bestellungen müssen als verschickt markiert werden, damit sie nicht noch einmal geschickt werden. Fügen Sie diese Methode in die Klasse ein, unter RebuildSummary:
*-- Every morning: the pending orders, to a CSV for the warehouse,
*-- and marked as sent.
*!* @daily 07:00
PROCEDURE SendToWarehouse
LOCAL lnPending, lcFile
THIS.OpenOrders()
COUNT FOR status == "P" TO lnPending
IF lnPending = 0
THIS.Log("Nothing to send to the warehouse")
RETURN
ENDIF
lcFile = THIS.Path("data") + "warehouse_" + TTOC(DATETIME(), 1) + ".csv"
COPY TO (lcFile) FIELDS id, customer, amount FOR status == "P" TYPE CSV
REPLACE ALL status WITH "S" FOR status == "P"
THIS.Log(TRANSFORM(lnPending) + " order(s) sent to the warehouse: " + lcFile)
ENDPROCDie Namen und die Kommentare sind auf Englisch, wie der Rest des Beispiels; in Ihren Diensten verwenden Sie Ihre eigenen.
@daily 07:00ist im 24-Stunden-Format, nach der Uhr des Rechners, auf dem der Dienst läuft. Ein@daily 7:00 AMerreicht den Server nicht: Strg+F7 hält den Build an und zeigt die Zeile,main.prg(134): error: @daily needs a local time HH:MM from 00:00 to 23:59.- Er fängt mit
OpenOrders()an, wie die anderen: die Tabelle geöffnet und ausgewählt, also arbeitenCOUNTundREPLACEauf ihr. - Die Uhrzeit steht im Dateinamen (
TTOC(DATETIME(), 1)), weilCOPY TOohne Nachfrage überschreibt, auch mitSET SAFETY ON. Mit dem Datum allein würde ein zweiter Lauf am selben Tag die Datei vom Morgen überschreiben. COPY TO ... TYPE CSVschreibt in Windows-1252, wie die Tabelle: „Ferretería“ kommt mit seinem Akzent heraus.
Strg+F7, und ohne bis sieben Uhr zu warten: Einen Job jetzt ausführen… > SendToWarehouse. Das Log sagt:
INFO job SendToWarehouse: 2 order(s) sent to the warehouse: C:\a1\tutorial-svc\pedidos\dist\data\warehouse_20261012143501.csvid,customer,amount
1001,"Ferretería Ruiz",125.40
1002,"Bar Manolo",48.00Führen Sie ihn noch einmal aus: „Nothing to send to the warehouse“. Beide Bestellungen sind schon markiert, und der Zähler der offenen sagt in seiner nächsten Runde 0.
Schritt 7. Ihn als Windows-Dienst installieren
FoxForge > Aktuelles Projekt > Windows-Dienst… zeigt den Zustand des Dienstes und die Schaltflächen, um ihn zu bedienen. Klicken Sie auf Installieren: Windows fragt nach Administratorrechten, und der Dienst ist installiert und läuft. Ab jetzt startet er von selbst beim Hochfahren des Rechners, auch wenn sich niemand anmeldet, und Windows startet ihn neu, wenn er abstürzt.

Unter den Schaltflächen zeigt das Fenster, was status sagt (das Konto, den automatischen Start, was Windows tut, wenn er abstürzt), und weiter unten eine Zeile pro Job: wie oft er gelaufen, fehlgeschlagen oder übersprungen worden ist und wann er zuletzt gestartet ist. Kopieren Sie eine CSV nach dist\inbox\ und sehen Sie sie im Log, jetzt ohne Konsole: Es liegt in dist\logs\pedidos_20261012.log, eines pro Tag.
Um erneut zu kompilieren, halten Sie ihn zuerst an, mit Anhalten: Windows lässt die Dateien eines laufenden Dienstes nicht ersetzen, und wenn Sie es vergessen, erinnert Strg+F7 Sie daran. Kompilieren, und Starten.
Schritt 8. Ihn auf einen Server bringen
Der Ordner dist\ enthält alles, was der Dienst braucht, die VFP-Laufzeit eingeschlossen. Auf dem Server braucht es weder Visual FoxPro noch eine Lizenz; nur Windows mit der 32-Bit-Laufzeit von .NET 10. Kopieren Sie dist\ vollständig auf eine lokale Platte des Servers und geben Sie in einer Administrator-Konsole in diesem Ordner ein:
pedidos.exe install --startDas installiert ihn mit dem Konto aus foxservice.json (dem des Systems, das Sie beim Anlegen gewählt haben) und startet ihn. pedidos.exe status sagt, wie es um ihn steht, und pedidos.exe uninstall entfernt ihn und lässt die Logs liegen. Wenn der Dienst auf dem Server einen Netzwerkordner erreichen muss, erklärt das Handbuch, welches Konto Sie nehmen.
Schritt 9. Die Lizenz
Ohne Lizenz funktioniert der Dienst genauso: Alle Jobs laufen, ohne Einschränkungen und ohne Ablaufdatum. Das Einzige, was sich ändert: Er sagt es einmal beim Start, mit einer Zeile in seinem Log und einem Eintrag in der Ereignisanzeige:
WARN Built with FoxCli (evaluation): this service runs every job, without limits. A FoxCli license seals it: https://irwinrodriguez.dev/foxcliMit einer FoxCli-Lizenz, geladen unter FoxForge > FoxCli-Lizenz…, siegelt Strg+F7 den Dienst mit Ihrem Namen: Diese Zeile verschwindet, und das Log und status sagen, an wen er lizenziert ist. Die Lizenz gehört dem Programmierer, der kompiliert: Die Server, auf denen Sie Ihre Dienste installieren, brauchen keine.
Wie es weitergeht
- Das FoxService-Handbuch finden Sie unter FoxForge > Hilfe > FoxService-Handbuch: was in einem Job leben kann und was nicht, Timeouts und Quarantäne, die Windows-Konten,
@watchim Detail, PDF-Berichte und VFP Advanced. - Mit einem Agenten: FoxStack gibt Claude die Befehle von
foxserviceals Werkzeuge, um Dienste aus einem Gespräch heraus anzulegen, zu kompilieren und zu installieren. - Ideen, wie es mit
pedidosweitergeht: dassSendToWarehousedie CSV per E-Mail schickt, statt sie indata\zu lassen, oder ein zweites@watchfür einen Ordner mit Retouren.