====== SDK-Übersicht ======
Das WvdS Add-in SDK definiert die programmatische Schnittstelle zwischen einem Add-in und dem Host. Es besteht aus einer Handvoll Interfaces, einer Basisklasse für Dokumentansichten und einem klar definierten Lebenszyklus. Ein Add-in-Entwickler muss genau zwei Funktionen exportieren und ein Interface implementieren — alles Weitere stellt der Host über ''IHost'' bereit.
Zurück zur [[..:start|Übersicht]].
===== DLL-Exporte =====
Jede Add-in-DLL exportiert exakt zwei Funktionen. Mehr ist nicht nötig und mehr wird vom Host auch nicht erwartet.
==== GetAbiInfo ====
function GetAbiInfo: TPluginAbiInfo; stdcall;
Diese Funktion liefert Informationen über die binäre Kompatibilität der DLL. Der Host ruft sie als Erstes auf, noch bevor irgendein Objekt erzeugt wird.
type
TPluginAbiInfo = record
Magic: UInt32; // Fester Wert: $57564453 ('WVDS')
AbiVersion: UInt32; // ABI-Versionsnummer, aktuell 1
FpcVersion: UInt32; // FPC-Compiler-Version, z.B. 30301
end;
Der Host prüft alle drei Felder. Die Magic-Number stellt sicher, dass es sich überhaupt um eine WvdS-Add-in-DLL handelt und nicht um eine beliebige Bibliothek. Die ABI-Version ermöglicht dem Host, in Zukunft Änderungen am Interface-Layout vorzunehmen, ohne bestehende Add-ins zu brechen — der Host kann dann ältere ABI-Versionen weiterhin unterstützen oder gezielt ablehnen. Die FPC-Version verhindert Abstürze durch unterschiedliche vtable-Layouts oder String-Implementierungen zwischen Compiler-Versionen.
Stimmt einer der Werte nicht überein, wird die DLL sofort per ''FreeLibrary'' entladen und das Add-in als fehlerhaft markiert. Der Benutzer sieht eine entsprechende Meldung in der Erweiterungsliste.
==== CreatePlugin ====
function CreatePlugin(const AHost: IHost): IPlugin; stdcall;
Diese Funktion ist die Factory des Add-ins. Der Host ruft sie nach dem erfolgreichen ABI-Handshake auf und übergibt sein ''IHost''-Objekt. Das Add-in erzeugt daraufhin sein Plugin-Objekt und gibt es als ''IPlugin'' zurück.
Typische Implementierung:
function CreatePlugin(const AHost: IHost): IPlugin; stdcall;
begin
Result := TMyPlugin.Create(AHost);
end;
Das ''IHost''-Objekt bleibt über die gesamte Lebensdauer des Add-ins gültig. Es wird vom Host verwaltet und darf nicht vom Add-in freigegeben werden.
===== IPlugin =====
type
IPlugin = interface
procedure Activate(const AContext: IExtensionContext);
procedure Deactivate;
end;
Das ''IPlugin''-Interface ist absichtlich minimal gehalten. Es hat genau zwei Methoden:
* **Activate** — Der Host ruft diese Methode auf, wenn das Add-in aktiviert wird. Hier registriert das Add-in seine Command-Handler, Event-Abonnements, Dokumentfabriken und alles andere, was es zur Laufzeit beiträgt. Der ''IExtensionContext'' dient als Lebenszeit-Container für alle Registrierungen.
* **Deactivate** — Der Host ruft diese Methode auf, wenn das Add-in deaktiviert wird (typischerweise beim Schließen der Anwendung). Das Add-in kann hier eigene Aufräumarbeiten durchführen. Die meisten Add-ins benötigen diese Methode nicht, weil der Host alle über ''IExtensionContext.Subscribe'' registrierten Disposables automatisch aufräumt.
===== IExtensionContext =====
type
IExtensionContext = interface
procedure Subscribe(const ADisposable: IDisposable);
function GetGlobalStoragePath: string;
function GetWorkspaceStoragePath: string;
procedure SetWorkspaceState(const AKey, AValue: string);
function GetWorkspaceState(const AKey, ADefault: string): string;
end;
Der ''IExtensionContext'' ist der zentrale Lebenszeit-Container des Add-ins. Er erfüllt drei Aufgaben:
=== Registrierungen sammeln ===
Jeder Service des Hosts gibt bei der Registrierung ein ''IDisposable'' zurück. Dieses Disposable-Token muss an den ''IExtensionContext'' übergeben werden:
procedure TMyPlugin.Activate(const AContext: IExtensionContext);
var
Disposable: IDisposable;
begin
Disposable := FHost.Commands.RegisterCommand('mein.command', 'Mein Befehl', TMyHandler.Create);
AContext.Subscribe(Disposable);
Disposable := FHost.Menus.AddMenuItem('menuBar/tools', 'mein.command', '');
AContext.Subscribe(Disposable);
end;
Wenn der Host das Add-in deaktiviert, ruft er ''DisposeAll'' auf dem Kontext auf. Alle gesammelten Disposables werden in umgekehrter Reihenfolge (LIFO) freigegeben. Das stellt sicher, dass Abhängigkeiten korrekt aufgelöst werden — ein Menüpunkt, der auf einen Command verweist, wird vor dem Command selbst entfernt.
=== Persistenter Speicher ===
Der Kontext stellt zwei Verzeichnispfade bereit:
* ''GlobalStoragePath'' — Ein Verzeichnis im Benutzer-Profil (''~/.wvdsx/storage/{addin-id}/''). Geeignet für Daten, die über Workspace-Grenzen hinweg gelten, etwa Benutzer-Präferenzen oder Caches.
* ''WorkspaceStoragePath'' — Ein Verzeichnis im Workspace (''.wvdsx/storage/{addin-id}/''). Geeignet für workspace-spezifische Daten, etwa projektbezogene Einstellungen.
Beide Verzeichnisse werden vom Host erstellt, wenn das Add-in zum ersten Mal darauf zugreift.
=== Workspace-State ===
Für einfache Schlüssel-Wert-Paare bietet der Kontext ''SetWorkspaceState'' und ''GetWorkspaceState''. Diese Werte werden im Arbeitsspeicher gehalten und beim Herunterfahren persistiert. Sie eignen sich für leichtgewichtigen Zustand wie die zuletzt geöffnete Ansicht oder den Scroll-Position eines Panels.
===== IHost =====
''IHost'' ist das Tor zu allen Services des Hosts. Das Add-in erhält es bei der Erzeugung über ''CreatePlugin'' und verwendet es während seiner gesamten Lebensdauer.
type
IHost = interface
property Commands: ICommandService;
property Events: IEventBus;
property Configuration: IConfigurationService;
property Context: IContextService;
property Menus: IMenuService;
property Toolbar: IToolbarService;
property StatusBar: IStatusBarService;
property Notifications: INotificationService;
property Documents: IDocumentService;
property SideBar: ISidebarService;
property TreeViews: ITreeViewService;
property Storage: IStorageService;
property Dialogs: IDialogService;
property Keybindings: IKeybindingService;
property Services: IServiceRegistry;
property Progress: IProgressService;
property Log: ILogService;
property Theme: IThemeService;
property Permissions: IPermissionService;
{ --- Data --- }
property Connections: IWvdSConnectionManager;
end;
Jeder Service ist über eine Property zugänglich. Das ''IHost''-Objekt erzeugt diese Services nicht bei jedem Zugriff neu — sie sind Singletons, die der Host einmal erzeugt und über die gesamte Laufzeit bereitstellt.
Die vollständige Referenz aller Services steht unter [[services|Service-Referenz]].
===== IDisposable =====
type
IDisposable = interface
procedure Dispose;
end;
''IDisposable'' ist das zentrale Muster für Ressourcenverwaltung im gesamten SDK. Jede Registrierung — ob Command, Menüpunkt, Event-Abonnement oder Service — gibt ein ''IDisposable'' zurück. Solange dieses Token existiert, ist die Registrierung aktiv. Sobald ''Dispose'' aufgerufen wird, wird die Registrierung entfernt.
Dieses Muster hat zwei Vorteile. Erstens muss sich das Add-in nicht merken, wie eine Registrierung rückgängig gemacht wird — es braucht nur das Token. Zweitens stellt der ''IExtensionContext'' sicher, dass beim Deaktivieren des Add-ins alle Tokens automatisch aufgeräumt werden. Das verhindert dangling References über die DLL-Grenze hinweg.
===== Lebenszyklus im Überblick =====
- Der Host ruft ''GetAbiInfo'' auf und prüft Magic, ABI-Version und FPC-Version.
- Der Host ruft ''CreatePlugin(IHost)'' auf. Das Add-in erzeugt sein Plugin-Objekt.
- Der Host ruft ''IPlugin.Activate(IExtensionContext)'' auf. Das Add-in registriert Command-Handler, Factories und Event-Abonnements.
- Das Add-in arbeitet: Benutzer lösen Commands aus, öffnen Dokumente, interagieren mit Panels.
- Beim Herunterfahren ruft der Host ''IPlugin.Deactivate'' auf, dann ''IExtensionContext.DisposeAll''.
- Der Host ruft ''FreeLibrary'' auf. Die DLL wird entladen.
Weiter zur [[services|Service-Referenz]], zum [[document-frame|Document-Frame]] oder zum [[script-host|ScriptHost & Host-Contract]].