Replaces the flat per-property layout in protocol.md and README.md with per-control subsections, each listing its supported properties. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| dfm2form.c | ||
| formcli.pas | ||
| formsrv.c | ||
| formsrv.h | ||
| makefile | ||
| protocol.md | ||
| README.md | ||
Remote Forms System
A remote GUI system for Windows 3.1. Forms are designed visually in Delphi 1.0's IDE, converted to a text protocol by a Linux tool, and served to a Delphi 1.0 client over serial. The client creates native Windows 3.1 controls and sends user events back to the server.
Architecture
Linux Serial Windows 3.1
+-----------------+ +----------------+ +------------------+
| Delphi IDE | | | | |
| design → .DFM | | Transport | | TFormClient |
| | | | (newline- | | creates native |
| dfm2form | | delimited) | | controls from |
| | | | | | commands, sends |
| .form file | | Server -----> |---->| events back |
| | | | commands | | |
| FormServerT | | | | |
| loads .form, | | <----- Client |<----| |
| sends commands | | events | | |
+-----------------+ +----------------+ +------------------+
Components
| File | Language | Platform | Purpose |
|---|---|---|---|
dfm2form.c |
C | Linux | Converts binary DFM to .form text |
formsrv.h |
C | Linux | Server library header |
formsrv.c |
C | Linux | Server library implementation |
formcli.pas |
Pascal | Win 3.1 | Client form engine (Delphi 1.0) |
protocol.md |
— | — | Protocol specification |
Building
make # builds dfm2form and formsrv.o
make clean # removes build artifacts
Requires GCC on Linux. The Delphi unit (formcli.pas) is compiled as
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. 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 <input.dfm> [output.form]
Examples:
dfm2form login.dfm # output to stdout
dfm2form login.dfm login.form # output to file
Output is a sequence of protocol commands:
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
Events below). Unknown control classes are skipped with a warning.
Server Library (C)
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
Implement FormTransportT to connect the server to your communication
channel:
typedef struct {
int (*readMessage)(char *buf, int32_t maxLen, void *ctx);
void (*writeMessage)(const char *buf, void *ctx);
void *ctx;
} FormTransportT;
readMessage— Read one complete message intobuf. Return the number of bytes read, or 0 if no message is available. Must not block.writeMessage— Send a null-terminated message string. The transport adds framing (e.g., CR+LF for serial).ctx— Opaque pointer passed through to both callbacks.
API
// Create/destroy
FormServerT *formServerCreate(FormTransportT *transport);
void formServerDestroy(FormServerT *server);
// 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 on the client.
void formServerDestroyForm(FormServerT *server, int32_t formId);
// Update a control property
void formServerSetProp(FormServerT *server, int32_t formId,
int32_t ctrlId, const char *prop,
const char *value);
// Bind/unbind opt-in events
void formServerBindEvent(FormServerT *server, int32_t formId,
int32_t ctrlId, const char *eventName);
void formServerUnbindEvent(FormServerT *server, int32_t formId,
int32_t ctrlId, const char *eventName);
// Set event callback
void formServerSetEventCallback(FormServerT *server,
EventCallbackT cb, void *userData);
// Poll for one incoming event. Returns true if an event was processed.
bool formServerPollEvent(FormServerT *server);
Event Callback
typedef void (*EventCallbackT)(int32_t formId, int32_t ctrlId,
const char *eventName, const char *data,
void *userData);
formId/ctrlId— Identify which control fired the event.eventName—"Click","Change","Select", etc.data— Event-specific data (may be empty). See the Events section below for formats.
Server Example
#include "formsrv.h"
void onEvent(int32_t formId, int32_t ctrlId, const char *eventName, const char *data, void *userData) {
printf("Event: form=%d ctrl=%d event=%s data=%s\n",
formId, ctrlId, eventName, data);
FormServerT *server = (FormServerT *)userData;
// React to a button click by updating a label
if (ctrlId == 5 && strcmp(eventName, "Click") == 0) {
formServerSetProp(server, formId, 1, "Caption", "\"Clicked!\"");
}
}
int main(void) {
FormTransportT transport = { myReadFn, myWriteFn, myCtx };
FormServerT *server = formServerCreate(&transport);
formServerSetEventCallback(server, onEvent, server);
int32_t formId = formServerSendForm(server, "login.form");
// Main loop
while (running) {
formServerPollEvent(server);
}
formServerDestroy(server);
return 0;
}
Client Engine (Delphi 1.0)
The client receives protocol commands, creates native Windows 3.1 controls, and sends user events back through the transport.
Transport Interface
Subclass TFormTransport to provide your communication channel:
TFormTransport = class(TObject)
public
function ReadMessage(Buf: PChar; BufSize: Integer): Integer; virtual; abstract;
procedure WriteMessage(Buf: PChar; Len: Integer); virtual; abstract;
end;
ReadMessage— Read one complete message intoBuf. Return the number of bytes read, or 0 if no message is available.WriteMessage— SendLenbytes fromBufas a complete message.
PChar-based (not short strings) to handle messages up to 4096 bytes.
Serial Transport Example
TSerialTransport = class(TFormTransport)
private
FComm: TKPComm;
FRecvBuf: array[0..4095] of Char;
FRecvLen: Integer;
public
function ReadMessage(Buf: PChar; BufSize: Integer): Integer; override;
procedure WriteMessage(Buf: PChar; Len: Integer); override;
end;
function TSerialTransport.ReadMessage(Buf: PChar; BufSize: Integer): Integer;
var
S: string;
I: Integer;
Len: Integer;
begin
Result := 0;
{ Accumulate serial data }
S := FComm.Input;
if Length(S) > 0 then
begin
Move(S[1], FRecvBuf[FRecvLen], Length(S));
Inc(FRecvLen, Length(S));
end;
{ Scan for newline }
for I := 0 to FRecvLen - 1 do
begin
if FRecvBuf[I] = #10 then
begin
Len := I;
if (Len > 0) and (FRecvBuf[Len - 1] = #13) then
Dec(Len);
if Len > BufSize - 1 then
Len := BufSize - 1;
Move(FRecvBuf, Buf^, Len);
Buf[Len] := #0;
{ Shift remainder }
Move(FRecvBuf[I + 1], FRecvBuf, FRecvLen - I - 1);
Dec(FRecvLen, I + 1);
Result := Len;
Exit;
end;
end;
end;
procedure TSerialTransport.WriteMessage(Buf: PChar; Len: Integer);
var
Msg: string;
begin
Msg := StrPas(Buf) + #13#10;
FComm.Output := Msg;
end;
Using TFormClient
var
Transport: TSerialTransport;
Client: TFormClient;
{ Setup }
Transport := TSerialTransport.Create;
Transport.FComm := KPComm1; { your TKPComm instance }
Client := TFormClient.Create(Transport);
{ Main loop (PeekMessage style) }
while not Terminated do
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
Client.ProcessMessages; { pumps incoming commands }
Yield;
end;
{ Cleanup }
Client.Free;
Transport.Free;
ProcessMessages calls ReadMessage in a loop until no more
messages are available, dispatching each command as it arrives.
Supported Controls
| Type | Delphi Class | Description |
|---|---|---|
Label |
TLabel | Static text label |
Edit |
TEdit | Single-line text input |
Button |
TButton | Push button |
CheckBox |
TCheckBox | Check box with label |
ListBox |
TListBox | Scrollable list of items |
ComboBox |
TComboBox | Drop-down list with text input |
Memo |
TMemo | Multi-line text input |
Image |
TImage | Bitmap image display (BMP only) |
GroupBox |
TGroupBox | Cosmetic grouping frame |
RadioButton |
TRadioButton | Radio button (one group/form) |
Panel |
TPanel | Cosmetic container panel |
ScrollBar |
TScrollBar | Horizontal or vertical scrollbar |
MediaPlayer |
TMediaPlayer | MCI media player control |
MainMenu |
TMainMenu | Form main menu bar |
PopupMenu |
TPopupMenu | Context (right-click) menu |
MenuItem |
TMenuItem | Menu item (child of menu) |
RadioGroup |
TRadioGroup | Grouped radio buttons |
BitBtn |
TBitBtn | Button with bitmap glyph |
SpeedButton |
TSpeedButton | Flat toolbar-style button |
TabSet |
TTabSet | Tab strip (no pages) |
Notebook |
TNotebook | Multi-page container |
TabbedNotebook |
TTabbedNotebook | Tabbed multi-page container |
MaskEdit |
TMaskEdit | Masked text input |
Outline |
TOutline | Hierarchical tree list |
Bevel |
TBevel | Cosmetic beveled line/box |
Header |
THeader | Column header bar |
ScrollBox |
TScrollBox | Scrollable container |
StringGrid |
TStringGrid | Editable string grid |
Creating Controls
Controls are created with CTRL.CREATE, specifying position, size,
and optional inline properties:
CTRL.CREATE <formId> <ctrlId> <type> <left> <top> <width> <height> [Key="val" ...]
Example:
CTRL.CREATE 1 3 Edit 120 50 200 21 Text="hello" MaxLength=32 TabOrder=1
Control IDs are positive integers assigned by the server. They must be unique within a form.
Updating Controls
Properties can be changed at any time after creation with CTRL.SET:
CTRL.SET <formId> <ctrlId> Key="val" [Key="val" ...]
Example:
CTRL.SET 1 3 Text="world" Enabled=0
Properties
Common Properties
These properties apply to all or most controls:
| Property | Format | Description |
|---|---|---|
| Enabled | 0 or 1 |
Disable or enable the control |
| Visible | 0 or 1 |
Hide or show the control |
| TabOrder | Integer | Keyboard tab order within the form (all windowed controls except Label and Image) |
| PopupMenu | Integer (ctrlId of PopupMenu) | Associates a right-click context menu with the control |
Label, Button, GroupBox
| Property | Format |
|---|---|
| Caption | Quoted string |
The display text for the control.
Example: Caption="Submit"
Edit
| Property | Format | Description |
|---|---|---|
| Text | Quoted string | Editable text content |
| MaxLength | Integer | Max characters (0 = no limit) |
| ReadOnly | 0 or 1 |
Prevent user editing when enabled |
Example: Text="Hello world" MaxLength=50
CheckBox, RadioButton
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Display text |
| Checked | 0 or 1 |
Checked or unchecked |
Example: Caption="Accept terms" Checked=1
ListBox
| Property | Format | Description |
|---|---|---|
| Items | Quoted string (\n-delimited) |
Item list |
| ItemIndex | Integer (-1 = none) | Selected item |
Replaces the entire item list. The control is cleared before the new items are added.
Example: Items="Red\nGreen\nBlue" ItemIndex=0
ComboBox
| Property | Format | Description |
|---|---|---|
| Text | Quoted string | Editable text |
| Items | Quoted string (\n-delimited) |
Drop-down items |
| ItemIndex | Integer (-1 = none) | Selected item |
Example: Items="Red\nGreen\nBlue" ItemIndex=1
Memo
| Property | Format | Description |
|---|---|---|
| Text | Quoted string (\n for line breaks) |
Text content |
| ReadOnly | 0 or 1 |
Prevent editing |
| ScrollBars | Integer 0-3 | Scroll bar style |
ScrollBars values:
0— ssNone (no scroll bars)1— ssHorizontal2— ssVertical3— ssBoth
Example:
CTRL.SET 1 5 Text="Line one\nLine two\nLine three" ScrollBars=2
Image
| Property | Format | Description |
|---|---|---|
| Picture | Quoted string (filename) | BMP file to display |
| Stretch | 0 or 1 |
Stretch to fill bounds |
| Center | 0 or 1 |
Center within bounds |
| Transparent | 0 or 1 |
Transparent background |
Picture path is resolved relative to the client's BasePath setting.
Subdirectories are allowed. Only BMP files are supported.
Example: Picture="images\logo.bmp" Stretch=1
Note: dfm2form does not emit Picture from DFM files because
image data is stored as a binary blob in the DFM. Set Picture at
runtime via CTRL.SET or by manually editing the .form file.
Panel
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Display text |
| BevelOuter | Integer 0-2 | Outer bevel |
| BevelInner | Integer 0-2 | Inner bevel |
| BorderStyle | 0 or 1 |
Border |
Bevel values: 0 (bvNone), 1 (bvLowered), 2 (bvRaised).
BorderStyle values: 0 (bsNone), 1 (bsSingle).
Example: BevelOuter=2 BevelInner=1 BorderStyle=0
ScrollBar
| Property | Format | Description |
|---|---|---|
| Kind | Integer | 0 (sbHorizontal), 1 (sbVertical) |
| Min | Integer | Minimum value |
| Max | Integer | Maximum value |
| Position | Integer | Current position |
| LargeChange | Integer | Change on track click |
| SmallChange | Integer | Change on arrow click |
Example: Kind=1 Min=0 Max=100 Position=50 LargeChange=10 SmallChange=1
MediaPlayer
| Property | Format | Description |
|---|---|---|
| FileName | Quoted string | Media file path |
| DeviceType | Quoted string | MCI device type |
| AutoOpen | 0 or 1 |
Open file automatically |
| Command | Quoted string | Pseudo-property (triggers method) |
FileName path is resolved relative to the client's BasePath setting.
DeviceType values: dtAutoSelect, dtAVIVideo, dtCDAudio, dtDAT,
dtDigitalVideo, dtMMMovie, dtOther, dtOverlay, dtScanner,
dtSequencer, dtVCR, dtVideodisc, dtWaveAudio.
Command triggers a method call instead of setting a value. Valid
commands: Open, Play, Stop, Close, Pause, Resume,
Rewind, Next, Previous.
Example: FileName="sounds\intro.wav" DeviceType="dtWaveAudio" AutoOpen=1
MainMenu, PopupMenu
No type-specific properties. One MainMenu per form (auto-attached).
PopupMenu is associated with any control via the common PopupMenu
property.
MenuItem
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Menu item text |
| Parent | Integer | ctrlId of parent menu/item |
| Checked | 0 or 1 |
Check mark |
| ShortCut | Integer | Delphi ShortCut value |
ShortCut uses Delphi's encoding (virtual key + modifier flags).
Example: Caption="&Open" Parent=2 ShortCut=16463
RadioGroup
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Group label |
| Items | Quoted string (\n-delimited) |
Radio button labels |
| ItemIndex | Integer (-1 = none) | Selected item |
| Columns | Integer | Column count |
Example: Caption="Color" Items="Red\nGreen\nBlue" Columns=2 ItemIndex=0
BitBtn
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Button text |
| Kind | Integer 0-10 | Predefined button kind |
| Layout | Integer 0-3 | Glyph position |
| NumGlyphs | Integer 1-4 | Glyph images in bitmap |
Kind values: 0 (bkCustom), 1 (bkOK), 2 (bkCancel), 3 (bkHelp),
4 (bkYes), 5 (bkNo), 6 (bkClose), 7 (bkAbort), 8 (bkRetry),
9 (bkIgnore), 10 (bkAll).
Layout values: 0 (blGlyphLeft), 1 (blGlyphRight), 2 (blGlyphTop),
3 (blGlyphBottom).
NumGlyphs: number of glyph images (up, disabled, clicked, down).
Example: Caption="OK" Kind=1 Layout=0 NumGlyphs=2
SpeedButton
| Property | Format | Description |
|---|---|---|
| Caption | Quoted string | Button text |
| Layout | Integer 0-3 | Glyph position |
| NumGlyphs | Integer 1-4 | Glyph images in bitmap |
| GroupIndex | Integer | Radio group (0 = no group) |
| Down | 0 or 1 |
Pressed state |
| AllowAllUp | 0 or 1 |
Allow all buttons unpressed |
Layout values: 0 (blGlyphLeft), 1 (blGlyphRight), 2 (blGlyphTop),
3 (blGlyphBottom).
Speed buttons with the same non-zero GroupIndex act as a radio group. Down is only meaningful when GroupIndex is non-zero.
Example: GroupIndex=1 Down=1 AllowAllUp=0
TabSet, Notebook, TabbedNotebook
| Property | Format | Description |
|---|---|---|
| Items | Quoted string (\n-delimited) |
Tab/page list |
| ItemIndex | Integer (-1 = none) | Active tab |
Example: Items="General\nAdvanced\nAbout" ItemIndex=0
MaskEdit
| Property | Format | Description |
|---|---|---|
| Text | Quoted string | Editable text content |
| MaxLength | Integer | Max characters (0 = no limit) |
| EditMask | Quoted string | Input mask (mask;save;blank char) |
Example: EditMask="(999) 000-0000;1;_"
Outline
| Property | Format | Description |
|---|---|---|
| Items | Quoted string (\n-delimited) |
Tree items |
| OutlineStyle | Integer 0-6 | Display style |
OutlineStyle values:
0— osText1— osPlusMinusText2— osPlusMinus3— osPictureText4— osPicturePlusMinusText5— osTreeText6— osTreePictureText
Example: OutlineStyle=5
Bevel
| Property | Format | Description |
|---|---|---|
| Shape | Integer 0-5 | Bevel shape |
| Style | 0 or 1 |
Lowered/raised |
Shape values:
0— bsBox1— bsFrame2— bsTopLine3— bsBottomLine4— bsLeftLine5— bsRightLine
Style values: 0 (bsLowered), 1 (bsRaised).
Example: Shape=2 Style=1
Header
| Property | Format | Description |
|---|---|---|
| Items | Quoted string (\n-delimited) |
Section headers |
Example: Items="Name\nAge\nCity"
ScrollBox
No type-specific properties.
StringGrid
| Property | Format | Description |
|---|---|---|
| ColCount | Integer (default 5) | Number of columns |
| RowCount | Integer (default 5) | Number of rows |
| FixedCols | Integer (default 1) | Non-scrollable left cols |
| FixedRows | Integer (default 1) | Non-scrollable top rows |
| DefaultColWidth | Integer (pixels) | Default column width |
| DefaultRowHeight | Integer (pixels) | Default row height |
| Options | Integer (bitmask) | Grid options |
| Cells | Quoted string (tab-delimited cols, \n-delimited rows) |
Bulk-load all cells |
| Cell | Quoted string (col,row,value) |
Set a single cell |
Options bitmask of TGridOption values:
| Bit | Value | Option | Description |
|---|---|---|---|
| 0 | 0x0001 | goFixedVertLine | Vertical lines on fixed |
| 1 | 0x0002 | goFixedHorzLine | Horizontal lines on fixed |
| 2 | 0x0004 | goVertLine | Vertical lines on cells |
| 3 | 0x0008 | goHorzLine | Horizontal lines on cells |
| 4 | 0x0010 | goRangeSelect | Allow range selection |
| 5 | 0x0020 | goDrawFocusSelected | Draw focused cell selected |
| 6 | 0x0040 | goRowSizing | Allow row resizing |
| 7 | 0x0080 | goColSizing | Allow column resizing |
| 8 | 0x0100 | goRowMoving | Allow row moving |
| 9 | 0x0200 | goColMoving | Allow column moving |
| 10 | 0x0400 | goEditing | Allow in-place editing |
| 11 | 0x0800 | goTabs | Tab between cells |
| 12 | 0x1000 | goThumbTracking | Track scrollbar thumb |
Cells is a bulk-load property: columns are separated by tab characters, rows by newlines. Row 0 is the first row (typically fixed header). Cell sets a single cell value: column and row are zero-based indices.
Example: Cells="Name\tAge\nAlice\t30\nBob\t25"
Example: Cell="1,2,Hello"
BasePath
BasePath is a property on TFormClient (not a protocol property).
Set it before loading forms to specify where file-based properties
(Picture, FileName) resolve relative paths. Example:
Client.BasePath := 'C:\MYAPP';
A Picture value of "images\logo.bmp" then resolves to
C:\MYAPP\images\logo.bmp.
Events
Auto-wired Events
These events are automatically connected when a control is created.
No EVENT.BIND command is needed.
| Control Type | Event | Data Sent |
|---|---|---|
| Button | Click | (none) |
| CheckBox | Click | (none) |
| RadioButton | Click | (none) |
| Edit | Change | "new text" |
| Memo | Change | "new text" |
| ScrollBar | Change | <position> |
| ListBox | Select | <index> "selected text" |
| ComboBox | Select | <index> "selected text" |
| ComboBox | Change | "new text" |
| MenuItem | Click | (none) |
| RadioGroup | Click | <itemIndex> |
| BitBtn | Click | (none) |
| SpeedButton | Click | (none) |
| TabSet | Change | <tabIndex> |
| TabbedNotebook | Change | <pageIndex> |
| MaskEdit | Change | "new text" |
| StringGrid | SelectCell | <col> <row> |
Opt-in Events
These events require an explicit EVENT.BIND command before the
client will send them. Use EVENT.UNBIND to disconnect.
| Event | Data Sent | Notes |
|---|---|---|
| Click | (none) | Image, GroupBox, Panel only |
| DblClick | (none) | Double-click on any control |
| Notify | (none) | MediaPlayer playback notification |
| KeyDown | <vkCode> |
Windows virtual key code |
| KeyUp | <vkCode> |
Windows virtual key code |
| Enter | (none) | Control received focus |
| Exit | (none) | Control lost focus |
| MouseDown | <x> <y> <button> |
0=left, 1=right, 2=middle |
| MouseUp | <x> <y> <button> |
0=left, 1=right, 2=middle |
| MouseMove | <x> <y> 0 |
Coordinates relative to control |
| SetEditText | <col> <row> "text" |
StringGrid in-place edit |
Form Close Event
When the user clicks the form's close button, the client sends:
EVENT <formId> 0 Close
The form is not automatically destroyed. The server must send
FORM.DESTROY to close it, or FORM.HIDE to hide it, or ignore
the event to keep it open.
Event Message Format
All events from client to server follow this format:
EVENT <formId> <ctrlId> <eventName> [<data>]
Binding Example
EVENT.BIND 1 3 KeyDown (server sends)
EVENT 1 3 KeyDown 13 (client sends back when Enter pressed)
EVENT.UNBIND 1 3 KeyDown (server disconnects)
Menu Hierarchy Example
Menus are built using flat CTRL.CREATE commands with Parent properties
to establish the hierarchy:
CTRL.CREATE 1 1 MainMenu 0 0 0 0
CTRL.CREATE 1 2 MenuItem 0 0 0 0 Caption="&File" Parent=1
CTRL.CREATE 1 3 MenuItem 0 0 0 0 Caption="&Open" Parent=2 ShortCut=16463
CTRL.CREATE 1 4 MenuItem 0 0 0 0 Caption="&Save" Parent=2 ShortCut=16467
CTRL.CREATE 1 5 MenuItem 0 0 0 0 Caption="E&xit" Parent=2
CTRL.CREATE 1 6 MenuItem 0 0 0 0 Caption="&Help" Parent=1
CTRL.CREATE 1 7 MenuItem 0 0 0 0 Caption="&About" Parent=6
This creates a menu bar with File (Open, Save, Exit) and Help (About). MenuItem ctrl 2 is a child of MainMenu ctrl 1; ctrl 3-5 are children of the File item (ctrl 2); ctrl 7 is a child of the Help item (ctrl 6).
String Encoding
All strings in the protocol are double-quoted. The following escape sequences are recognized:
| Escape | Character |
|---|---|
\" |
Literal " |
\\ |
Literal \ |
\n |
Newline (LF) |
\r |
Carriage return |
\t |
Tab |
Transport Layer
The protocol is transport-agnostic. Both the C server and Delphi client communicate through abstract interfaces that deliver whole messages. The current implementation uses newline-delimited serial (CR+LF framing), but the transport can be replaced without changing any protocol or application code.
Requirements
ReadMessagemust be non-blocking (return 0 if no data).WriteMessagesends one complete message per call.- Transport handles framing — the protocol layer never sees delimiters.
Limitations
- RadioButton grouping: All RadioButtons on a form belong to one group. Use RadioGroup for multiple independent radio groups per form.
- Image format: Only BMP files are supported for the Picture property.
- dfm2form and Picture: The converter cannot extract image
filenames from DFM files (image data is stored as a binary blob).
Set Picture at runtime via
CTRL.SETor by manually editing the.formfile. - GroupBox and Panel: These are cosmetic-only containers. The protocol has no child containment — all controls are direct children of the form.
Limits
| Limit | Value |
|---|---|
| Max message length | 4096 bytes |
| 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) |