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-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 Ü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 <exe>/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 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:

  • CommandsTWvdSCommandRouter → CommandPalette
  • MenüsTWvdSMainMenu (Menüleiste)
  • Submenüs → Top-Level-Menüs in der MenuBar
  • ToolbarTWvdSShellToolbar (Haupt-Toolbar)
  • ViewsTWvdSActivityBar + TWvdSSideBar (Seitenleiste)
  • KeybindingsTWvdSCommandRouter (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:

  1. Lazy-Stubs entfernen — Alle Stubs des Add-ins werden disposed, damit die Command-Namen für die echten Handler frei sind.
  2. DLL ladenLoadLibrary lädt die DLL in den Prozess.
  3. ABI-HandshakeGetAbiInfo wird aufgerufen. Der Host prüft Magic ($57564453), ABI-Version und FPC-Version.
  4. Plugin erzeugenCreatePlugin(IHost) wird aufgerufen. Das Add-in erhält Zugriff auf alle Host-Services.
  5. ActivateIPlugin.Activate(IExtensionContext) wird aufgerufen. Das Add-in registriert echte Command-Handler, Document-Factories und Event-Abonnements.
  6. 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:

  1. IPlugin.Deactivate — Das Add-in kann eigene Aufräumarbeiten durchführen. Die meisten Add-ins lassen diese Methode leer.
  2. 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.
  3. 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 <exe>/plugins/<name>/ oder ~/.wvdsx/extensions/<name>/ 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 oder zurück zur Übersicht.

Zuletzt geändert: den 15.03.2026 um 21:07