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.
<WRAP center round important 90%>
Die letzte -I-Angabe gewinnt, nicht die erste. Der Plattformpfad
gehört hinter -I ..
</WRAP>
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
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.
<WRAP center round alert 90%>
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.
</WRAP>
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/<Name>.lyxanlegen,IVegaBackenderfüllen.- Ereignisse in
TEventübersetzen — die Ereignisarten stehen in
Vega/Events.lyx.
platform/<ziel>/Vega/BackendAuto.lyxanlegen.build.shundrun-tests-*.shum 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.
