====== Inter-Add-in-Kommunikation ======
Add-ins laufen isoliert voneinander. Jedes Add-in kennt nur den Host und seine eigenen Interfaces. Trotzdem gibt es Szenarien, in denen ein Add-in Funktionalität eines anderen nutzen möchte — etwa wenn ein Druck-Add-in den Dokumentinhalt eines Editor-Add-ins abrufen muss. Die ''IServiceRegistry'' macht das möglich, ohne dass die Add-ins einander direkt referenzieren.
Zurück zur [[start|SDK-Übersicht]] oder zur [[..:start|Hauptübersicht]].
===== Vertragsbasierte Kommunikation =====
Die Kommunikation läuft über benannte Verträge (Contract Names). Ein anbietendes Add-in registriert einen Service unter einem Vertragsnamen, ein konsumierendes Add-in fragt diesen Vertrag ab. Beide Seiten müssen dasselbe Interface kennen, referenzieren sich aber nicht gegenseitig. Der Vertrag ist der einzige Berührungspunkt.
Das funktioniert, weil der Vertrag ein Interface definiert, das in einem gemeinsamen SDK-Paket liegt. Beide Add-ins binden dieses SDK-Paket ein und kennen dadurch das Interface, ohne voneinander zu wissen.
===== Manifest-Deklaration =====
Add-ins deklarieren ihre angebotenen und konsumierten Services im [[..:manifest|Manifest]]:
{
"id": "wvds.asset-data",
"servicesProvided": ["IAssetDataService"],
"servicesConsumed": []
}
{
"id": "wvds.asset-report",
"servicesProvided": [],
"servicesConsumed": ["IAssetDataService"]
}
Der Host nutzt diese Deklarationen bei der Abhängigkeitsauflösung. Wenn ''wvds.asset-report'' den Service ''IAssetDataService'' konsumiert und ''wvds.asset-data'' ihn bereitstellt, stellt der Host sicher, dass ''wvds.asset-data'' zuerst aktiviert wird. Das passiert automatisch im Rahmen der topologischen Sortierung — der Entwickler muss sich nicht manuell um die Reihenfolge kümmern.
===== Registrierung =====
Das anbietende Add-in registriert seinen Service in der ''Activate''-Methode:
procedure TAssetDataPlugin.Activate(const AContext: IExtensionContext);
var
Service: IAssetDataService;
begin
Service := TAssetDataServiceImpl.Create;
AContext.Subscribe(
FHost.Services.RegisterService('IAssetDataService', Service)
);
end;
''RegisterService'' gibt ein ''IDisposable'' zurück, das den Service abmeldet, wenn das Add-in deaktiviert wird. Wichtig: Der erste Aufruf mit einem bestimmten Vertragsnamen gewinnt. Versucht ein zweites Add-in, denselben Vertrag zu registrieren, wirft der Host eine Exception. Das verhindert stilles Überschreiben und macht Konflikte sofort sichtbar.
===== Konsum =====
Das konsumierende Add-in fragt den Service in seiner ''Activate''-Methode ab:
procedure TAssetReportPlugin.Activate(const AContext: IExtensionContext);
var
Service: IUnknown;
DataService: IAssetDataService;
begin
Service := FHost.Services.GetService('IAssetDataService');
if Supports(Service, IAssetDataService, DataService) then
FDataService := DataService;
end;
''GetService'' gibt ''IUnknown'' zurück. Das konsumierende Add-in castet auf das erwartete Interface. Wenn der Service nicht registriert ist, gibt ''GetService'' ''nil'' zurück. Das konsumierende Add-in muss damit umgehen können.
===== Reaktion auf späte Registrierung =====
In manchen Fällen wird der anbietende Service erst nach dem konsumierenden Add-in aktiviert — etwa wenn beide ''onStartup'' verwenden und die Reihenfolge nicht durch Abhängigkeiten erzwungen ist. Für diesen Fall bietet die Registry einen Änderungslistener:
AContext.Subscribe(
FHost.Services.OnServiceChanged('IAssetDataService',
TServiceChangedHandler.Create(
procedure(const AEventName, APayload: string)
var
Svc: IUnknown;
begin
Svc := FHost.Services.GetService('IAssetDataService');
if Supports(Svc, IAssetDataService, FDataService) then
RefreshData;
end
)
)
);
''OnServiceChanged'' feuert, sobald sich der Service unter dem angegebenen Vertragsnamen ändert — sei es durch Registrierung oder Abmeldung. Dadurch kann ein konsumierendes Add-in dynamisch auf die Verfügbarkeit reagieren, ohne die Aktivierungsreihenfolge fest vorgeben zu müssen.
===== Empfehlungen =====
* Vertragsnamen sollten dem Interface-Namen entsprechen (''IAssetDataService''), um die Zuordnung offensichtlich zu machen.
* Das gemeinsame Interface gehört in ein SDK-Paket, nicht in eines der beteiligten Add-ins. So können beide Add-ins unabhängig voneinander entwickelt und ausgeliefert werden.
* ''servicesProvided'' und ''servicesConsumed'' im Manifest immer pflegen. Der Host nutzt diese Informationen für die Abhängigkeitsauflösung und zeigt sie in der Erweiterungsliste an.
* Defensiv programmieren: Immer prüfen, ob ''GetService'' ''nil'' zurückgibt. Ein konsumierendes Add-in muss auch dann funktionieren können, wenn der Service nicht verfügbar ist — zumindest soweit, dass es dem Benutzer eine sinnvolle Meldung anzeigt.
Weiter zu den [[..:contributions:start|Contributions]] oder zurück zur [[start|SDK-Übersicht]].