|
|
||
|---|---|---|
| .. | ||
| 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
Caption
- Applies to: Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup, BitBtn, SpeedButton
- Format: Quoted string
- Example:
Caption="Submit"
The display text for labels, buttons, check boxes, group boxes, radio buttons, panels, menu items, radio groups, and bitmap/speed buttons.
Text
- Applies to: Edit, ComboBox, Memo, MaskEdit
- Format: Quoted string
- Example:
Text="Hello world"
The editable text content. For Memo controls, use \n for line
breaks within the quoted string:
CTRL.SET 1 5 Text="Line one\nLine two\nLine three"
Items
- Applies to: ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook, Outline, Header
- Format: Quoted string, items separated by
\n - Example:
Items="Red\nGreen\nBlue"
Replaces the entire item list. The control is cleared before the new items are added.
Checked
- Applies to: CheckBox, RadioButton, MenuItem
- Format:
0(unchecked) or1(checked) - Example:
Checked=1
Enabled
- Applies to: All control types
- Format:
0(disabled) or1(enabled) - Example:
Enabled=0
Visible
- Applies to: All control types
- Format:
0(hidden) or1(visible) - Example:
Visible=0
MaxLength
- Applies to: Edit, MaskEdit
- Format: Integer (0 = no limit)
- Example:
MaxLength=50
Maximum number of characters the user can type.
ReadOnly
- Applies to: Edit, Memo
- Format:
0(editable) or1(read-only) - Example:
ReadOnly=1
ScrollBars
- Applies to: Memo
- Format: Integer 0-3
- Values:
0— ssNone (no scroll bars)1— ssHorizontal2— ssVertical3— ssBoth
- Example:
ScrollBars=2
ItemIndex
- Applies to: ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook
- Format: Integer (-1 = no selection)
- Example:
ItemIndex=2
TabOrder
- Applies to: All windowed controls (all types except Label and Image)
- Format: Integer
- Example:
TabOrder=3
Controls the keyboard tab navigation order within the form.
Stretch
- Applies to: Image
- Format:
0(off) or1(on) - Example:
Stretch=1
When enabled, the image is stretched to fill the control bounds.
Center
- Applies to: Image
- Format:
0(off) or1(on) - Example:
Center=1
When enabled, the image is centered within the control bounds.
Transparent
- Applies to: Image
- Format:
0(off) or1(on) - Example:
Transparent=1
When enabled, the image background is transparent.
Picture
- Applies to: Image
- Format: Quoted string (filename)
- Example:
Picture="images\logo.bmp"
BMP file to display. The path is resolved relative to the client's
BasePath setting. Subdirectories are allowed.
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.
BevelOuter
- Applies to: Panel
- Format: Integer 0-2
- Values:
0— bvNone1— bvLowered2— bvRaised
- Example:
BevelOuter=2
BevelInner
- Applies to: Panel
- Format: Integer 0-2
- Values:
0— bvNone1— bvLowered2— bvRaised
- Example:
BevelInner=1
BorderStyle (Panel)
- Applies to: Panel
- Format:
0(bsNone) or1(bsSingle) - Example:
BorderStyle=1
Kind
- Applies to: ScrollBar, BitBtn
- Format: Integer
- Values (ScrollBar):
0(sbHorizontal),1(sbVertical) - Values (BitBtn):
0(bkCustom),1(bkOK),2(bkCancel),3(bkHelp),4(bkYes),5(bkNo),6(bkClose),7(bkAbort),8(bkRetry),9(bkIgnore),10(bkAll) - Example:
Kind=1
Min
- Applies to: ScrollBar
- Format: Integer
- Example:
Min=0
Max
- Applies to: ScrollBar
- Format: Integer
- Example:
Max=100
Position
- Applies to: ScrollBar
- Format: Integer
- Example:
Position=50
LargeChange
- Applies to: ScrollBar
- Format: Integer
- Example:
LargeChange=10
The amount the position changes when the user clicks the scroll bar track.
SmallChange
- Applies to: ScrollBar
- Format: Integer
- Example:
SmallChange=1
The amount the position changes when the user clicks an arrow button.
FileName
- Applies to: MediaPlayer
- Format: Quoted string (file path)
- Example:
FileName="sounds\intro.wav"
Media file to open. The path is resolved relative to the client's
BasePath setting.
DeviceType
- Applies to: MediaPlayer
- Format: Quoted string
- Example:
DeviceType="dtWaveAudio"
MCI device type. Valid values: dtAutoSelect, dtAVIVideo,
dtCDAudio, dtDAT, dtDigitalVideo, dtMMMovie, dtOther,
dtOverlay, dtScanner, dtSequencer, dtVCR, dtVideodisc,
dtWaveAudio.
AutoOpen
- Applies to: MediaPlayer
- Format:
0(off) or1(on) - Example:
AutoOpen=1
When enabled, the media file is opened automatically when FileName is set.
Command
- Applies to: MediaPlayer
- Format: Quoted string
- Example:
Command="Play"
Pseudo-property that triggers a method call instead of setting a
value. Valid commands: Open, Play, Stop, Close, Pause,
Resume, Rewind, Next, Previous.
Parent
- Applies to: MenuItem
- Format: Integer (ctrlId of parent menu or menu item)
- Example:
Parent=1
Specifies the parent for a menu item. The parent can be a MainMenu, PopupMenu, or another MenuItem (for submenus).
Columns
- Applies to: RadioGroup
- Format: Integer
- Example:
Columns=2
Number of columns for the radio button layout.
ShortCut
- Applies to: MenuItem
- Format: Integer (Delphi ShortCut value)
- Example:
ShortCut=16467
Keyboard accelerator for the menu item. Uses Delphi's ShortCut encoding (virtual key + modifier flags).
PopupMenu
- Applies to: Any visual control
- Format: Integer (ctrlId of a PopupMenu)
- Example:
PopupMenu=2
Associates a PopupMenu with a control. When the user right-clicks the control, the popup menu is displayed.
Layout
- Applies to: BitBtn, SpeedButton
- Format: Integer 0-3
- Values:
0— blGlyphLeft1— blGlyphRight2— blGlyphTop3— blGlyphBottom
- Example:
Layout=2
Position of the glyph relative to the caption text.
NumGlyphs
- Applies to: BitBtn, SpeedButton
- Format: Integer (1-4)
- Example:
NumGlyphs=2
Number of glyph images in the bitmap (up, disabled, clicked, down).
GroupIndex
- Applies to: SpeedButton
- Format: Integer (0 = no group)
- Example:
GroupIndex=1
Speed buttons with the same non-zero GroupIndex act as a radio group.
Down
- Applies to: SpeedButton
- Format:
0(up) or1(down) - Example:
Down=1
Whether the speed button is pressed. Only meaningful when GroupIndex is non-zero.
AllowAllUp
- Applies to: SpeedButton
- Format:
0(off) or1(on) - Example:
AllowAllUp=1
When enabled, all speed buttons in a group can be unpressed.
EditMask
- Applies to: MaskEdit
- Format: Quoted string
- Example:
EditMask="(999) 000-0000;1;_"
Input mask string (mask;save literals;blank char).
OutlineStyle
- Applies to: Outline
- Format: Integer 0-6
- Values:
0— osText1— osPlusMinusText2— osPlusMinus3— osPictureText4— osPicturePlusMinusText5— osTreeText6— osTreePictureText
- Example:
OutlineStyle=5
Shape
- Applies to: Bevel
- Format: Integer 0-5
- Values:
0— bsBox1— bsFrame2— bsTopLine3— bsBottomLine4— bsLeftLine5— bsRightLine
- Example:
Shape=2
Style (Bevel)
- Applies to: Bevel
- Format:
0(bsLowered) or1(bsRaised) - Example:
Style=1
ColCount
- Applies to: StringGrid
- Format: Integer (default 5)
- Example:
ColCount=10
Number of columns in the grid.
RowCount
- Applies to: StringGrid
- Format: Integer (default 5)
- Example:
RowCount=20
Number of rows in the grid.
FixedCols
- Applies to: StringGrid
- Format: Integer (default 1)
- Example:
FixedCols=1
Number of non-scrollable columns on the left.
FixedRows
- Applies to: StringGrid
- Format: Integer (default 1)
- Example:
FixedRows=1
Number of non-scrollable rows at the top.
DefaultColWidth
- Applies to: StringGrid
- Format: Integer (pixels)
- Example:
DefaultColWidth=80
DefaultRowHeight
- Applies to: StringGrid
- Format: Integer (pixels)
- Example:
DefaultRowHeight=20
Options (StringGrid)
- Applies to: StringGrid
- Format: Integer (bitmask)
- Example:
Options=1549
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
- Applies to: StringGrid
- Format: Quoted string (tab-delimited columns,
\n-delimited rows) - Example:
Cells="Name\tAge\nAlice\t30\nBob\t25"
Bulk-loads all cell data. Columns are separated by tab characters, rows by newlines. Row 0 is the first row (typically fixed header).
Cell
- Applies to: StringGrid
- Format: Quoted string (
col,row,value) - Example:
Cell="1,2,Hello"
Sets a single cell value. Column and row are zero-based indices.
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) |