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: TKlasseend — verschachtelt wie der Steuerelementbaum.
  • Eigenschaften stehen als Name = Wert; Zeichenketten in einfachen Anführungszeichen.
  • Align = alTop benutzt die Delphi-Schreibweise der Ausrichtung.
  • Handler stehen ohne Anführungszeichen: OnClick = EndeKlick. Mit Anführungszeichen meldet der Lader rechts 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

  1. 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.
  2. Laden. ld.LoadFile(pfad, null) liefert ein TLfmResult.
  3. 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 := true bricht stattdessen ab.
  4. Controls holen. g_form.FindTyped(„Stand“, „TLabel“) sucht über den Namen aus der Datei und prüft dabei die Klasse; null heiß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.

Ein LfmLoader.LoadFile in 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 res bleibt das Element stumm.
  • Anführungszeichen um den Handlernamen — Syntaxfehler mit Zeilennummer (siehe oben).
  • FindTyped liefert null — 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.