====== 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.