====== Lifecycle ======
Der Lifecycle beschreibt den vollständigen Lebenszyklus eines Add-ins, von der Entdeckung über die Aktivierung bis zur Deaktivierung. Ziel dieser Seite ist es, Add-in-Entwicklern das Zusammenspiel der vier Phasen nachvollziehbar zu machen, damit sie verstehen, wann welcher Teil ihres Codes ausgeführt wird und wie sie Probleme bei der Aktivierung diagnostizieren können.
Im Unterschied zur [[architektur|Architektur-Seite]], die das Modell abstrakt beschreibt, zeigt diese Seite den konkreten Ablauf mit Log-Ausgaben und Sequenzen. Der Vorteil besteht darin, dass ein Entwickler beim Debugging sofort erkennen kann, in welcher Phase sich sein Add-in befindet und was als Nächstes passieren müsste.
Zurück zur [[start|Übersicht]].
===== Übersicht der Phasen =====
Phase 1: SCAN Phase 2: RESOLVE Phase 3: REGISTER Phase 4: ACTIVATE
───────────────── ───────────────── ───────────────────── ──────────────────
Plugin-Verzeichnisse Abhängigkeitsgraph Lazy-Command-Stubs DLL laden
↓ aufbauen Menüpunkte GetAbiInfo
plugin.json lesen ↓ Toolbar-Buttons CreatePlugin
↓ Topologisch sortieren Sidebar-Views ↓
NLS-Dateien laden ↓ Keybindings IPlugin.Activate
↓ Engine-Version Konfiguration ↓
Manifest validieren prüfen ↓ Stubs entfernen
↓ ↓ UI steht vollständig Echte Handler
psDiscovered psResolved registrieren
↓
psActive
┌──── Kein Code geladen ────┐ ┌── UI vollständig ──┐ ┌── DLL aktiv ──┐
===== Phase 1: Scan =====
Der Host durchsucht zwei Verzeichnisse nach ''plugin.json''-Dateien:
| **Scope** | **Pfad** | **Zweck** |
| Machine | ''/plugins/'' | Vom Administrator gebundelte Add-ins (MSI/GPO) |
| User | ''~/.wvdsx/extensions/'' | Vom Benutzer installierte Add-ins (Roaming-Profile-kompatibel) |
Jedes Unterverzeichnis, das eine ''plugin.json'' enthält, wird als Kandidat behandelt. Der Host lädt die [[lokalisierung|NLS-Dateien]] (''package.nls.json'' + sprachspezifische Variante) und ersetzt ''%platzhalter%''-Tokens im Manifest durch die übersetzten Texte.
Danach wird das Manifest validiert. Pflichtfelder wie ''id'', ''name'', ''version'' und ''main'' müssen vorhanden sein. Fehlende Felder führen dazu, dass das Add-in übersprungen wird — der Host zeigt eine Warnung, bricht aber nicht ab.
Typische Log-Ausgabe:
[INFO] Plugin scanning 2 search path(s)
[DEBUG] Plugin discovered: "wvds.amed-wis" v36.01.15.001 (AMED Prüfungsverwaltung (WIS))
[INFO] Plugin found 1 plugin(s)
==== Fehlerbehandlung beim Scan ====
* **Manifest fehlt** — Das Unterverzeichnis wird still übersprungen.
* **JSON-Syntaxfehler** — Error-Log und Benutzer-Benachrichtigung. Das Add-in wird nicht registriert.
* **Doppelte Id** — Das zweite Add-in mit derselben Id wird übersprungen. Warning-Log.
* **Fehlende Pflichtfelder** — Warning-Log mit den konkreten fehlenden Feldern.
* **NLS-Datei fehlt** — Kein Fehler. Platzhalter bleiben als ''%schlüssel%'' stehen.
===== Phase 2: Resolve =====
Der Host baut einen Abhängigkeitsgraphen auf. Quellen für Abhängigkeiten sind:
* ''dependencies'' — Explizite Add-in-Abhängigkeiten mit Id und Version.
* ''servicesConsumed'' — Der Host sucht das Add-in, das den entsprechenden Vertragsnamen in ''servicesProvided'' deklariert.
Der Graph wird topologisch sortiert (Kahn-Algorithmus). Das Ergebnis ist die Aktivierungsreihenfolge: Abhängigkeiten werden vor ihren Konsumenten aktiviert.
Typische Log-Ausgabe:
[INFO] Plugin resolving dependencies
[DEBUG] Plugin activation order: 2 plugin(s)
[DEBUG] 1. wvds.core-services
[DEBUG] 2. wvds.amed-wis
==== Fehlerbehandlung bei Resolve ====
* **Zirkuläre Abhängigkeit** — Beide betroffenen Add-ins werden als ''psError'' markiert. Error-Log mit den Ids.
* **Fehlende Abhängigkeit** — Das konsumierende Add-in wird übersprungen. Warning-Log.
* **Inkompatible Engine-Version** — Das Add-in wird übersprungen. Der Host zeigt eine Benachrichtigung mit der erwarteten vs. vorhandenen Version.
===== Phase 3: Register Contributions =====
Noch bevor eine einzige DLL geladen wird, registriert der Host alle statischen Contributions aus den Manifesten. Für jeden deklarierten Command wird ein **Lazy-Stub** angelegt — ein Platzhalter-Handler, der bei der ersten Ausführung die Aktivierung des Add-ins auslöst.
Gleichzeitig fließen die Manifest-Contributions in die zentrale Contribution-Sammlung (''TWvdSShellContributions''), die der ''TWvdSContributionRouter'' anschließend an die UI-Komponenten verteilt:
* **Commands** → ''TWvdSCommandRouter'' → CommandPalette
* **Menüs** → ''TWvdSMainMenu'' (Menüleiste)
* **Submenüs** → Top-Level-Menüs in der MenuBar
* **Toolbar** → ''TWvdSShellToolbar'' (Haupt-Toolbar)
* **Views** → ''TWvdSActivityBar'' + ''TWvdSSideBar'' (Seitenleiste)
* **Keybindings** → ''TWvdSCommandRouter'' (Tastenkürzel)
Typische Log-Ausgabe:
[INFO] Plugin registering contributions for 1 plugin(s)
[DEBUG] Plugin "wvds.amed-wis": 10 commands, 11 menus, 2 keybindings
[INFO] Plugin contributions fed: 10 commands, 11 menus, 3 views
[INFO] Routed 10 commands
[DEBUG] TWvdSSideBar.RegisterView: id="wis.orgTree" title="ORGANISATIONSSTRUKTUR"
[DEBUG] TWvdSSideBar.RegisterView: id="wis.filter" title="FILTER"
[INFO] Routed 5 toolbar items
Beachtenswert: Die Oberfläche steht jetzt vollständig. Der Benutzer sieht alle Menüpunkte, Toolbar-Buttons, Sidebar-Panels und Tastenkürzel — obwohl keine einzige DLL geladen wurde. Das ist der zentrale Vorteil des deklarativen Contribution-Modells.
===== Phase 4: Activate =====
Die Aktivierung verläuft in zwei Varianten:
==== Eager-Aktivierung (onStartup) ====
Add-ins mit ''activationEvents: ["onStartup"]'' werden sofort nach Phase 3 aktiviert. Das ist typisch für Sidebar-Panels, die beim Start sichtbar sein sollen.
==== Lazy-Aktivierung (onCommand) ====
Add-ins mit ''activationEvents: ["onCommand:mein.command"]'' werden erst beim ersten Aufruf des Commands aktiviert. Der Lazy-Stub fängt den Aufruf ab und löst folgende Schritte aus:
- **Lazy-Stubs entfernen** — Alle Stubs des Add-ins werden disposed, damit die Command-Namen für die echten Handler frei sind.
- **DLL laden** — ''LoadLibrary'' lädt die DLL in den Prozess.
- **ABI-Handshake** — ''GetAbiInfo'' wird aufgerufen. Der Host prüft Magic ($57564453), ABI-Version und FPC-Version.
- **Plugin erzeugen** — ''CreatePlugin(IHost)'' wird aufgerufen. Das Add-in erhält Zugriff auf alle Host-Services.
- **Activate** — ''IPlugin.Activate(IExtensionContext)'' wird aufgerufen. Das Add-in registriert echte Command-Handler, Document-Factories und Event-Abonnements.
- **Command erneut ausführen** — Der ursprünglich aufgerufene Command wird erneut dispatcht — diesmal an den echten Handler.
Typische Log-Ausgabe:
[INFO] Plugin lazy activation "wvds.amed-wis" by command "wis.proofExec.open"
[DEBUG] Plugin DLL loaded: bin/WIS.Plugin.dll
[DEBUG] Plugin ABI handshake: magic=$57564453, abi=1, fpc=30301
[INFO] Plugin "wvds.amed-wis" activated successfully
==== Fehlerbehandlung bei der Aktivierung ====
* **DLL nicht gefunden** — Error-Log. Add-in wird als ''psError'' markiert.
* **ABI-Mismatch** — DLL wird sofort entladen. Error-Log mit erwarteten vs. gefundenen Werten. Typische Ursache: Add-in mit einer anderen FPC-Version kompiliert als der Host.
* **CreatePlugin wirft Exception** — Exception wird gefangen, geloggt, Add-in als ''psError'' markiert.
* **Activate wirft Exception** — Exception wird gefangen, Fehlerzähler inkrementiert. Nach drei aufeinanderfolgenden Fehlern wird das Add-in automatisch deaktiviert.
===== Deaktivierung =====
Beim Herunterfahren der Anwendung durchläuft jedes aktive Add-in die Deaktivierung:
- **IPlugin.Deactivate** — Das Add-in kann eigene Aufräumarbeiten durchführen. Die meisten Add-ins lassen diese Methode leer.
- **IExtensionContext.DisposeAll** — Alle über ''Subscribe'' gesammelten ''IDisposable''-Tokens werden in umgekehrter Reihenfolge (LIFO) aufgelöst. Command-Handler, Menüpunkte, Factories und Event-Abonnements werden entfernt.
- **FreeLibrary** — Die DLL wird entladen.
Die LIFO-Reihenfolge ist wichtig: Wenn ein Menüpunkt auf einen Command verweist, wird der Menüpunkt vor dem Command entfernt. Dadurch gibt es keine Dangling References.
===== Troubleshooting =====
==== Add-in erscheint nicht ====
| **Symptom** | **Mögliche Ursache** | **Diagnose** |
| Kein Log-Eintrag | Plugin-Verzeichnis nicht im Suchpfad | Prüfe, ob ''plugin.json'' in ''/plugins//'' oder ''~/.wvdsx/extensions//'' liegt |
| ''Plugin found 0'' | Verzeichnis existiert nicht | Erstelle das Verzeichnis und kopiere ''plugin.json'' hinein |
| ''invalid manifest'' | Pflichtfeld fehlt | Prüfe Log auf fehlende Felder (''id'', ''name'', ''version'', ''main'') |
| ''%displayName%'' in der UI | NLS-Datei fehlt oder ''package.nls.json'' enthält den Schlüssel nicht | Prüfe, ob ''package.nls.json'' alle ''%platzhalter%'' abdeckt |
==== Command in Palette nicht sichtbar ====
| **Symptom** | **Mögliche Ursache** | **Diagnose** |
| Kein Eintrag trotz ''Routed N commands'' | Command wurde nicht in ''contributes.commands'' deklariert | Prüfe ''plugin.json'' auf Tippfehler im Command-Namen |
| ''Routed 0 commands'' | Contributions wurden nicht in die Routing-Pipeline gespeist | Prüfe Log auf ''Plugin contributions fed: N commands'' |
==== DLL wird nicht geladen ====
| **Symptom** | **Mögliche Ursache** | **Diagnose** |
| ''DLL not found'' | ''main''-Pfad im Manifest stimmt nicht mit der Datei überein | Prüfe ''main: "bin/MyPlugin.dll"'' gegen die reale Dateistruktur |
| ''ABI mismatch'' | DLL mit anderer FPC-Version kompiliert | Kompiliere mit derselben FPC-Version wie der Host |
| ''CreatePlugin failed'' | Exception im Constructor | Starte im Debug-Modus: ''wdochost --debug-extension=mein.addin'' |
Weiter zur [[architektur|Architektur]] oder zurück zur [[start|Übersicht]].