API Reference
Complete reference for FoxCore.Runtime 3.4.2: the six COM types, their members, and what each returns when something goes wrong.
Starting the engine
The minimum that works: create, activate the license, attach to the thread, launch a task, pump while it runs, and stop.
LOCAL loRt, lcId
loRt = CREATEOBJECT("FoxCore.Runtime")
? loRt.Activate(lcJwt) && .T. -- your license, as a string
loRt.Attach(_VFP) && attaches the runtime TO THIS thread
loRt.MaxWorkers = 8
lcId = loRt.RunScript("RETURN 6 * 42", "la primera") && the script runs in ANOTHER VFP process
DO WHILE !loRt.IsCompleted(lcId)
loRt.Pump() && Pump() serves the workers; without it they just wait
DOEVENTS
ENDDO
? loRt.GetState(lcId) && Completed
? loRt.GetResult(lcId) && 252
? loRt.GetError(lcId) && ""
loRt.Shutdown() && ALWAYS: it is what kills the workersThe JWT is passed in code: there is no file to copy. How you get it and what happens when it is missing is on the Licensing page.
The six types
Three classes and their three interfaces. There is no seventh type, and there will not be: twice something looked like it needed one - TryCall's result, then GetStats - and both times the answer was a JSON string.
| Class | Interface | ProgId | What it is |
|---|---|---|---|
FoxRuntime | IFoxRuntime | FoxCore.Runtime | The object the host creates. It is the engine: license, options, tasks, pumping, events and handlers. |
FoxTaskHandle | IFoxTask | — | The handle NewTask() returns. Sugar: it holds no state, every property asks the registry. |
WorkerBridge | IFoxWorker | — | The runtime variable INSIDE the script. It is all the worker sees of the main process. |
FoxCore.Runtime
Lifecycle
| Member | Returns | Notes |
|---|---|---|
Activate(cLicenseKey) | L | Validates the JWT. It NEVER throws on an invalid license: it returns .F. and what gets blocked are the tasks, not the lifecycle. |
Attach(oHost, nVersion, cRuntimeMode) | — | Attaches the runtime TO THE THREAD it is called from. Usually loRt.Attach(_VFP). The last two are optional. |
Shutdown() | — | Idempotent. IT IS WHAT KILLS THE WORKERS. Always call it. |
Ping() | C | "FoxCore.Runtime 3.4.2". Works in any state, even after Shutdown(). |
Version | C | Read-only. |
State | C | Created | Activated | Attached | ShuttingDown | ShutDown. A STRING, not an enum: no numeric value crosses COM. |
IsRunning | L | .T. between Attach() and Shutdown(). |
IsLicensed | L | .T. if the JWT is valid. Since 3.4.0 there is NO exception by mode: it is required outside the IDE too. |
Options
All read/write. Set them after creating the object and before launching tasks.
| Property | Default | What it controls |
|---|---|---|
MaxWorkers | min(nucleos, 8) | Simultaneous worker processes. Range 1-64. Rule of thumb: physical cores. |
DefaultTimeoutSeconds | 30 | Deadline per task. Range 1-86,400. On expiry the task ends TimedOut and the worker dies. |
WorkerProgId | "VisualFoxpro.Application.9" | Which process acts as worker: VFP 9, VFP Advanced or your own registered EXE. The FORMAT is validated, not the existence: to know whether it works, ProbeWorker(). |
KeepCompletedTasks | 200 | How many FINISHED tasks the registry keeps before purging the oldest. It matters in large batches. |
MaxTasksPerWorker | 1 | How many tasks a worker serves before being recycled. 1 = one process per task, full isolation. 0 = no limit. |
WorkerInitScript | "" | Script that runs ONCE PER WORKER, before its first task. This is where the environment goes. |
WorkerResetScript | "" | Script that runs BETWEEN TASKS of a reused worker. This is where you clean up. |
WorkerInitFailurePolicy | "abort" | What happens if the init script errors: "abort" takes it down, "tolerate" lets its own ON ERROR take over and carry on. |
PumpMaxItems | 8 | How much each Pump() serves. |
PumpMaxMilliseconds | 20 | Time budget of each Pump(). |
RequestTimeoutSeconds | 15 | How long a worker blocked in Call() waits before getting a Timeout. It is what stops a host that does not pump from hanging a worker forever. |
SecurityMode | "Safe" | "Safe" is the only real mode. |
AuditLevel | "basic" | off | basic | full. |
Tasks
| Member | Returns | Notes |
|---|---|---|
RunScript(cScript, cTaskName) | C | Creates, queues and returns the task id. THE NORMAL WAY. |
NewTask(cScript, cTaskName) | FoxTaskHandle | Creates WITHOUT queueing, so you can configure it first (for instance with SetTaskTimeout). |
Start(cTaskId) | L | Queues a created task. .F. if it does not exist or was already started. |
GetState(cTaskId) | C | Created | Queued | Running | Completed | Failed | Cancelled | TimedOut, and NotFound for an unknown or already purged id. |
GetResult(cTaskId) | valor | The script's RETURN. .NULL. until it has finished successfully; an empty string for an unknown id. |
GetError(cTaskId) | C | An empty string if it did not fail; "Task not found." for an unknown id. |
IsCompleted(cTaskId) | L | .T. in any terminal state AND ALSO for an id that does not exist - on purpose: otherwise the canonical loop would spin forever once the purge took the task away. |
GetProgress(cTaskId) | N | 0-100. It works even if nobody pumps. |
GetProgressMessage(cTaskId) | C | The last message, or an empty string. |
PollCompleted() | C | The ids finished SINCE THE LAST CALL, separated by ";". One COM call per round instead of N. READING IT EMPTIES IT. |
SetTaskTimeout(cTaskId, nSegundos) | L | Deadline for ONE task. Only before it starts: NewTask + this + Start. .F. if it already started or does not exist. |
Forget(cTaskId) | L | Forgets a FINISHED task and frees its result. .F. if it does not exist or is still alive. |
Cancellation
| Member | Returns | Notes |
|---|---|---|
Cancel(cTaskId) | L | COOPERATIVE: raises the flag the script reads with runtime.IsCancelled() and instantly unblocks any Call in flight. .F. if there is nothing to stop. |
CancelAll() | N | How many it canceled. |
Kill(cTaskId) | L | HARD: kills the process by PID. For a script that never checks the flag it is the only way. It ends Cancelled with "Killed by host." |
Pumping
| Member | Returns | Notes |
|---|---|---|
Pump(nMaxItems, nMaxMs) | N | Serves the synchronous requests first - behind each one there is a BLOCKED worker - and the notifications after. Both parameters at 0 or absent = use the options. |
Events
| Member | Returns | Notes |
|---|---|---|
SetEventSink(oSink) | L | The runtime notifies FROM INSIDE Pump(), on your thread. It returns .F. WITHOUT THROWING if the object lacks the required methods. |
ClearEventSink() | — | Release it in the form's Destroy: while the runtime holds it, the form stays alive. |
The sink must have OnProgress(tcTaskId, tnPercent, tcMessage) and OnLog(tcTaskId, tcLevel, tcMessage), both required, and may have OnTaskCompleted(tcTaskId, tcState) and OnTaskFailed(tcTaskId, tcError), detected if present. The sink is additional: GetProgress() and GetProgressMessage() work just the same without it.
Handlers - what a worker can ask the host for
| Member | Returns | Notes |
|---|---|---|
RegisterHandler(cName, oTarget, cMethodName) | L | cMethodName defaults to "Invoke". It returns .F. WITHOUT THROWING if the name is empty, has spaces, exceeds 64 characters, is already registered, is one of the built-ins, OR IF THE OBJECT DOES NOT EXPOSE THAT METHOD - that last check makes the error surface in the form Init and not six hours later inside a worker. |
UnregisterHandler(cName) | L | .F. if it was not registered. |
GetHandlerNames() | C | The names separated by ";", alphabetical. It is the COMPLETE surface of what a worker can ask for. Three built-ins are always available: echo, upper and add. |
Shared blackboard
It solves "the worker needs the host's configuration" with no round trip and no pump.
| Member | Returns | Notes |
|---|---|---|
Publish(cName, uValue) | — | Publishes a value the workers read with runtime.GetValue() with NO queue and NO pump cost. |
GetValue(cName) | valor | .NULL. if it does not exist. |
Other
| Member | Returns | Notes |
|---|---|---|
GetStats() | C | The engine snapshot as a flat JSON STRING. It returns a string and not an object for two reasons: a COM object costs one round trip PER PROPERTY, and adding a new type would break the six-type contract. |
ProbeWorker(cProgId) | C | Does that ProgId work as a worker? The answer comes from RUNNING it. With no argument it probes the current WorkerProgId. |
GetAuditPath() | C | WHERE the journal WOULD go. It writes nothing. |
SetCorrelation(cId, cName, cValue) | L | A CLOSED vocabulary of seven names. .F. without writing for anything else. |
GetCorrelation(cId, cName) | C | An empty string if not set. |
What GetStats() returns
A flat JSON string, parsed with jsonfox in a single call. There are two families of counters and confusing them is expensive:
- createdEver, finishedEver, completedEver, failedEver, cancelledEver, timedOutEver - they count what has HAPPENED since the runtime started and they NEVER go down. These are the ones for painting progress.
- total, created, completed, failed, cancelled, timedOut - they are the SNAPSHOT of the registry right now, and they do go down: Forget() and the purge subtract from them. These tell you what is alive.
{
"state": "Attached",
"workerProgId": "VisualFoxpro.Application.9",
"maxWorkers": 8,
"queued": 22,
"running": 8,
"peakRunning": 8,
"liveWorkers": 8,
"createdEver": 30,
"finishedEver": 0,
"completedEver": 0,
"failedEver": 0,
"cancelledEver": 0,
"timedOutEver": 0,
"total": 30,
"created": 0,
"completed": 0,
"failed": 0,
"cancelled": 0,
"timedOut": 0,
"purged": 0,
"keepCompletedTasks": 200,
"completedPending": 0,
"completedDropped": 0,
"pendingRequests": 0,
"expiredRequests": 0,
"pendingNotifications": 3,
"droppedTelemetry": 0,
"handlers": 2,
"requestsHandled": 47,
"handlerErrors": 0
}FoxTaskHandle
Returned by NewTask(). It holds no state: every property delegates to the engine's registry. It is sugar - everything it does can be done with the taskId and the runtime methods.
| Member | Type | Notes |
|---|---|---|
Id | C | The taskId. THE CANONICAL TRUTH is this string: it crosses COM with no lifetime cost and it is what you paste into a ticket. |
Name | C | The name given when it was created. |
State | C | The same as GetState(Id). |
Result | valor | The same as GetResult(Id). |
Error | C | The same as GetError(Id). |
IsCompleted | L | The same as IsCompleted(Id). |
OperationId | C | It exists and is ALWAYS an empty string: operations are not implemented. |
Start() | L | Queues the task. The same as Start(Id). |
WorkerBridge - the runtime variable inside the script
It is all the worker sees of the main process, and its surface is scoped to that task. The "Blocks?" column is the one that matters: only Call and TryCallJson wait for the host to pump, because they are the only ones that run VFP code in the main process.
| Member | Blocks? | Notes |
|---|---|---|
TaskId / TaskName | no | The id and name of THIS task. |
ElapsedSeconds | no | Seconds since the task started. |
Ping() | no | "pong". |
Progress(nPercent, cMessage) | no | Fire-and-forget. It needs no pump and does not fail. The percentage is clamped to 0-100. |
Log(cLevel, cMessage) | no | "info" | "warn" | "error". Fire-and-forget. |
IsCancelled() | no | A LOCAL read, it does not touch the host. Meant to be called on every turn of a loop. |
GetValue(cName) / SetValue(cName, uValue) | no | Shared blackboard, local, no pump. |
Call(cName, arg1..arg8) | SI | Calls a host handler. Synchronous: the worker waits for the host to pump. It THROWS an OLE error if the handler fails. |
TryCallJson(cName, arg1..arg8) | SI | Like Call but it NEVER THROWS. It returns JSON: {"ok":true,"value":"42","error":"","errorCode":""}. The value always travels AS A STRING. |
Patterns
The two loops you need to know:
- Normal batch: launch with RunScript(), keep the ids, and spin a DO WHILE calling Pump() and DOEVENTS until IsCompleted() says yes for all of them.
- Large batch (hundreds or thousands): asking task by task is N COM calls per round. Raise KeepCompletedTasks, call PollCompleted() once per round, read the result of the ids it hands you, and call Forget() on each. Progress comes from finishedEver, not from completed: with Forget() every round the snapshot stays at zero, and that is correct.
Both of them, with their full code and the traps of each, are on the Patterns page.
What no longer exists
If you arrive from a search engine or from code written for the classic engine (WinFx.FoxCore), this is what changed. The classic engine still exists and is still supported; it is not the one described on this page.
| Classic engine | In FoxCore.Runtime 3.4.2 |
|---|---|
CREATEOBJECT("WinFx.FoxCore") / _screen.FoxCore | CREATEOBJECT("FoxCore.Runtime") - a different COM object, with a different ProgId and GUID. Both can be registered at the same time. |
Run(cProc, oParams) | RunScript(cScript, cName). You no longer pass the NAME of a procedure that must exist in the worker: you pass the SCRIPT, as text, and the worker runs it with EXECSCRIPT(). |
RunMultiple(@laTareas) | No equivalent, on purpose: you call RunScript() in a FOR and keep the ids. A method taking an array by reference does not cross COM reliably, and what it returned was a container object: one more type, and the contract is six. |
WaitAll() / WaitAny() | No equivalent, and this is the deep difference: the loop is written by the HOST, with Pump() and DOEVENTS. A blocking WaitFor inside VFP is exactly the frozen UI this product exists to avoid. |
GetTasksSummary() / GetSystemStats() | GetStats() for the numbers (queued, running, liveWorkers) and PollCompleted() to know who just finished. An array of Task objects over COM costs one round trip per property per task. |
CancelTask(loTask) | Cancel(cTaskId). Identified by id, not by object. |
Activate(cClave) | Activate(cJwt), same name but a different key: since 3.4.0 there is no file, the license is a JWT passed in code. |
foxcore.lic | No equivalent: since 3.4.0 there is no license file. See the Licensing page. |
PROCEDURE MiTarea(loParams, loTask) | Inside the script there are no parameters: there is a variable called runtime (WorkerBridge). loTask.Progress = 50 becomes runtime.Progress(50, "...") and loTask.Result = x becomes RETURN x. |
Next: Licensing ->