Freigeben über


Wählen Sie das richtige Visual Studio-Erweiterbarkeitsmodell für Sie aus.

Sie können Visual Studio mit drei Haupterweiterungsmodellen, VSSDK, Community Toolkit und VisualStudio.Extensibility erweitern. In diesem Artikel werden die Vor- und Nachteile jedes Einzelnen behandelt. Wir verwenden ein einfaches Beispiel, um die Architektur- und Codeunterschiede zwischen den Modellen hervorzuheben.

VSSDK

VSSDK- (oder Visual Studio SDK) ist das Modell, auf dem die meisten Erweiterungen auf dem Visual Studio Marketplace- basieren. Dieses Modell basiert auf Visual Studio selbst. Es ist die umfassendste und leistungsfähigste, aber auch die komplexeste, um richtig zu lernen und zu verwenden. Erweiterungen, die VSSDK verwenden, werden im gleichen Prozess wie Visual Studio selbst ausgeführt. Das Laden im selben Prozess wie Visual Studio bedeutet, dass eine Erweiterung mit einer Zugriffsverletzung, einer Endlosschleife oder anderen Problemen dazu führen kann, dass Visual Studio abstürzt oder hängt und die Benutzerfreundlichkeit beeinträchtigt. Und da Erweiterungen im selben Prozess wie Visual Studio ausgeführt werden, können sie nur mit .NET Frameworkerstellt werden. Extender, die Bibliotheken verwenden oder integrieren möchten, die .NET 5 und höher verwenden, können dies nicht mit VSSDK tun.

APIs in VSSDK wurden im Laufe der Jahre aggregiert, da Visual Studio selbst transformiert und weiterentwickelt wurde. In nur einer Erweiterung können Sie sich mit COM-basierten APIs aus Legacy-Imprints auseinandersetzen, von der bestechenden Einfachheit von DTE profitieren und sich mit MEF-Importen und -Exporten beschäftigen. Nehmen wir uns ein Beispiel für das Schreiben einer Erweiterung an, die den Text aus dem Dateisystem liest und am Anfang des aktuellen aktiven Dokuments im Editor einfügt. Der folgende Codeausschnitt zeigt den Code, den Sie schreiben würden, um zu behandeln, wenn ein Befehl in einer VSSDK-basierten Erweiterung aufgerufen wird:

private void Execute(object sender, EventArgs e)
{
    var textManager = package.GetService<SVsTextManager, IVsTextManager>();
    textManager.GetActiveView(1, null, out IVsTextView activeTextView);

    if (activeTextView != null && activeTextView is IVsTextViewEx nativeView)
    {
        ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

        IComponentModel2 compService = package.GetService<SComponentModel, IComponentModel2>();
        IVsEditorAdaptersFactoryService editorAdapter = compService.GetService<IVsEditorAdaptersFactoryService>();
        var wpfTextView = editorAdapter?.GetWpfTextView(activeTextView);

        if (frameValue is IVsWindowFrame frame && wpfTextView != null)
        {
            var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
            wpfTextView.TextBuffer?.Insert(0, fileText);
        }
    }
}

Darüber hinaus müssen Sie auch eine .vsct Datei bereitstellen, die die Befehlskonfiguration definiert, z. B. wo sie in der Benutzeroberfläche platziert werden soll, der zugeordnete Text usw.:

<Commands package="guidVSSDKPackage">
    <Groups>
        <Group guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />
        </Group>
    </Groups>

    <Buttons>
        <Button guid="guidVSSDKPackageCmdSet" id="InsertTextCommandId" priority="0x0100" type="Button">
        <Parent guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
            <ButtonText>Invoke InsertTextCommand (Unwrapped Community Toolkit)</ButtonText>
        </Strings>
        </Button>
        <Button guid="guidVSSDKPackageCmdSet" id="cmdidVssdkInsertTextCommand" priority="0x0100" type="Button">
        <Parent guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages1" id="bmpPic1" />
        <Strings>
            <ButtonText>Invoke InsertTextCommand (VSSDK)</ButtonText>
        </Strings>
        </Button>
    </Buttons>

    <Bitmaps>
        <Bitmap guid="guidImages" href="Resources\InsertTextCommand.png" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows, bmpPicStrikethrough" />
        <Bitmap guid="guidImages1" href="Resources\VssdkInsertTextCommand.png" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows, bmpPicStrikethrough" />
    </Bitmaps>
</Commands>

Wie Sie im Beispiel sehen können, mag der Code unintuitiv erscheinen, und es ist unwahrscheinlich, dass jemand, der mit .NET vertraut ist, diesen leicht verstehen kann. Es gibt viele Konzepte, die sie erlernen müssen, und die API-Muster für den Zugriff auf den aktiven Editortext sind antiquiert. Für die meisten Extender werden VSSDK-Erweiterungen aus dem Kopieren und Einfügen aus Onlinequellen erstellt, was zu schwierigen Debuggingsitzungen, Test- und Fehlersitzungen und Frustration führen kann. In vielen Fällen sind VSSDK-Erweiterungen möglicherweise nicht die einfachste Möglichkeit, die Erweiterungsziele zu erreichen (manchmal sind sie jedoch die einzige Wahl).

Gemeinschafts-Werkzeugkasten

Community Toolkit ist das communitybasierte Open-Source-Erweiterungsmodell für Visual Studio, das das VSSDK umschließt und so die Entwicklung erleichtert. Da es auf dem VSSDK basiert, unterliegt es den gleichen Einschränkungen wie VSSDK (d. h. nur .NET Framework, keine Isolation vom Rest von Visual Studio usw.). Wenn Sie dasselbe Beispiel zum Schreiben einer Erweiterung, mit der der aus dem Dateisystem gelesene Text eingefügt wird, mit Community Toolkit fortsetzen, wird die Erweiterung für einen Befehlshandler wie folgt geschrieben:

protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
    DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();
    if (docView?.TextView == null) return;
    var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
    docView.TextBuffer?.Insert(0, fileText);
}

Der resultierende Code wird im Hinblick auf Einfachheit und Intuitiveität von VSSDK wesentlich verbessert! Nicht nur haben wir die Anzahl der Zeilen deutlich verringert, aber der resultierende Code sieht auch vernünftig aus. Es ist nicht erforderlich zu verstehen, was der Unterschied zwischen SVsTextManager und IVsTextManagerist. Die APIs wirken nun .NET-freundlicher, indem sie gängige Benennungs- und asynchrone Muster übernehmen und die Priorisierung häufiger Vorgänge unterstützen. Das Community Toolkit basiert jedoch noch auf dem vorhandenen VSSDK-Modell, sodass Spuren der zugrunde liegenden Struktur durchscheinen. Beispielsweise ist eine .vsct Datei immer noch erforderlich. Während das Community-Toolkit eine hervorragende Aufgabe bei der Vereinfachung der APIs hat, ist es an die Einschränkungen von VSSDK gebunden und hat keine Möglichkeit, die Erweiterungskonfiguration zu vereinfachen.

VisualStudio.Extensibility

VisualStudio.Extensibility ist das neue Erweiterbarkeitsmodell, bei dem Erweiterungen außerhalb des Visual Studio-Hauptprozesses ausgeführt werden. Aufgrund dieser grundlegenden Architekturverschiebung stehen jetzt neue Muster und Funktionen für Erweiterungen zur Verfügung, die mit VSSDK oder Community Toolkit nicht möglich sind. VisualStudio.Extensibility bietet einen völlig neuen Satz von APIs, die konsistent und einfach zu verwenden sind, ermöglicht Erweiterungen, .NET als Ziel zu nutzen, isoliert Fehler, die sich aus Erweiterungen ergeben, vom Rest von Visual Studio und ermöglicht es Benutzern, Erweiterungen zu installieren, ohne Visual Studio neu zu starten. Da das neue Modell jedoch auf einer neuen zugrunde liegenden Architektur basiert, verfügt es noch nicht über die Breite, die VSSDK und das Community Toolkit haben. Um diese Lücke zu überbrücken, können Sie Ihre VisualStudio.Extensibility-Erweiterungen im Prozessausführen, sodass Sie weiterhin VSSDK-APIs verwenden können. Dies bedeutet jedoch, dass Ihre Erweiterung nur .NET Framework als Ziel verwenden kann, da sie denselben Prozess wie Visual Studio verwendet, der auf .NET Framework basiert.

Wenn Sie dasselbe Beispiel zum Schreiben einer Erweiterung, mit der der Text aus einer Datei eingefügt wird, mit VisualStudio.Extensibility fortsetzen, wird die Erweiterung für die Befehlsbehandlung wie folgt geschrieben:

public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
    var activeTextView = await context.GetActiveTextViewAsync(cancellationToken);
    if (activeTextView is not null)
    {
        var editResult = await Extensibility.Editor().EditAsync(batch =>
        {
            var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));

            ITextDocumentEditor editor = activeTextView.Document.AsEditable(batch);
            editor.Insert(0, fileText);
        }, cancellationToken);
                
    }
}

Um den Befehl für Platzierung, Text usw. zu konfigurieren, müssen Sie keine .vsct Datei mehr bereitstellen. Stattdessen erfolgt dies über Code:

public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
    Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
    Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};

Dieser Code ist einfacher zu verstehen und zu folgen. In den meisten Fällen können Sie diese Erweiterung ausschließlich über den Editor mit Hilfe von IntelliSense schreiben, auch für die Befehlskonfiguration.

Vergleich der verschiedenen Visual Studio-Erweiterbarkeitsmodelle

Aus dem Beispiel stellen Sie möglicherweise fest, dass bei der Verwendung von VisualStudio.Extensibility mehr Codezeilen nötig sind als beim Community Toolkit im Befehlshandler. Community Toolkit ist ein hervorragender Wrapper für Benutzerfreundlichkeit bei der Erstellung von Erweiterungen mit dem VSSDK, jedoch gibt es Fallstricke, die nicht sofort offensichtlich sind und zur Entwicklung von VisualStudio.Extensibility geführt haben. Um den Übergang und die Notwendigkeit zu verstehen, insbesondere wenn es so scheint, als ob das Community-Toolkit auch Code ergibt, der einfach zu schreiben und zu verstehen ist, sehen wir uns das Beispiel an und vergleichen, was in den tieferen Ebenen des Codes passiert.

Wir können den Code in diesem Beispiel schnell entpacken und sehen, was tatsächlich auf der VSSDK-Seite aufgerufen wird. Wir konzentrieren uns ausschließlich auf den Befehlsausführungsausschnitt, da es zahlreiche Details gibt, die VSSDK benötigt, was Community Toolkit gut ausblendet. Aber sobald wir uns den zugrunde liegenden Code ansehen, verstehen Sie, warum die Einfachheit hier ein Kompromiss ist. Die Einfachheit blendet einige der zugrunde liegenden Details aus, was zu unerwartetem Verhalten, Fehlern und sogar Leistungsproblemen und Abstürzen führen kann. Der folgende Codeausschnitt zeigt den Community-Toolkit-Code, der entwrappt wurde, um die VSSDK-Aufrufe anzuzeigen:

private void Execute(object sender, EventArgs e)
{
    package.JoinableTaskFactory.RunAsync(async delegate
    {
        var textManager = await package.GetServiceAsync<SVsTextManager, IVsTextManager>();
        textManager.GetActiveView(1, null, out IVsTextView activeTextView);

        if (activeTextView != null && activeTextView is IVsTextViewEx nativeView)
        {
            await package.JoinableTaskFactory.SwitchToMainThreadAsync();
            ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

            IComponentModel2 compService = package.GetService<SComponentModel, IComponentModel2>();
            IVsEditorAdaptersFactoryService editorAdapter = compService.GetService<IVsEditorAdaptersFactoryService>();
            var wpfTextView = editorAdapter?.GetWpfTextView(activeTextView);

            if (frameValue is IVsWindowFrame frame && wpfTextView != null)
            {
                var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
                wpfTextView.TextBuffer?.Insert(0, fileText);    
            }
        }
    });
}

Es gibt nur wenige Themen, die es hier zu besprechen gibt, und sie drehen sich alle um Threading und asynchronen Code. Wir werden jeden einzelnen ausführlich durchgehen.

Asynchrone API im Vergleich zur asynchronen Codeausführung

Beachten Sie zunächst, dass die ExecuteAsync-Methode im Community Toolkit ein verkapselter asynchroner "fire-and-forget"-Aufruf in VSSDK ist:

package.JoinableTaskFactory.RunAsync(async delegate
{
  …
});

VSSDK selbst unterstützt keine asynchrone Befehlsausführung aus der Sicht der Kern-API. Wenn ein Befehl ausgeführt wird, hat VSSDK also keine Möglichkeit, den Befehlshandlercode in einem Hintergrundthread auszuführen, warten, bis er abgeschlossen ist, und den Benutzer mit Ausführungsergebnissen an den ursprünglichen Aufrufkontext zurückzugeben. Obwohl die ExecuteAsync-API im Community Toolkit syntaktisch asynchron ist, ist dies nicht die echte asynchrone Ausführung. Und da es sich um eine Fire-and-Forget-Methode der asynchronen Ausführung handelt, können Sie ExecuteAsync immer wieder aufrufen, ohne zu warten, bis der vorherige Aufruf abgeschlossen ist. Das Community Toolkit bietet zwar eine bessere Erfahrung in Bezug auf die Unterstützung von Extendern bei der Implementierung gängiger Szenarien, aber letztendlich kann es die grundlegenden Probleme mit VSSDK nicht lösen. In diesem Fall ist die zugrunde liegende VSSDK-API nicht asynchron, und die von Community Toolkit bereitgestellten Fire-and-Forget-Hilfsmethoden können das asynchrone Ergebnis und das Arbeiten mit dem Clientzustand nicht ordnungsgemäß angehen. Dies kann einige potenzielle schwer zu debuggende Probleme mit sich bringen.

UI-Thread im Vergleich zum Hintergrundthread

Der andere Nachteil dieses umwickelten asynchronen Aufrufs des Community Toolkits besteht darin, dass der Code selbst weiterhin im UI-Thread ausgeführt wird, und es liegt am Erweiterungsentwickler herauszufinden, wie man ordnungsgemäß zu einem Hintergrundthread wechselt, wenn man das Einfrieren der Benutzeroberfläche nicht riskieren möchte. Obwohl das Community Toolkit das Rauschen und den zusätzlichen Code des VSSDK ausblenden kann, erfordert es dennoch, dass Sie die Komplexität des Threadings in Visual Studio verstehen. Und einer der ersten Lektionen, die Sie in VS-Threading lernen, ist, dass nicht alles aus einem Hintergrundthread ausgeführt werden kann. Mit anderen Worten, nicht alles ist threadsicher, insbesondere die Aufrufe, die in COM-Komponenten fließen. Im obigen Beispiel sehen Sie also, dass ein Aufruf zum Wechsel zum Hauptthread (UI) vorhanden ist:

await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

Sie können nach diesem Aufruf natürlich wieder zu einem Hintergrundthread wechseln. Wenn Sie jedoch das Community Toolkit zur Erweiterung verwenden, müssen Sie genau darauf achten, in welchem Thread sich Ihr Code befindet, und sicherstellen, dass kein Risiko besteht, die Benutzeroberfläche zum Einfrieren zu bringen. Das korrekte Verwenden von Threads in Visual Studio ist schwierig und erfordert die ordnungsgemäße Verwendung von JoinableTaskFactory, um Deadlocks zu vermeiden. Der Kampf um das Schreiben von Code, der sich korrekt mit Threading befasst, war eine konstante Quelle von Fehlern, auch für unsere internen Visual Studio-Techniker. VisualStudio.Extensibility hingegen vermeidet dieses Problem vollständig, indem Erweiterungen außerhalb des Prozesses ausgeführt und auf asynchrone APIs end-to-End angewiesen werden.

Einfache API im Vergleich zu einfachen Konzepten

Da das Community Toolkit viele der Komplexitäten von VSSDK ausblendet, könnte es den Erweiterungsentwicklern ein falsches Gefühl von Einfachheit geben. Lassen Sie uns mit demselben Beispielcode fortfahren. Wenn ein Extender die Threadinganforderungen der Visual Studio-Entwicklung nicht kennt, könnte möglicherweise davon ausgegangen werden, dass der Code die ganze Zeit von einem Hintergrundthread ausgeführt wird. Sie haben kein Problem damit, dass der Aufruf zum Lesen einer Datei aus einer Textdatei synchron erfolgt. Wenn es sich in einem Hintergrundthread befindet, wird die Benutzeroberfläche nicht einfrieren, wenn die betreffende Datei groß ist. Wenn der Code jedoch in das VSSDK entpackt wird, wird man feststellen, dass dies nicht der Fall ist. Während die API aus dem Community Toolkit sicherlich einfacher zu verstehen und besser zu schreiben aussieht, da sie an VSSDK gebunden ist, unterliegen sie VSSDK-Einschränkungen. Durch diese Vereinfachungen können wichtige Konzepte übersehen werden, die noch mehr Schaden anrichten können, wenn sie von den Extendern nicht beachtet werden. Mit VisualStudio.Extensibility werden die vielen Probleme vermieden, die durch Abhängigkeiten vom Hauptthread entstehen, da der Schwerpunkt auf dem prozessexternen Modell und den asynchronen APIs als Basis liegt. Obwohl das Ausführen außerhalb des Prozesses das Threading am einfachsten vereinfachen würde, übertragen sich viele dieser Vorteile auch auf Erweiterungen, die innerhalb des Prozesses ausgeführt werden. Beispielsweise werden VisualStudio.Extensibility-Befehle immer in einem Hintergrundthread ausgeführt. Die Interaktion mit den VSSDK-APIs erfordert zwar umfassende Kenntnisse der Funktionsweise von Threads, aber zumindest müssen Sie nicht die Kosten für versehentliche Blockaden wie in diesem Beispiel tragen.

Vergleichsdiagramm

Die folgende Tabelle zeigt einen schnellen Vergleich, um das, was wir im vorherigen Abschnitt ausführlich behandelt haben, zusammenzufassen:

VSSDK Community Toolkit VisualStudio.Extensibility
Laufzeitunterstützung .NET Framework .NET Framework .NET
Abschottung von Visual Studio
Simple API
Asynchrone Ausführung und API-
VS Szenariobreite
Installierbar ohne Neustart
UnterstütztVS 2019 undälter

Um Ihnen bei der Anwendung des Vergleichs auf Ihre Visual Studio-Erweiterungsanforderungen zu helfen, finden Sie hier einige Beispielszenarien und unsere Empfehlungen für das zu verwendende Modell:

  • Ich bin neu in der Entwicklung von Visual Studio-Erweiterungen und möchte die einfachste Onboarding-Erfahrung machen, um eine qualitativ hochwertige Erweiterung zu erstellen, und ich muss nurVisual Studio 2022 oder höher unterstützen.
    • In diesem Fall wird empfohlen, VisualStudio.Extensibility zu verwenden.
  • möchte ich eine Erweiterung schreiben, die auf Visual Studio 2022 und höher ausgerichtet ist.VisualStudio.Extensibility unterstützt jedoch nicht alleFunktionen, die ich benötige.
    • Es wird empfohlen, in diesem Fall eine Hybridmethode zur Kombination von VisualStudio.Extensibility und VSSDK zu übernehmen. Sie können eine VisualStudio.Extensibility-Erweiterung erstellen, die im Prozessausgeführt wird, wodurch Sie auf VSSDK- oder Community-Toolkit-APIs zugreifen können.
  • habe ich eine vorhandene Erweiterung und möchte sie aktualisieren, um neuere Versionen zu unterstützen. Ich möchte, dass meine Erweiterung so viele Versionen von Visual Studio wie möglich unterstützt.
    • Da VisualStudio.Extensibility nur Visual Studio 2022 und höher unterstützt, ist VSSDK oder Community Toolkit die beste Option für diesen Fall.
  • Ich habe eine vorhandene Erweiterung, die ich zuVisualStudio.Extensibility migrieren möchte, um .NET zu nutzen und ohne Neustart zu installieren.
    • Dieses Szenario ist etwas differenzierter, da VisualStudio.Extensibility keine Untergeordneten Versionen von Visual Studio unterstützt.
      • Wenn Ihre vorhandene Erweiterung nur Visual Studio 2022 unterstützt und über alle benötigten APIs verfügt, empfehlen wir, die Erweiterung neu zu schreiben, um VisualStudio.Extensibility zu verwenden. Wenn Ihre Erweiterung jedoch APIs benötigt, die visualStudio.Extensibility noch nicht aufweist, fahren Sie fort, und erstellen Sie eine VisualStudio.Extensibility-Erweiterung, die im Prozess ausgeführt wird, damit Sie auf VSSDK-APIs zugreifen können. Mit der Zeit können Sie die Verwendung der VSSDK-API vermeiden, da VisualStudio.Extensibility Unterstützung hinzufügt, und Ihre Erweiterungen so verschieben, dass sie außerhalb des Prozesses ausgeführt werden.
      • Wenn Ihre Erweiterung Down-Level-Versionen von Visual Studio unterstützen muss, die keine Unterstützung für VisualStudio.Extensibility haben, empfehlen wir, dass Sie eine Umgestaltung in Ihrer Codebasis durchführen. Rufen Sie den gesamten gemeinsamen Code ab, der für Visual Studio-Versionen in eine eigene Bibliothek freigegeben werden kann, und erstellen Sie separate VSIX-Projekte, die auf unterschiedliche Erweiterbarkeitsmodelle abzielen. Wenn Ihre Erweiterung beispielsweise Visual Studio 2019 und Visual Studio 2022 unterstützen muss, können Sie die folgende Projektstruktur in Ihrer Projektmappe übernehmen:
        • MyExtension-VS2019 (dies ist Ihr VSSDK-basiertes VSIX-Containerprojekt, das auf Visual Studio 2019 ausgerichtet ist)
        • MyExtension-VS2022 (dies ist Ihr VSSDK+VisualStudio.Extensibility-basiertes VSIX-Containerprojekt, das auf Visual Studio 2022 ausgerichtet ist)
        • VSSDK-CommonCode (dies ist die allgemeine Bibliothek, die zum Aufrufen von Visual Studio-APIs über VSSDK verwendet wird. Beide VsIX-Projekte können auf diese Bibliothek verweisen, um Code zu teilen.)
        • MyExtension-BusinessLogic (dies ist die allgemeine Bibliothek, die den gesamten Code enthält, der für die Geschäftslogik Ihrer Erweiterung relevant ist. Beide VsIX-Projekte können auf diese Bibliothek verweisen, um Code zu teilen.)

Nächste Schritte

Unsere Empfehlung ist, dass Extender mit VisualStudio.Extensibility beginnen, wenn sie neue Erweiterungen erstellen oder vorhandene erweitern, und VSSDK oder das Community Toolkit verwenden, wenn sie in nicht unterstützten Szenarien auftreten. Um zu beginnen, durchsuchen Sie mit VisualStudio.Extensibility die in diesem Abschnitt vorgestellte Dokumentation. Beispiele finden Sie auch im VSExtensibility-GitHub-Repository, dort können Sie auch Probleme melden.