====== Backends ======
Ein Backend ist die **einzige** Stelle mit Kontakt zum Betriebssystem. Es
erfüllt den Vertrag ''IVegaBackend'' aus ''Vega/Backend.lyx'' — 21
Methoden, mehr nicht.
===== Vorhandene Backends =====
^ Backend ^ Unit ^ Plattform ^ Stand ^
| ''TX11Backend'' | ''Vega/Backend/X11.lyx'' | Linux, X11 | vollständig |
| ''TWin32Backend'' | ''Vega/Backend/Win32.lyx'' | Windows | vollständig |
| ''TNullBackend'' | ''Vega/Backend/Null.lyx'' | keine | für Tests |
Das **Null-Backend** ist kein Notbehelf: es macht die 67 Linux-Testprogramme
möglich, die ohne Fenstersystem laufen. Ereignisse werden hineingereicht
(''PushEvent''), Fenstergrößen und Monitore sind frei einstellbar.
===== Auswahl nach Zielplattform =====
==== Was der Compiler hergibt ====
^ Symbol ^ ''--target=linux'' ^ ''--target=win64'' ^
| ''TARGET_OS'' | 1 | 3 |
Dazu ''OS_LINUX'' = 1, ''OS_MACOS'' = 2, ''OS_WINDOWS'' = 3.
Es gibt **keine bedingte Übersetzung** — kein ''--define'', kein ''@if'',
kein ''ifdef''.
==== Warum TARGET_OS allein nicht reicht ====
Die naheliegende Fabrikfunktion
pub fn NewPlatformBackend(): IVegaBackend {
if (TARGET_OS == OS_WINDOWS) { return new TWin32Backend() as IVegaBackend; }
return new TX11Backend() as IVegaBackend;
}
übersetzt fehlerfrei und ist trotzdem unbrauchbar. Beide Zweige werden
**gebunden**, und ''extern''-Deklarationen sind harte Abhängigkeiten, auch
wenn der Code sie nie erreicht:
$ ./programm # ELF, Win32-Backend im toten Zweig
error while loading shared libraries: kernel32.dll: cannot open shared object file
$ wine programm.exe # PE, X11-Backend im toten Zweig
wine: failed to open "programm.exe": c0000135
Beide starten **gar nicht erst**. Die Entscheidung muss also vor das
Binden.
==== Die Lösung: der Include-Pfad ====
Die Unit ''Vega.BackendAuto'' gibt es zweimal:
platform/linux/Vega/BackendAuto.lyx -> TX11Backend
platform/win64/Vega/BackendAuto.lyx -> TWin32Backend
Beide bieten dasselbe:
pub fn NewPlatformBackend(): IVegaBackend;
pub fn PlatformBackendName(): pchar; // "X11" bzw. "Win32"
pub fn PlatformBackendMatches(): bool; // prueft gegen TARGET_OS
lyxc app.lyx -I . -I platform/linux
lyxc app.lyx -I . -I platform/win64 --target=win64 --format=pe
Dasselbe Verfahren benutzt Lazarus mit seinen plattformabhängigen
Verzeichnissen. Eine dritte Plattform braucht damit nur ein weiteres
Verzeichnis und keine Zeile im gemeinsamen Code.
Die **letzte** ''-I''-Angabe gewinnt, nicht die erste. Der Plattformpfad
gehört hinter ''-I .''.
==== Wozu TARGET_OS dann doch gut ist ====
''PlatformBackendMatches()'' vergleicht es mit der Erwartung der Unit und
fängt den häufigsten Baufehler ab: den Plattformpfad stehenlassen und für
das andere Ziel bauen. Ohne die Prüfung entstünde eine ''.exe'', die libX11
verlangt — und die Meldung beim Start hat nichts mit der Ursache zu tun.
==== Was ausdrücklich so bleibt ====
[[lyx_-_programmiersprache:vega:controls:tapplication|TApplication]] nimmt das Backend **weiterhin als Argument**. Das ist
keine Nachlässigkeit, sondern die Voraussetzung dafür, dass die Tests ohne
Fenstersystem laufen. ''NewPlatformBackend()'' ist das Angebot obendrauf,
kein Ersatz.
Auch ''Vega/Backend.lyx'' und ''Vega/Backend/'' bleiben frei von
Auswahlcode — dort steht nur ein Verweis hierher.
===== Das Windows-Backend im Besonderen =====
==== Die Aufrufschicht ====
''lyxc'' kann ''extern fn'' auf win64 nur mit **vier Argumenten**
(Compilerbefund #1674). ''CreateWindowExA'' braucht zwölf,
''SetDIBitsToDevice'' zwölf, ''SetWindowPos'' sieben.
''Vega/Backend/WinCall.lyx'' löst das mit einem **Trampolin**: knapp
hundert Byte Maschinencode, zur Laufzeit über ''VirtualAlloc'' (vier
Argumente, geht direkt) in eine ausführbare Seite geschrieben. Es legt die
Argumente in die Microsoft-Register und auf den Stapel und springt.
Dazu ein zweites, kleineres Trampolin für die Gegenrichtung: Windows ruft
die Fensterprozedur nach **seiner** Konvention, Lyx erwartet seine eigene.
Das Trampolin **erzwingt** die Stapelausrichtung mit ''and rsp, -16''
statt sie vom Aufrufer zu erben. Ohne diese eine Zeile läuft derselbe
Aufruf aus ''main'' richtig und aus einem Konstruktor heraus in einen
page fault. Ein Aufruf über einen Lyx-Funktionszeiger benutzt außerdem
weiterhin die SysV-Register — das Ziel steht in ''rdi'', nicht in ''rcx''.
==== Eigenheiten von Win32 ====
* **Die Zeichenfläche muss umgerechnet werden.** Windows nimmt
Außenmaße; ohne ''AdjustWindowRect'' ist jedes Fenster um Rahmen und
Titelleiste zu klein.
* **''SetDIBitsToDevice'' statt ''StretchDIBits''** — letzteres hat
dreizehn Argumente, eines mehr als der Argumentblock fasst. Gestreckt
wird ohnehin nicht.
* **''biHeight'' negativ** heißt „von oben nach unten" — genau so liegt
Vegas Puffer. Ohne das Minus stünde das Bild auf dem Kopf.
* **''WM_SETCURSOR'' muss behandelt werden.** Windows setzt den Zeiger
bei jeder Bewegung zurück; ohne Behandlung zeigt ein Eingabefeld nie
seinen Textcursor.
* **Mauskoordinaten in ''lParam'' sind vorzeichenbehaftete 16 Bit.**
Ohne Umrechnung wird aus x = −3 die Zahl 65533.
* **''WM_MOUSEWHEEL'' liefert Bildschirmkoordinaten** — als einzige
Mausnachricht. Ohne ''ScreenToClient'' weiß der Dispatcher nicht, über
welchem Control gedreht wurde.
==== Stand unter Wine ====
Von 65 dort gebauten Testprogrammen laufen **54 byteweise identisch** zur
Linux-Fassung.
Die übrigen hängen an zwei Compilerbefunden und nicht an Vega:
^ Befund ^ Wirkung ^
| lyxc #1678 | Objekte anlegen und wieder freigeben bricht nach wenigen Durchläufen ab |
| lyxc #1677 | ''std.env'' stürzt auf win64 ab (''EnvBase'' unterstellt das Linux-Speicherbild) |
===== Ein neues Backend schreiben =====
- ''Vega/Backend/.lyx'' anlegen, ''IVegaBackend'' erfüllen.
- Ereignisse in ''TEvent'' übersetzen — die Ereignisarten stehen in
''Vega/Events.lyx''.
- ''platform//Vega/BackendAuto.lyx'' anlegen.
- ''build.sh'' und ''run-tests-*.sh'' um das Ziel erweitern.
Der plattformunabhängige Teil von Vega läuft dann unverändert — das ist
gemessen, nicht behauptet: Windows brauchte keine einzige Änderung an
Controls, Zeichnen, Schrift oder Layout.