IR
irwinrodriguez.dev
Volver a documentación

Referencia API

Referencia completa de FoxCore.Runtime 3.4.2: los seis tipos COM, sus miembros y qué devuelve cada uno cuando algo va mal.

Válido para FoxCore.Runtime 3.4.2 (2026-09-15). El contrato está congelado en seis tipos; los miembros crecen siempre por el final, así que nada de lo que esté aquí cambia de nombre ni de firma en una versión posterior.

Arrancar el motor

Lo mínimo que funciona: crear, activar la licencia, engancharse al hilo, lanzar una tarea, bombear mientras corre y parar.

Arrancar el motor: de CREATEOBJECT() a Shutdown()
LOCAL loRt, lcId

loRt = CREATEOBJECT("FoxCore.Runtime")
? loRt.Activate(lcJwt)                    && .T. -- tu licencia, como cadena
loRt.Attach(_VFP)                         && engancha el runtime A ESTE hilo
loRt.MaxWorkers = 8

lcId = loRt.RunScript("RETURN 6 * 42", "la primera")   && el guion corre en OTRO proceso VFP

DO WHILE !loRt.IsCompleted(lcId)
    loRt.Pump()                           && Pump() atiende a los workers; sin el, se quedan esperando
    DOEVENTS
ENDDO

? loRt.GetState(lcId)                     && Completed
? loRt.GetResult(lcId)                    && 252
? loRt.GetError(lcId)                     && ""

loRt.Shutdown()                           && SIEMPRE: es lo que mata a los workers

El JWT se pasa por código: no hay ningún fichero que copiar. Cómo se consigue y qué pasa si falta está en Licencias.

Los seis tipos

Tres clases y sus tres interfaces. No hay un séptimo tipo, y no lo habrá: dos veces pareció hacer falta uno (el resultado de TryCall y luego GetStats), y las dos veces la respuesta fue una cadena JSON.

ClaseInterfazProgIdQué es
FoxRuntime IFoxRuntime FoxCore.Runtime El objeto que crea el host. Es el motor: licencia, opciones, tareas, bombeo, eventos y handlers.
FoxTaskHandle IFoxTask — El handle que devuelve NewTask(). Azúcar: no guarda estado, cada propiedad pregunta al registro.
WorkerBridge IFoxWorker — La variable runtime DENTRO del guion. Es lo único que el worker ve del proceso principal.

FoxCore.Runtime

Ciclo de vida

MiembroDevuelveNotas
Activate(cLicenseKey)LValida el JWT. NUNCA lanza por licencia inválida: devuelve .F. y lo que se bloquea son las tareas, no el ciclo de vida.
Attach(oHost, nVersion, cRuntimeMode)—Engancha el runtime AL HILO desde el que se llama. Normalmente loRt.Attach(_VFP). Los dos ultimos son opcionales.
Shutdown()—Idempotente. ES LO QUE MATA A LOS WORKERS. Llamarlo siempre.
Ping()C"FoxCore.Runtime 3.4.2". Funciona en cualquier estado, incluso tras Shutdown().
VersionCSolo lectura.
StateCCreated | Activated | Attached | ShuttingDown | ShutDown. CADENA, no enum: ningún valor numérico cruza COM.
IsRunningL.T. entre Attach() y Shutdown().
IsLicensedL.T. si el JWT vale. Desde 3.4.0 NO hay excepción por modo: fuera del IDE también hace falta.

Opciones

Todas de lectura y escritura. Se ponen después de crear el objeto y antes de lanzar tareas.

PropiedadPor defectoQué controla
MaxWorkersmin(nucleos, 8)Procesos worker simultáneos. Rango 1-64. Regla practica: núcleos físicos.
DefaultTimeoutSeconds30Plazo de cada tarea. Rango 1-86.400. Al vencer, la tarea acaba TimedOut y el worker muere.
WorkerProgId"VisualFoxpro.Application.9"Que proceso hace de worker: VFP 9, VFP Advanced o tu propio EXE registrado. Se valida el FORMATO, no la existencia: para saber si sirve, ProbeWorker().
KeepCompletedTasks200Cuantas tareas TERMINADAS conserva el registro antes de purgar las más viejas. Importa en lotes grandes.
MaxTasksPerWorker1Cuantas tareas sirve un worker antes de reciclarse. 1 = un proceso por tarea, aislamiento total. 0 = sin límite.
WorkerInitScript""Guion que corre UNA VEZ POR WORKER, antes de su primera tarea. Aquí va el entorno.
WorkerResetScript""Guion que corre ENTRE TAREAS de un worker reutilizado. Aquí se limpia.
WorkerInitFailurePolicy"abort"Que pasa si el guion de inicio da un error: "abort" lo tumba, "tolerate" deja que su propio ON ERROR mande y siga.
PumpMaxItems8Cuanto atiende cada Pump().
PumpMaxMilliseconds20Presupuesto de tiempo de cada Pump().
RequestTimeoutSeconds15Cuanto espera un worker bloqueado en Call() antes de recibir Timeout. Es lo que impide que un host que no bombea deje colgado a un worker.
SecurityMode"Safe""Safe" es el unico modo real.
AuditLevel"basic"off | basic | full.

Tareas

MiembroDevuelveNotas
RunScript(cScript, cTaskName)CCrea, encola y devuelve el id de la tarea. LA VÍA NORMAL.
NewTask(cScript, cTaskName)FoxTaskHandleCrea SIN encolar, para configurarla antes (por ejemplo, con SetTaskTimeout).
Start(cTaskId)LEncola una tarea creada. .F. si no existe o ya se lanzo.
GetState(cTaskId)CCreated | Queued | Running | Completed | Failed | Cancelled | TimedOut, y NotFound para un id desconocido o ya purgado.
GetResult(cTaskId)valorEl RETURN del guion. .NULL. mientras no haya terminado bien; cadena vacía para un id desconocido.
GetError(cTaskId)CCadena vacia si no ha fallado; "Task not found." para un id desconocido.
IsCompleted(cTaskId)L.T. en cualquier estado terminal Y TAMBIÉN para un id que no existe -- a propósito: si no, el bucle canonico giraria para siempre cuando la purga se llevara la tarea.
GetProgress(cTaskId)N0-100. Funciona aunque nadie bombee.
GetProgressMessage(cTaskId)CEl ultimo mensaje, o cadena vacía.
PollCompleted()CLos ids terminados DESDE LA ULTIMA LLAMADA, separados por ";". Una llamada COM por vuelta en vez de N. LEERLO LO VACIA.
SetTaskTimeout(cTaskId, nSegundos)LPlazo de UNA tarea. Solo antes de que arranque: NewTask + esto + Start. .F. si ya arrancó o no existe.
Forget(cTaskId)LOlvida una tarea TERMINADA y libera su resultado. .F. si no existe o sigue viva.

Cancelación

MiembroDevuelveNotas
Cancel(cTaskId)LCOOPERATIVA: levanta la bandera que el guion lee con runtime.IsCancelled() y desbloquea al instante cualquier Call en vuelo. .F. si no hay nada que parar.
CancelAll()NCuantas cancelo.
Kill(cTaskId)LDURA: mata el proceso por PID. Para el guion que no mira la bandera es la unica via. Acaba Cancelled con "Killed by host."

Bombeo

MiembroDevuelveNotas
Pump(nMaxItems, nMaxMs)NAtiende primero las peticiones sincronas -- detrás de cada una hay un worker BLOQUEADO -- y después las notificaciones. Los dos parámetros a 0 o ausentes = usar las opciones.
Tres reglas de Pump() que hay que conocer: sólo se puede llamar desde el hilo del Attach (desde otro lanza un error controlado); no es reentrante (si se llama mientras ya se está bombeando devuelve 0 sin hacer nada y sin lanzar); y después de Shutdown() devuelve 0, sin error.

Eventos

MiembroDevuelveNotas
SetEventSink(oSink)LEl runtime avisa DESDE DENTRO DE Pump(), en tu hilo. Devuelve .F. SIN LANZAR si al objeto le faltan los métodos obligatorios.
ClearEventSink()—Soltarlo en el Destroy del formulario: mientras el runtime lo tenga, el formulario sigue vivo.

El sink debe tener OnProgress(tcTaskId, tnPercent, tcMessage) y OnLog(tcTaskId, tcLevel, tcMessage), obligatorios, y puede tener OnTaskCompleted(tcTaskId, tcState) y OnTaskFailed(tcTaskId, tcError), que se detectan si están. El sink es adicional: GetProgress() y GetProgressMessage() funcionan igual sin él.

Handlers: lo que un worker puede pedirle al host

MiembroDevuelveNotas
RegisterHandler(cName, oTarget, cMethodName)LcMethodName por defecto "Invoke". Devuelve .F. SIN LANZAR si el nombre esta vacio, lleva espacios, pasa de 64 caracteres, ya esta registrado, es uno de los integrados, O SI EL OBJETO NO EXPONE ESE METODO -- esa ultima comprobacion hace que el error salga en el Init del formulario y no seis horas despues dentro de un worker.
UnregisterHandler(cName)L.F. si no estaba.
GetHandlerNames()CLos nombres separados por ";", alfabeticos. Es la superficie COMPLETA de lo que un worker puede pedir. Hay tres integrados siempre disponibles: echo, upper y add.

Pizarra compartida

Resuelve «el worker necesita la configuración del host» sin viaje de ida y vuelta ni pump.

MiembroDevuelveNotas
Publish(cName, uValue)—Publica un valor que los workers leen con runtime.GetValue() SIN coste de cola ni de pump.
GetValue(cName)valor.NULL. si no existe.

Otros

MiembroDevuelveNotas
GetStats()CLa foto del motor como CADENA JSON plana. Devuelve una cadena y no un objeto por dos razones: un objeto COM cobra un viaje de ida y vuelta POR PROPIEDAD, y añadir un tipo nuevo romperia el contrato de seis tipos.
ProbeWorker(cProgId)CSirve ese ProgId como worker? Se contesta EJECUTANDOLO. Sin argumento, sondea el WorkerProgId vigente.
GetAuditPath()CDONDE IRIA el journal. No escribe nada.
SetCorrelation(cId, cName, cValue)LVocabulario CERRADO de siete nombres. .F. sin escribir para cualquier otro.
GetCorrelation(cId, cName)CCadena vacía si no esta puesto.

Lo que devuelve GetStats()

Una cadena JSON plana, que se parsea con jsonfox en una sola llamada. Hay dos familias de contadores y confundirlas cuesta caro:

  • createdEver, finishedEver, completedEver, failedEver, cancelledEver, timedOutEver: cuentan lo que ha PASADO desde que arrancó el runtime y no bajan NUNCA. Son los que sirven para pintar progreso.
  • total, created, completed, failed, cancelled, timedOut: son la FOTO del registro ahora mismo, y sí bajan: Forget() y la purga los descuentan. Sirven para saber qué hay vivo.
GetStats() con un lote de 30 tareas en marcha
{
  "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
}
La foto no es atómica: cada contador se lee bajo su propio cerrojo. Sirve para pintar una barra de progreso, no para cuadrar una suma. La verdad de una tarea concreta siempre es GetState(id).

FoxTaskHandle

Lo devuelve NewTask(). No guarda estado: cada propiedad delega en el registro del motor. Es azúcar: todo lo que hace se puede hacer con el taskId y los métodos del runtime.

MiembroTipoNotas
IdCEl taskId. LA VERDAD CANONICA es esta cadena: cruza COM sin coste de vida útil y es lo que se pega en un ticket.
NameCEl nombre que se le dio al crearla.
StateCLo mismo que GetState(Id).
ResultvalorLo mismo que GetResult(Id).
ErrorCLo mismo que GetError(Id).
IsCompletedLLo mismo que IsCompleted(Id).
OperationIdCExiste y SIEMPRE es cadena vacía: las operaciones no están implementadas.
Start()LEncola la tarea. Lo mismo que Start(Id).

WorkerBridge: la variable runtime dentro del guion

Es lo único que el worker ve del proceso principal, y su superficie está acotada a esa tarea. La columna «¿Bloquea?» es la que importa: sólo Call y TryCallJson esperan a que el host bombee, porque son las únicas que ejecutan código VFP en el proceso principal.

Miembro¿Bloquea?Notas
TaskId / TaskNamenoEl id y el nombre de ESTA tarea.
ElapsedSecondsnoSegundos desde que la tarea empezo.
Ping()no"pong".
Progress(nPercent, cMessage)noFire-and-forget. No necesita pump y no falla. El porcentaje se acota a 0-100.
Log(cLevel, cMessage)no"info" | "warn" | "error". Fire-and-forget.
IsCancelled()noLectura LOCAL, no toca el host. Pensada para llamarse en cada vuelta de un bucle.
GetValue(cName) / SetValue(cName, uValue)noPizarra compartida, local, sin pump.
Call(cName, arg1..arg8)SILlama a un handler del host. Síncrona: el worker espera a que el host bombee. LANZA error OLE si el handler falla.
TryCallJson(cName, arg1..arg8)SIComo Call pero NO LANZA NUNCA. Devuelve JSON: {"ok":true,"value":"42","error":"","errorCode":""}. El valor viaja SIEMPRE como cadena.

Patrones

Los dos bucles que hay que conocer:

  1. Lote normal: lanzar con RunScript(), guardar los ids, y girar un DO WHILE llamando a Pump() y DOEVENTS hasta que IsCompleted() diga que sí para todos.
  2. Lote grande (cientos o miles): preguntar tarea por tarea son N llamadas COM por vuelta. Se sube KeepCompletedTasks, se llama a PollCompleted() una vez por vuelta, se lee el resultado de los ids que entrega y se llama a Forget() en cada uno. El progreso sale de finishedEver, no de completed: con Forget() en cada vuelta, la foto se queda en cero y es correcto.

Los dos, con su código completo y las trampas de cada uno, están en Patrones.

Lo que ya no existe

Si llegas desde un buscador o desde código escrito para el motor clásico (WinFx.FoxCore), esto es lo que cambió. El motor clásico sigue existiendo y sigue soportado; no es el que se describe en esta página.

Motor clásicoEn FoxCore.Runtime 3.4.2
CREATEOBJECT("WinFx.FoxCore") / _screen.FoxCoreCREATEOBJECT("FoxCore.Runtime") -- es otro objeto COM, con otro ProgId y otro GUID. Los dos pueden estar registrados a la vez.
Run(cProc, oParams)RunScript(cGuion, cNombre). Ya no se pasa el NOMBRE de un procedimiento que tenga que existir en el worker: se pasa el GUION, como texto, y el worker lo ejecuta con EXECSCRIPT().
RunMultiple(@laTareas)Sin equivalente, y a propósito: se llama a RunScript() en un FOR y se guardan los ids. Un método que recibe un array por referencia no cruza COM de forma estable, y lo que devolvía era un objeto contenedor: un tipo más, y el contrato son seis.
WaitAll() / WaitAny()Sin equivalente, y es la diferencia de fondo: el bucle lo escribe el HOST, con Pump() y DOEVENTS. Un WaitFor bloqueante dentro de VFP es exactamente la UI congelada que este producto existe para evitar.
GetTasksSummary() / GetSystemStats()GetStats() para los números (queued, running, liveWorkers) y PollCompleted() para saber quien acaba de terminar. Un array de objetos Task por COM cuesta un viaje por propiedad y por tarea.
CancelTask(loTask)Cancel(cTaskId). Se identifica por id, no por objeto.
Activate(cClave)Activate(cJwt), con el mismo nombre pero otra clave: desde 3.4.0 no hay fichero, la licencia es un JWT que se pasa por código.
foxcore.licSin equivalente: desde 3.4.0 no hay fichero de licencia. Ver la página de Licencias.
PROCEDURE MiTarea(loParams, loTask)Dentro del guion no hay parámetros: hay una variable llamada runtime (WorkerBridge). loTask.Progress = 50 pasa a runtime.Progress(50, "...") y loTask.Result = x pasa a RETURN x.

Siguiente: Licencias ->