Vega — Formulare aus LFM-Dateien
Eine Oberfläche muss nicht im Quelltext stehen. Das LFM-Format beschreibt sie als Datei; das Programm daneben enthält nur noch die Handler und drei Zeilen zum Laden. Damit lässt sich das Fenster ändern, ohne den Quelltext anzufassen — und ohne neu zu übersetzen.
Die Formatreferenz steht in LFM-Formularformat; dieser Guide zeigt den Arbeitsablauf.
→ Vega-Guides · Komponenten · LFM-Referenz
Die Datei
lfm 1
object Form1: TForm
Width = 360
Height = 220
Caption = 'Aus einer Datei geladen'
object Kopf: TPanel
Left = 0
Top = 0
Width = 360
Height = 40
Align = alTop
object Titel: TLabel
Left = 12
Top = 12
Width = 320
Height = 18
Caption = 'Dieser Aufbau steht in guide.lfm'
end
end
object Feld: TEdit
Left = 16
Top = 60
Width = 220
Height = 26
Text = 'Text aendern'
OnChange = FeldGeaendert
end
object Stand: TLabel
Left = 16
Top = 100
Width = 320
Height = 18
Caption = 'bereit'
end
object BtnEnde: TButton
Left = 220
Top = 150
Width = 120
Height = 28
Caption = 'Beenden'
OnClick = EndeKlick
end
end
object Name: TKlasse…end— verschachtelt wie der Steuerelementbaum.- Eigenschaften stehen als
Name = Wert; Zeichenketten in einfachen Anführungszeichen. Align = alTopbenutzt die Delphi-Schreibweise der Ausrichtung.- Handler stehen ohne Anführungszeichen:
OnClick = EndeKlick. Mit Anführungszeichen meldet der Laderrechts von einem Ereignis steht ein Name ohne Anfuehrungszeichen— nachgemessen mit lyxc 1.1.3I.
Das Programm
unit Main;
import std.io;
import Vega.Types;
import Vega.Events;
import Vega.Canvas;
import Vega.Theme;
import Vega.Control;
import Vega.Font;
import Vega.Strings;
import Vega.Lists;
import Vega.Style;
import Vega.StdCtrls;
import Vega.Edits;
import Vega.Layout;
import Vega.Backend;
import Vega.BackendAuto;
import Vega.Forms;
import Vega.LfmValue;
import Vega.LfmParse;
import Vega.LfmClasses;
import Vega.LfmLoad;
var g_app: TApplication;
var g_form: TForm;
var g_stand: TLabel;
fn FeldGeaendert(sender: TControl): void {
var e: TEdit := sender as TEdit;
var t: pchar := e.GetText();
g_stand.SetCaption(t);
}
fn EndeKlick(sender: TControl): void {
g_app.Terminate();
}
fn main(): int64 {
g_app := new TApplication(NewPlatformBackend());
if (!g_app.Ready) { PrintLn("Kein Fenstersystem erreichbar."); return 1; }
// Ohne Eintragung ist ein Handlername in der Datei nur eine Zeichenkette.
RegisterNotify("FeldGeaendert", FeldGeaendert);
RegisterNotify("EndeKlick", EndeKlick);
var ld: TLfmLoader := new TLfmLoader();
var res: TLfmResult := ld.LoadFile("guide.lfm", null);
if (!res.Ok()) {
PrintLn("Formular mit Fehlern:");
var i: int64 := 0;
while (i < res.ErrorCount()) {
var e: pchar := res.ErrorAt(i);
PrintLn(e);
i := i + 1;
}
}
if (res.Root == null) { PrintLn("Kein Formular geladen."); return 1; }
g_form := res.Root as TForm;
// Controls ueber ihren Namen aus der Datei holen
var sc: TControl := g_form.FindTyped("Stand", "TLabel");
if (sc != null) { g_stand := sc as TLabel; }
if (!g_app.ShowForm(g_form)) { return 2; }
g_app.Run();
g_app.Shutdown();
return 0;
}
Übersetzen: lyxc lfm.lyx -I . -I platform/linux -o lfm. Nachgemessen mit lyxc 1.1.3I, übersetzt fehlerfrei; die Datei wird zur Laufzeit relativ zum Arbeitsverzeichnis gelesen.
Die vier Schritte
- Handler eintragen.
RegisterNotify(„EndeKlick“, EndeKlick)verbindet den Namen aus der Datei mit der Funktion. Ohne Eintragung ist der Name nur Text — der Lader meldet das mit Zeilennummer, statt den Knopf später stumm zu lassen. Für die anderen Ereignisarten:RegisterMouse,RegisterKey,RegisterChar,RegisterWheel,RegisterPaint. - Laden.
ld.LoadFile(pfad, null)liefert einTLfmResult. - Fehler ansehen.
res.Ok(),res.ErrorCount(),res.ErrorAt(i). Vorgabe ist nachsichtig: Das Fenster geht trotz Fehlern auf, damit man sieht, wo man steht.ld.Strict := truebricht stattdessen ab. - Controls holen.
g_form.FindTyped(„Stand“, „TLabel“)sucht über den Namen aus der Datei und prüft dabei die Klasse;nullheißt „nicht gefunden„.
Zurückschreiben
LfmSave(wurzel, pfad) schreibt einen bestehenden Steuerelementbaum als LFM-Datei — der Weg, um eine im Code aufgebaute Oberfläche in eine Datei zu überführen und ab da dort zu pflegen.
Wann sich LFM lohnt
- Die Oberfläche ändert sich öfter als die Logik.
- Mehrere Fenster teilen sich Muster, die man in Dateien nebeneinanderlegen kann.
- Jemand ohne Lyx-Kenntnisse soll Beschriftungen oder Positionen anpassen.
Dagegen: Für ein Fenster mit drei Knöpfen ist der Code kürzer als Datei plus Ladecode.
LFM und @flight_crit
Im Regelzyklus einer sicherheitskritischen Anwendung hat das Laden eines Formulars nichts zu suchen. Ein LoadFile liest zur Laufzeit eine Datei, legt dabei Speicher an, baut einen Objektbaum auf und ruft am Ende Handler über Zeiger, deren Ziel erst die Datei bestimmt. Jeder dieser vier Punkte widerspricht dem, was @flight_crit zusagt: eine Laufzeit, die sich vorab nachweisen lässt.
Der Compiler weist die offensichtlichen Fälle ab (gemessen mit lyxc 1.1.3I):
@flight_crit
fn MitHeap(): int64 { var p: int64 := alloc(64); free(p, 64); return 0; }
sema error (line 8): Speicheranforderung unter @flight_crit nicht erlaubt 'alloc'
sema error: new ist unter @flight_crit nicht erlaubt — der Speicher muss ausserhalb angelegt und hereingereicht werden
sema error: lokales Feld unter @flight_crit nicht erlaubt (es entsteht per mmap, nicht auf dem Stapel)
Die Prüfung reicht nur bis zum eigenen Rumpf (#1701). Ruft eine@flight_crit-Funktion einen Helfer, der seinerseits alloziert, meldet der Compiler nichts — die Allokation ist dieselbe, sie wird nur nicht mehr gesehen. Ebenso geht ein Aufruf über einen Funktionszeiger durch, obwohl sein Ziel zur Übersetzungszeit nicht feststeht.
EinLfmLoader.LoadFilein einer@flight_crit-Funktion würde also nicht abgewiesen werden — er wäre trotzdem falsch.
Die brauchbare Aufteilung ist dieselbe wie bei jeder anderen Ressource:
| Phase | Was dort erlaubt ist |
|---|---|
| Initialisierung | Formular laden, Speicher anlegen, Handler eintragen, Fenster öffnen — hier gibt es kein WCET-Budget |
Regelzyklus (@flight_crit) | Nur rechnen und auf vorhandene Puffer schreiben; kein new, kein alloc, kein Dateizugriff, keine indirekten Aufrufe |
| Herunterfahren | Freigeben, speichern |
Wer eine Oberfläche über einer sicherheitskritischen Regelung baut, trennt beide sauber: Die Regelung läuft in ihrem eigenen Rahmen mit vorab angelegten Puffern, die Anzeige liest daraus und wird von der Ereignisschleife bedient. Vega selbst ist nicht für DAL-A/B-Code gedacht — es alloziert, zeichnet in Puffer und ruft Handler indirekt; das ist für eine Oberfläche genau richtig und für den Regelzyklus genau falsch.
→ DO-178C-Guide · Attribute & Pragmas
Fallstricke
- Handlername nicht eingetragen — der Lader meldet es; ohne Blick auf
resbleibt das Element stumm. - Anführungszeichen um den Handlernamen — Syntaxfehler mit Zeilennummer (siehe oben).
FindTypedliefertnull— Name oder Klasse stimmen nicht; beide stehen in der Datei.- Relativer Pfad. Die Datei wird zur Laufzeit gelesen; das Programm muss aus dem passenden Verzeichnis gestartet werden.
Letzte Aktualisierung: 2026-08-19 — Abschnitt „LFM und @flight_crit“ ergänzt (Reichweite der Prüfung mit lyxc 1.1.3I nachgemessen, #1701); Ablauf gegen Vega/LfmLoad.lyx und Vega/LfmClasses.lyx erhoben; Datei und Programm mit lyxc 1.1.3I übersetzt und das Laden nachgemessen.
