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 SDK-Übersicht oder zur 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:

{
  "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 oder zurück zur SDK-Übersicht.

Zuletzt geändert: den 15.03.2026 um 02:23