Das Add-in-Modell trennt sauber zwischen dem, was ein Add-in beschreibt, und dem, was es zur Laufzeit tut. Diese Trennung ist kein Selbstzweck. Sie erlaubt dem Host, die Oberfläche vollständig aufzubauen, bevor auch nur eine einzige Add-in-DLL geladen wird. Gleichzeitig definiert sie klare Verantwortlichkeiten: Der Host besitzt die Shell, das Add-in liefert Inhalt.
Zurück zur Übersicht.
Die erste Ebene beschreibt das Add-in statisch. Alles, was der Host wissen muss, steht in einer einzigen JSON-Datei, dem Manifest. Dieses Manifest enthält:
engineVersion gibt an, mit welcher Host-Version das Add-in zusammenarbeitet. Der Host prüft diesen Wert beim Laden und lehnt inkompatible Add-ins ab, bevor es zu Laufzeitfehlern kommen kann.dllChecksum). Der Host verifiziert diese Prüfsumme vor dem Laden. Damit wird sichergestellt, dass die DLL nicht nach dem Signieren verändert wurde. Details zur Signierung stehen im Kapitel Sicherheit.activationEvents legt fest, wann der Host die DLL tatsächlich laden soll. Ein Add-in kann beim Start aktiviert werden (onStartup), beim ersten Aufruf eines bestimmten Commands (onCommand:mein.command) oder gar nicht, bis es explizit benötigt wird. Dieses Lazy-Loading hält den Anwendungsstart schnell.Die zweite Ebene ist der lebendige Code. Sobald der Host entscheidet, ein Add-in zu aktivieren, durchläuft es einen definierten Lebenszyklus:
GetAbiInfo auf. Diese liefert eine Magic-Number, die ABI-Version und die FPC-Compiler-Version zurück. Stimmt einer dieser Werte nicht mit den Erwartungen des Hosts überein, wird die DLL sofort entladen. Dieses Vorgehen verhindert Abstürze durch inkompatible Binaries, etwa wenn ein Add-in mit einer anderen Compiler-Version gebaut wurde als der Host.CreatePlugin(IHost) auf. Diese Funktion ist der einzige Einstiegspunkt, den ein Add-in exportieren muss. Sie erhält ein IHost-Objekt als Parameter, über das das Add-in auf alle Services des Hosts zugreifen kann: Commands, Menüs, Dokumente, Konfiguration, Events und mehr. Die vollständige Service-Referenz steht unter Services.IPlugin.Activate(IExtensionContext) auf. Der IExtensionContext ist der Lebenszeit-Container des Add-ins. Alles, was das Add-in registriert — Command-Handler, Menüpunkte, Event-Abonnements — wird über Subscribe(IDisposable) an diesen Kontext gebunden. Wenn der Host das Add-in deaktiviert, werden alle Registrierungen in umgekehrter Reihenfolge (LIFO) aufgeräumt. Das Add-in muss sich nicht selbst um Aufräumarbeiten kümmern.IHost stehen dem Add-in über achtzehn Services zur Verfügung: von ICommandService über IDocumentService bis hin zu IServiceRegistry für die Kommunikation zwischen Add-ins. Jeder Service gibt bei der Registrierung ein IDisposable zurück, das an den IExtensionContext gebunden wird.IWvdSDocumentFactory beim IDocumentService. Der Host ruft diese Factory auf, wenn der Benutzer ein Dokument des entsprechenden Typs öffnet. Die Factory liefert einen TWvdSDocumentFrame zurück, den der Host in einen Dokument-Tab einbettet. Details zur Frame-Entwicklung stehen unter Document-Frame.Der Host bleibt Eigentümer von allem, was zur Shell gehört. Diese Trennung ist bewusst gewählt, weil sie verhindert, dass ein fehlerhaftes oder bösartiges Add-in die Anwendung destabilisiert.
DoCanClose). Das Add-in entscheidet, ob gespeichert oder verworfen werden soll, aber der Host führt das Schließen durch.when-Ausdrücke enthalten, die gegen diese Schlüssel ausgewertet werden. Ändert sich der Kontext, aktualisiert der Host die Sichtbarkeit aller betroffenen UI-Elemente automatisch.TWvdSDocumentFrame ist das Herzstück eines dokumentorientierten Add-ins. Er füllt den Tab mit Inhalt: Formulare, Grids, Editoren oder beliebige andere LCL-Controls. Der Host kümmert sich um den Rahmen, das Add-in um den Inhalt.TWvdSDocumentFrame registriert. Das Add-in stellt den Frame-Inhalt bereit, der Host bettet ihn in die Seitenleiste ein.IEventBus kann ein Add-in auf Anwendungsereignisse reagieren, etwa auf den Start einer Debug-Sitzung oder auf Konfigurationsänderungen.Einige Entwurfsentscheidungen sind bewusst anders als bei verbreiteten Plugin-Systemen. Diese Abgrenzungen sind wichtig, weil sie häufige Fehlerquellen eliminieren.
TStringList, das mit FPC 3.2 erzeugt wurde, hat ein anderes internes Format als eines von FPC 3.4. Interfaces umgehen dieses Problem, weil sie auf einem stabilen vtable-Layout basieren.IWvdSDocumentFactory auf, die das Add-in selbst bereitstellt. Diese Indirektion stellt sicher, dass das Add-in die volle Kontrolle über die Erzeugung seiner Objekte behält.Die Shell und die Add-ins haben klar getrennte Verantwortlichkeiten. Diese Grenzziehung ist kein Detail, sondern ein Architekturprinzip. Jede Verletzung führt zu Duplikation, erschwerter Wartung und falschen Abhängigkeiten.
Die Shell liefert provider-agnostische Infrastruktur, die für jedes Add-in wiederverwendbar ist:
user.config.json, erzeugt Provider-Instanzen über austauschbare Factories, stellt Borrowed References bereit. Details unter Connection Manager.mssql für ODBC Driver 17/18). Add-ins können eigene Factories nachregistrieren (rest, sqlite, etc.).wvds.settings.open, wvds.quit, wvds.about. Diese können an Add-in-Commands delegieren (z. B. wis.proofExec.open → das WIS-Add-in übernimmt).1. Keine Duplikation. Wenn die Shell SQL-Queries oder View-Frames enthält, die das Add-in ebenfalls implementiert, existiert dieselbe Logik an zwei Stellen. Änderungen müssen synchron erfolgen — das passiert in der Praxis nicht.
2. Unabhängige Release-Zyklen. Add-ins können unabhängig vom Host aktualisiert werden. Wenn die Shell domänenspezifischen Code enthält, erzwingt jede Änderung am Datenmodell einen Shell-Release.
3. Testbarkeit. Die Shell lässt sich ohne Datenbank testen. Add-ins lassen sich mit Mock-Providern testen. Vermischte Verantwortlichkeiten machen beides unmöglich.
4. Wiederverwendbarkeit. Der Connection Manager, die Provider-Factories und die UI-Container sind für jedes Add-in nutzbar — nicht nur für ein bestimmtes Fachmodul.
Der Host verarbeitet Add-ins in vier Phasen, die strikt nacheinander ablaufen:
plugin.json-Dateien: <exe>/plugins/ (Machine-Scope, vom Administrator verwaltet) und ~/.wvdsx/extensions/ (User-Scope, Roaming-Profile-kompatibel). Jedes gefundene Manifest wird geparst und validiert. Fehlende Pflichtfelder führen dazu, dass das Add-in übersprungen wird.onStartup werden sofort aktiviert. Alle anderen warten, bis ihr Aktivierungsereignis eintritt, typischerweise der erste Aufruf eines ihrer Commands. Bei der Aktivierung werden die Lazy-Stubs entfernt und durch die echten Handler des Add-ins ersetzt.