From 17394e4f5def37ec5fd0d19e8064225e0159961a Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Wed, 4 Mar 2026 19:14:36 -0600 Subject: [PATCH] Replace form store with stream-from-disk and dynamic form IDs Form IDs were baked into .form files at conversion time, preventing a form from being displayed more than once. dfm2form now writes a placeholder ID (0), and formServerSendForm streams the file directly from disk, assigning a unique ID on the fly via rewriteFormId. This eliminates formServerLoadFile, the in-memory form store, and the -i flag from dfm2form. Co-Authored-By: Claude Opus 4.6 --- forms/README.md | 53 ++++---- forms/dfm2form.c | 21 +-- forms/formsrv.c | 331 +++++++++++++++++++---------------------------- forms/formsrv.h | 15 +-- 4 files changed, 164 insertions(+), 256 deletions(-) diff --git a/forms/README.md b/forms/README.md index f725943..594ecc6 100644 --- a/forms/README.md +++ b/forms/README.md @@ -46,39 +46,37 @@ part of a Delphi 1.0 project on Windows. ## DFM Converter `dfm2form` reads Delphi 1.0 binary DFM files (TPF0 format) and outputs -`.form` files containing protocol commands. +`.form` files containing protocol commands. The form ID in the output +is a placeholder (`0`); the server assigns a dynamic ID when it streams +the file to the client. ``` -dfm2form [-i ] [output.form] +dfm2form [output.form] ``` -**Options:** - -- `-i ` — Set the form ID (default: 1). Each form needs a - unique ID when serving multiple forms. - **Examples:** ``` dfm2form login.dfm # output to stdout dfm2form login.dfm login.form # output to file -dfm2form -i 2 settings.dfm settings.form ``` **Output** is a sequence of protocol commands: ``` -FORM.CREATE 1 400 300 "Login" -CTRL.CREATE 1 1 Label 20 20 100 17 Caption="Username:" -CTRL.CREATE 1 2 Edit 120 18 200 21 Text="" MaxLength=32 TabOrder=0 -CTRL.CREATE 1 3 Label 20 52 100 17 Caption="Password:" -CTRL.CREATE 1 4 Edit 120 50 200 21 Text="" MaxLength=32 TabOrder=1 -CTRL.CREATE 1 5 Button 245 90 75 25 Caption="OK" TabOrder=2 -CTRL.CREATE 1 6 Button 160 90 75 25 Caption="Cancel" TabOrder=3 -EVENT.BIND 1 5 Enter -FORM.SHOW 1 +FORM.CREATE 0 400 300 "Login" +CTRL.CREATE 0 1 Label 20 20 100 17 Caption="Username:" +CTRL.CREATE 0 2 Edit 120 18 200 21 Text="" MaxLength=32 TabOrder=0 +CTRL.CREATE 0 3 Label 20 52 100 17 Caption="Password:" +CTRL.CREATE 0 4 Edit 120 50 200 21 Text="" MaxLength=32 TabOrder=1 +CTRL.CREATE 0 5 Button 245 90 75 25 Caption="OK" TabOrder=2 +CTRL.CREATE 0 6 Button 160 90 75 25 Caption="Cancel" TabOrder=3 +EVENT.BIND 0 5 Enter +FORM.SHOW 0 ``` +The placeholder `0` IDs are replaced at runtime by `formServerSendForm`. + The converter maps Delphi class names to protocol control types, extracts geometry and properties, and emits `EVENT.BIND` for any event handler assignments in the DFM that are not auto-wired (see @@ -86,9 +84,9 @@ Events below). Unknown control classes are skipped with a warning. ## Server Library (C) -The server loads `.form` files and sends their commands to a remote -client through a pluggable transport interface. It also receives -events from the client and dispatches them to a callback. +The server streams `.form` files from disk to a remote client through +a pluggable transport interface, assigning dynamic form IDs. It also +receives events from the client and dispatches them to a callback. ### Transport Interface @@ -117,17 +115,15 @@ typedef struct { FormServerT *formServerCreate(FormTransportT *transport); void formServerDestroy(FormServerT *server); -// Load a .form file. Returns the form ID, or -1 on error. -int32_t formServerLoadFile(FormServerT *server, const char *path); - -// Send all commands for a loaded form to the client. -void formServerSendForm(FormServerT *server, int32_t formId); +// Stream a .form file to the client, assigning a dynamic form ID. +// Returns the assigned form ID, or -1 on error. +int32_t formServerSendForm(FormServerT *server, const char *path); // Form visibility void formServerShowForm(FormServerT *server, int32_t formId); void formServerHideForm(FormServerT *server, int32_t formId); -// Destroy a form (sends FORM.DESTROY and removes from store) +// Destroy a form on the client. void formServerDestroyForm(FormServerT *server, int32_t formId); // Update a control property @@ -188,8 +184,7 @@ int main(void) formServerSetEventCallback(server, onEvent, server); - int32_t formId = formServerLoadFile(server, "login.form"); - formServerSendForm(server, formId); + int32_t formId = formServerSendForm(server, "login.form"); // Main loop while (running) { @@ -540,8 +535,6 @@ any protocol or application code. | Limit | Value | |---------------------------|-------| | Max message length | 4096 bytes | -| Max forms (server) | 64 | -| Max lines per .form file | 1024 | | Max controls per form | 256 | | Form ID range | 1-65535 (stored in high word of Tag) | | Control ID range | 1-65535 (stored in low word of Tag) | diff --git a/forms/dfm2form.c b/forms/dfm2form.c index 3f35b8c..4ad9028 100644 --- a/forms/dfm2form.c +++ b/forms/dfm2form.c @@ -1,6 +1,6 @@ // dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text // -// Usage: dfm2form [-i ] [output.form] +// Usage: dfm2form [output.form] // // Reads a binary DFM file, extracts the form and control definitions, // and outputs protocol commands (FORM.CREATE, CTRL.CREATE, EVENT.BIND, @@ -864,8 +864,7 @@ static void emitForm(FILE *out, int32_t formId, DfmFormT *form) static void usage(const char *progName) { - fprintf(stderr, "Usage: %s [-i ] [output.form]\n", progName); - fprintf(stderr, " -i Set form ID (default: 1)\n"); + fprintf(stderr, "Usage: %s [output.form]\n", progName); exit(1); } @@ -876,23 +875,13 @@ static void usage(const char *progName) int main(int argc, char *argv[]) { - int32_t formId = 1; - const char *inputPath = NULL; + const char *inputPath = NULL; const char *outputPath = NULL; // Parse arguments int32_t i = 1; while (i < argc) { - if (strcmp(argv[i], "-i") == 0) { - if (i + 1 >= argc) { - usage(argv[0]); - } - formId = atoi(argv[++i]); - if (formId <= 0) { - fprintf(stderr, "Error: form ID must be positive\n"); - exit(1); - } - } else if (argv[i][0] == '-') { + if (argv[i][0] == '-') { usage(argv[0]); } else if (inputPath == NULL) { inputPath = argv[i]; @@ -965,7 +954,7 @@ int main(int argc, char *argv[]) fout = stdout; } - emitForm(fout, formId, &form); + emitForm(fout, 0, &form); if (fout != stdout) { fclose(fout); diff --git a/forms/formsrv.c b/forms/formsrv.c index 2028876..aa0b1bb 100644 --- a/forms/formsrv.c +++ b/forms/formsrv.c @@ -4,6 +4,7 @@ #include "formsrv.h" +#include #include #include #include @@ -13,26 +14,17 @@ // Constants // --------------------------------------------------------------------------- -#define MAX_FORMS 64 -#define MAX_LINES 1024 #define MAX_MSG_LEN 4096 // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- -typedef struct { - int32_t formId; - char *lines[MAX_LINES]; - int32_t lineCount; -} FormDataT; - struct FormServerS { FormTransportT *transport; EventCallbackT eventCallback; void *eventUserData; - FormDataT forms[MAX_FORMS]; - int32_t formCount; + int32_t nextFormId; char msgBuf[MAX_MSG_LEN]; }; @@ -40,66 +32,16 @@ struct FormServerS { // Prototypes // --------------------------------------------------------------------------- -static FormDataT *findForm(FormServerT *server, int32_t formId); -static void freeFormData(FormDataT *fd); -static int32_t parseFormId(const char *line); -static void sendCommand(FormServerT *server, const char *fmt, ...); -static bool skipSpaces(const char **p); -static bool parseToken(const char **p, char *buf, int32_t bufSize); +static bool parseToken(const char **p, char *buf, int32_t bufSize); +static void rewriteFormId(char *line, int32_t lineSize, int32_t formId); +static void sendCommand(FormServerT *server, const char *fmt, ...); +static bool skipSpaces(const char **p); // --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- -static FormDataT *findForm(FormServerT *server, int32_t formId) -{ - for (int32_t i = 0; i < server->formCount; i++) { - if (server->forms[i].formId == formId) { - return &server->forms[i]; - } - } - return NULL; -} - - -static void freeFormData(FormDataT *fd) -{ - for (int32_t i = 0; i < fd->lineCount; i++) { - free(fd->lines[i]); - fd->lines[i] = NULL; - } - fd->lineCount = 0; - fd->formId = 0; -} - - -static int32_t parseFormId(const char *line) -{ - // Skip command prefix (e.g., "FORM.CREATE "), then read first integer - const char *p = line; - // Skip non-space command name - while (*p && *p != ' ') { - p++; - } - // Skip space - while (*p == ' ') { - p++; - } - // Parse integer - return (int32_t)atoi(p); -} - - -static bool skipSpaces(const char **p) -{ - while (**p == ' ' || **p == '\t') { - (*p)++; - } - return **p != '\0'; -} - - static bool parseToken(const char **p, char *buf, int32_t bufSize) { skipSpaces(p); @@ -152,7 +94,49 @@ static bool parseToken(const char **p, char *buf, int32_t bufSize) } -#include +static void rewriteFormId(char *line, int32_t lineSize, int32_t formId) +{ + // Protocol lines have the form: COMMAND + // Skip command prefix (non-space chars) + char *p = line; + while (*p && *p != ' ') { + p++; + } + // Skip spaces + while (*p == ' ') { + p++; + } + + // p now points at the placeholder ID token ("0") + // Find the end of this token + char *idStart = p; + while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') { + p++; + } + char *idEnd = p; + + // Format the new ID + char idBuf[16]; + int32_t idLen = snprintf(idBuf, sizeof(idBuf), "%d", formId); + + int32_t oldLen = (int32_t)(idEnd - idStart); + int32_t shift = idLen - oldLen; + + // Check that the rewritten line still fits + int32_t curLen = (int32_t)strlen(line); + if (curLen + shift >= lineSize - 1) { + return; + } + + // Shift the remainder of the line + if (shift != 0) { + memmove(idStart + idLen, idEnd, strlen(idEnd) + 1); + } + + // Write the new ID (no NUL — the memmove already placed the rest) + memcpy(idStart, idBuf, idLen); +} + static void sendCommand(FormServerT *server, const char *fmt, ...) { @@ -167,17 +151,34 @@ static void sendCommand(FormServerT *server, const char *fmt, ...) } +static bool skipSpaces(const char **p) +{ + while (**p == ' ' || **p == '\t') { + (*p)++; + } + return **p != '\0'; +} + + // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- +void formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, + const char *eventName) +{ + sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName); +} + + FormServerT *formServerCreate(FormTransportT *transport) { FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT)); if (server == NULL) { return NULL; } - server->transport = transport; + server->transport = transport; + server->nextFormId = 1; return server; } @@ -187,151 +188,19 @@ void formServerDestroy(FormServerT *server) if (server == NULL) { return; } - for (int32_t i = 0; i < server->formCount; i++) { - freeFormData(&server->forms[i]); - } free(server); } -int32_t formServerLoadFile(FormServerT *server, const char *path) -{ - FILE *f = fopen(path, "r"); - if (f == NULL) { - fprintf(stderr, "formsrv: cannot open '%s': %s\n", path, strerror(errno)); - return -1; - } - - if (server->formCount >= MAX_FORMS) { - fprintf(stderr, "formsrv: too many forms (max %d)\n", MAX_FORMS); - fclose(f); - return -1; - } - - FormDataT *fd = &server->forms[server->formCount]; - memset(fd, 0, sizeof(FormDataT)); - - char lineBuf[MAX_MSG_LEN]; - int32_t formId = -1; - - while (fgets(lineBuf, sizeof(lineBuf), f) != NULL) { - // Strip trailing newline - int32_t len = (int32_t)strlen(lineBuf); - while (len > 0 && (lineBuf[len - 1] == '\n' || lineBuf[len - 1] == '\r')) { - lineBuf[--len] = '\0'; - } - - // Skip empty lines - if (len == 0) { - continue; - } - - if (fd->lineCount >= MAX_LINES) { - fprintf(stderr, "formsrv: too many lines in '%s' (max %d)\n", path, MAX_LINES); - freeFormData(fd); - fclose(f); - return -1; - } - - fd->lines[fd->lineCount] = strdup(lineBuf); - if (fd->lines[fd->lineCount] == NULL) { - fprintf(stderr, "formsrv: out of memory\n"); - freeFormData(fd); - fclose(f); - return -1; - } - fd->lineCount++; - - // Extract form ID from first FORM.CREATE line - if (formId == -1 && strncmp(lineBuf, "FORM.CREATE ", 12) == 0) { - formId = parseFormId(lineBuf); - } - } - - fclose(f); - - if (formId <= 0) { - fprintf(stderr, "formsrv: no FORM.CREATE found in '%s'\n", path); - freeFormData(fd); - return -1; - } - - fd->formId = formId; - server->formCount++; - return formId; -} - - -void formServerSendForm(FormServerT *server, int32_t formId) -{ - FormDataT *fd = findForm(server, formId); - if (fd == NULL) { - return; - } - - for (int32_t i = 0; i < fd->lineCount; i++) { - server->transport->writeMessage(fd->lines[i], server->transport->ctx); - } -} - - -void formServerShowForm(FormServerT *server, int32_t formId) -{ - sendCommand(server, "FORM.SHOW %d", formId); -} - - -void formServerHideForm(FormServerT *server, int32_t formId) -{ - sendCommand(server, "FORM.HIDE %d", formId); -} - - void formServerDestroyForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.DESTROY %d", formId); - - // Remove from form store - for (int32_t i = 0; i < server->formCount; i++) { - if (server->forms[i].formId == formId) { - freeFormData(&server->forms[i]); - // Shift remaining forms down - for (int32_t j = i; j < server->formCount - 1; j++) { - server->forms[j] = server->forms[j + 1]; - } - server->formCount--; - break; - } - } } -void formServerSetProp(FormServerT *server, int32_t formId, int32_t ctrlId, - const char *prop, const char *value) +void formServerHideForm(FormServerT *server, int32_t formId) { - sendCommand(server, "CTRL.SET %d %d %s=%s", formId, ctrlId, prop, value); -} - - -void formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, - const char *eventName) -{ - sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName); -} - - -void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, - const char *eventName) -{ - sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName); -} - - -void formServerSetEventCallback(FormServerT *server, EventCallbackT cb, - void *userData) -{ - server->eventCallback = cb; - server->eventUserData = userData; + sendCommand(server, "FORM.HIDE %d", formId); } @@ -348,7 +217,7 @@ bool formServerPollEvent(FormServerT *server) // Parse: EVENT [] const char *p = server->msgBuf; - char token[256]; + char token[256]; if (!parseToken(&p, token, sizeof(token))) { return false; @@ -386,3 +255,63 @@ bool formServerPollEvent(FormServerT *server) return true; } + + +int32_t formServerSendForm(FormServerT *server, const char *path) +{ + FILE *f = fopen(path, "r"); + if (f == NULL) { + fprintf(stderr, "formsrv: cannot open '%s': %s\n", path, strerror(errno)); + return -1; + } + + int32_t formId = server->nextFormId++; + char lineBuf[MAX_MSG_LEN]; + + while (fgets(lineBuf, sizeof(lineBuf), f) != NULL) { + // Strip trailing newline + int32_t len = (int32_t)strlen(lineBuf); + while (len > 0 && (lineBuf[len - 1] == '\n' || lineBuf[len - 1] == '\r')) { + lineBuf[--len] = '\0'; + } + + // Skip empty lines + if (len == 0) { + continue; + } + + rewriteFormId(lineBuf, sizeof(lineBuf), formId); + server->transport->writeMessage(lineBuf, server->transport->ctx); + } + + fclose(f); + return formId; +} + + +void formServerSetEventCallback(FormServerT *server, EventCallbackT cb, + void *userData) +{ + server->eventCallback = cb; + server->eventUserData = userData; +} + + +void formServerSetProp(FormServerT *server, int32_t formId, int32_t ctrlId, + const char *prop, const char *value) +{ + sendCommand(server, "CTRL.SET %d %d %s=%s", formId, ctrlId, prop, value); +} + + +void formServerShowForm(FormServerT *server, int32_t formId) +{ + sendCommand(server, "FORM.SHOW %d", formId); +} + + +void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, + const char *eventName) +{ + sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName); +} diff --git a/forms/formsrv.h b/forms/formsrv.h index c0b8fab..54a2704 100644 --- a/forms/formsrv.h +++ b/forms/formsrv.h @@ -1,8 +1,8 @@ // formsrv.h - Remote forms server library // -// Loads .form files (protocol command sequences) and sends them to a -// remote client via a transport interface. Receives EVENT messages -// from the client and dispatches them to a callback. +// Streams .form files from disk to a remote client via a transport +// interface, assigning dynamic form IDs. Receives EVENT messages from +// the client and dispatches them to a callback. #ifndef FORMSRV_H #define FORMSRV_H @@ -48,12 +48,9 @@ typedef struct FormServerS FormServerT; FormServerT *formServerCreate(FormTransportT *transport); void formServerDestroy(FormServerT *server); -// Load a .form file into the server's form store. Returns the form ID -// parsed from the first FORM.CREATE line, or -1 on error. -int32_t formServerLoadFile(FormServerT *server, const char *path); - -// Send all commands for a loaded form to the client. -void formServerSendForm(FormServerT *server, int32_t formId); +// Stream a .form file to the client, assigning a dynamic form ID. +// Returns the assigned form ID, or -1 on error. +int32_t formServerSendForm(FormServerT *server, const char *path); // Send FORM.SHOW / FORM.HIDE / FORM.DESTROY commands. void formServerShowForm(FormServerT *server, int32_t formId);