Dela via


Välj rätt Utökningsmodell för Visual Studio åt dig

Du kan utöka Visual Studio med tre huvudsakliga utökningsmodeller, VSSDK, Community Toolkit och VisualStudio.Extensibility. Den här artikeln beskriver för- och nackdelarna med var och en. Vi använder ett enkelt exempel för att markera arkitektur- och kodskillnaderna mellan modellerna.

VSSDK

VSSDK (eller Visual Studio SDK) är den modell som de flesta tillägg på Visual Studio Marketplace baseras på. Den här modellen är vad Visual Studio bygger på. Det är det mest kompletta och mest kraftfulla, men också det mest komplexa att lära sig och använda korrekt. Tillägg som använder VSSDK körs i samma process som Själva Visual Studio. Inläsning i samma process som Visual Studio innebär att ett tillägg som har en åtkomstöverträdelse, oändlig loop eller andra problem kan krascha eller hänga Visual Studio och försämra kundupplevelsen. Och eftersom tillägg körs i samma process som Visual Studio kan de bara skapas med hjälp av .NET Framework-. Utökare som vill använda eller införliva bibliotek som använder .NET 5 och senare kan inte göra det med VSSDK.

API:er i VSSDK har sammanställts under åren när Visual Studio självt transformerats och utvecklats. I ett enda tillägg kan du komma att brottas med COM--baserade API:er från äldre teknik, glida igenom den bedrägliga enkelheten i DTEoch justera MEF importer och exporter. Låt oss ta ett exempel på hur du skriver ett tillägg som läser texten från filsystemet och infogar den i början av det aktuella aktiva dokumentet i redigeraren. Följande kodfragment visar den kod som du skulle skriva för att hantera när ett kommando anropas i ett VSSDK-baserat tillägg:

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);
        }
    }
}

Dessutom skulle du också behöva ange en .vsct fil som definierar kommandokonfigurationen, till exempel var den ska placeras i användargränssnittet, den text som är associerad och så vidare:

<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>

Som du ser i exemplet kan koden verka ointuitiv och det är osannolikt att någon som är bekant med .NET enkelt kan hämta den. Det finns många begrepp att lära sig och API-mönstren för att komma åt den aktiva redigeringstexten är föråldrade. För de flesta utvecklare konstrueras VSSDK-tillägg genom att kopiera och klistra in från onlinekällor, vilket kan leda till svåra felsökningssessioner, försök och misstag samt frustration. I många fall är VSSDK-tillägg kanske inte det enklaste sättet att uppnå tilläggsmålen (även om de ibland är det enda valet).

Gemenskapsverktygslåda

Community Toolkit är den communitybaserade utökningsmodellen med öppen källkod för Visual Studio som omsluter VSSDK för en enklare utvecklingsupplevelse. Eftersom den baseras på VSSDK omfattas den av samma begränsningar som VSSDK (dvs. endast .NET Framework, ingen isolering från resten av Visual Studio och så vidare). Om du fortsätter med samma exempel på att skriva ett tillägg som infogar texten som lästs från filsystemet, med hjälp av Community Toolkit, skulle tillägget skrivas på följande sätt för en kommandohanterare:

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);
}

Den resulterande koden har förbättrats avsevärt från VSSDK när det gäller enkelhet och intuitivitet! Vi minskade inte bara antalet rader avsevärt, utan den resulterande koden ser också rimlig ut. Det finns ingen anledning att förstå vad skillnaden är mellan SVsTextManager och IVsTextManager. API:erna ser ut och känns mer . NET-vänlig, omfattar vanliga namngivnings- och asynkrona mönster, tillsammans med prioritering av vanliga åtgärder. Community Toolkit bygger dock fortfarande på den befintliga VSSDK-modellen, så spåren av den underliggande strukturen blöder igenom. Till exempel är en .vsct fil fortfarande nödvändig. Community Toolkit gör ett bra jobb med att förenkla API:erna, men det är bundet till begränsningarna i VSSDK och kan inte göra tilläggskonfigurationen enklare.

VisualStudio.Extensibility

VisualStudio.Extensibility är den nya utökningsmodellen där tillägg körs utanför visual studio-huvudprocessen. På grund av detta grundläggande arkitekturskifte är nya mönster och funktioner nu tillgängliga för tillägg som inte är möjliga med VSSDK eller Community Toolkit. VisualStudio.Extensibility erbjuder en helt ny uppsättning API:er som är konsekventa och enkla att använda, gör det möjligt för tillägg att rikta in sig på .NET, isolerar buggar som uppstår från tillägg från resten av Visual Studio och gör det möjligt för användare att installera tillägg utan att starta om Visual Studio. Men eftersom den nya modellen bygger på en ny underliggande arkitektur har den ännu inte den bredd som VSSDK och Community Toolkit har. Om du vill överbrygga det gapet kan du köra visualStudio.Extensibility-tilläggen i processen, vilket gör att du kan fortsätta använda VSSDK-API:er. Detta innebär dock att tillägget bara kan rikta in sig på .NET Framework eftersom det delar samma process som Visual Studio, som baseras på .NET Framework.

Om du fortsätter med samma exempel på att skriva ett tillägg som infogar texten från en fil med hjälp av VisualStudio.Extensibility, skulle tillägget skrivas på följande sätt för kommandohantering:

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);
                
    }
}

För att konfigurera kommandot för placering, text och så vidare behöver du inte längre ange en .vsct fil. I stället görs det via kod:

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

Den här koden är lättare att förstå och följa. För det mesta kan du skriva det här tillägget enbart via redigeraren med hjälp av IntelliSense, även för kommandokonfiguration.

Jämföra de olika Utökningsmodellerna för Visual Studio

I exemplet kanske du märker att det finns fler kodrader än Community Toolkit i kommandohanteraren med VisualStudio.Extensibility. Community Toolkit är en utmärkt omslutning för enklare användning för utveckling av tillägg med VSSDK; Det finns dock vissa fallgropar som inte är omedelbart uppenbara, vilket ledde till utvecklingen av VisualStudio.Extensibility. För att förstå övergången och behovet, särskilt när det verkar som om Community Toolkit också resulterar i kod som är lätt att skriva och förstå, ska vi granska exemplet och jämföra vad som händer i de djupare lagren i koden.

Vi kan snabbt packa upp koden i det här exemplet och se vad som faktiskt anropas på VSSDK-sidan. Vi ska fokusera enbart på kommandokörningsfragmentet eftersom det finns många detaljer som VSSDK behöver, vilket Community Toolkit döljer fint. Men när vi tittar på den underliggande koden förstår du varför enkelheten här är en kompromiss. Enkelheten döljer en del av den underliggande informationen, vilket kan leda till oväntat beteende, buggar och till och med prestandaproblem och krascher. Följande kodfragment visar Community Toolkit-koden som har packats upp för att visa VSSDK-anropen:

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);    
            }
        }
    });
}

Det finns få problem att komma in på här, och alla kretsar kring trådning och asynkron kod. Vi går igenom var och en i detalj.

Async API jämfört med asynkron kodkörning

Det första du bör tänka på är att metoden ExecuteAsync i Community Toolkit är ett kapslat asynkront "fire-and-forget"-anrop i VSSDK.

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

SJÄLVA VSSDK stöder inte asynkron kommandokörning ur ett kärn-API-perspektiv. När ett kommando körs har VSSDK inte något sätt att köra kommandohanterarkoden på en bakgrundstråd, vänta tills den har slutförts och returnera användaren till den ursprungliga samtalskontexten med körningsresultat. Så även om ExecuteAsync-API:et i Community Toolkit är syntaktiskt asynkront är det inte en sann asynkron körning. Och eftersom det är ett asynkront körsätt där man inte behöver vänta på tidigare anrop, kan du anropa ExecuteAsync om och om igen utan att någonsin vänta på att föregående anrop ska slutföras först. Community Toolkit ger en bättre upplevelse när det gäller att hjälpa förlängare att upptäcka hur man implementerar vanliga scenarier, men det kan i slutändan inte lösa de grundläggande problemen med VSSDK. I det här fallet är det underliggande VSSDK-API:et inte asynkront, och de fire-and-forget-hjälpmetoder som tillhandahålls av Community Toolkit kan inte korrekt hantera asynkron hantering och arbete med klienttillstånd; det kan dölja vissa potentiella svårfelsökta problem.

UI-tråd kontra bakgrundstråd

Den andra nackdelen med det här inkapslade asynkrona anropet från Community Toolkit är att själva koden fortfarande körs från användargränssnittstråden, och det är upp till tilläggsutvecklaren att ta reda på hur man korrekt växlar till en bakgrundstråd om man inte vill riskera att frysa användargränssnittet. Så mycket som Community Toolkit kan dölja bruset och extra kod för VSSDK, kräver det fortfarande att du förstår komplexiteten i trådning i Visual Studio. Och en av de första lärdomarna du lär dig i VS-trådning är att inte allt kan köras från en bakgrundstråd. Med andra ord är inte allt trådsäkert, särskilt de anrop som går in i COM-komponenter. I exemplet ovan ser du att det finns ett anrop för att växla till huvudtråden (UI):

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

Du kan naturligtvis växla tillbaka till en bakgrundstråd efter det här anropet. Men som förlängare med hjälp av Community Toolkit måste du vara uppmärksam på vilken tråd koden är på och avgöra om den riskerar att frysa användargränssnittet. Trådning i Visual Studio är svårt att få rätt och kräver korrekt användning av JoinableTaskFactory för att undvika dödlägen. Kampen för att skriva kod som hanterar trådning korrekt har varit en ständig källa till buggar, även för våra interna Visual Studio-tekniker. VisualStudio.Extensibility undviker å andra sidan problemet helt och hållet genom att köra tillägg ur processen och förlita sig på asynkrona API:er från slutpunkt till slutpunkt.

Enkla API jämfört med enkla begrepp

Eftersom Community Toolkit döljer många av invecklingarna i VSSDK kan det ge förlängarna en falsk känsla av enkelhet. Låt oss fortsätta med samma exempelkod. Om en förlängare inte kände till trådkraven för Visual Studio-utveckling kan de anta att koden körs från en bakgrundstråd hela tiden. De har inga problem med att anropet för att läsa en fil från text är synkront. Om den finns i en bakgrundstråd låses inte användargränssnittet om filen i fråga är stor. Men när koden packas upp till VSSDK inser de att så inte är fallet. Så även om API:et från Community Toolkit verkligen ser enklare ut att förstå och mer sammanhängande att skriva, eftersom det är kopplat till VSSDK, omfattas det av VSSDK-begränsningar. De förenklingarna kan släta över viktiga begrepp som, om användarna inte förstår dem, kan orsaka mer skada. VisualStudio.Extensibility undviker de många problem som orsakas av huvudtrådsberoenden genom att fokusera på out-of-process-modellen och asynkrona API:er som grund. Genom att köra utanför processen skulle trådhanteringen förenklas mest, men många av dessa fördelar överförs även till tillägg som körs inom processen. Till exempel körs VisualStudio.Extensibility-kommandon alltid på en bakgrundstråd. Att interagera med VSSDK-API:er kräver fortfarande djupgående kunskaper om hur trådning fungerar, men du betalar åtminstone inte kostnaden för oavsiktliga hängningar, som i det här exemplet.

Jämförelsediagram

För att sammanfatta vad vi gick igenom i detalj i föregående avsnitt visar följande tabell en snabb jämförelse:

VSSDK Verktygslåda för communityn VisualStudio.Extensibility
Körtidsstöd .NET Framework .NET Framework .NÄT
Avskildhet från Visual Studio
Simple API-
Async-körning och API
VS Scenariobredd
kan installeras utan omstart
stöderVS 2019 ochnedan

Här följer några exempelscenarier och våra rekommendationer för vilken modell du ska använda för att använda jämförelsen med dina utökningsbehov i Visual Studio:

  • jag är nybörjare på utveckling av Visual Studio-tillägg, och jag vill ha den enklaste introduktionsupplevelsen för att skapa ett tillägg av hög kvalitet, och jag behöver barabehöverstödja Visual Studio 2022 eller senare.
    • I det här fallet rekommenderar vi att du använder VisualStudio.Extensibility.
  • jag vill skriva ett tillägg som riktar sig till Visual Studio 2022 och senare. MenVisualStudio.Extensibility stöder inte allafunktioner som jag behöver.
    • Vi rekommenderar att du i det här fallet använder en hybridmetod för att kombinera VisualStudio.Extensibility och VSSDK. Du kan skapa ett VisualStudio.Extensibility-tillägg som körs, vilket gör att du kan komma åt VSSDK- eller Community Toolkit-API:er.
  • jag har ett befintligt tillägg och vill uppdatera det för att stödja nyare versioner. Jag vill att mitt tillägg ska stödja så många versioner av Visual Studio som möjligt.
    • Eftersom VisualStudio.Extensibility endast stöder Visual Studio 2022 och senare är VSSDK eller Community Toolkit det bästa alternativet för det här fallet.
  • jag har ett befintligt tillägg som jag vill migrera tillVisualStudio.Extensibility för att dra nytta av .NET och installera utan omstart.
    • Det här scenariot är lite mer nyanserat eftersom VisualStudio.Extensibility inte stöder nednivåversioner av Visual Studio.
      • Om ditt befintliga tillägg endast stöder Visual Studio 2022 och har alla API:er som du behöver, rekommenderar vi att du skriver om tillägget för att använda VisualStudio.Extensibility. Men om tillägget behöver API:er som VisualStudio.Extensibility ännu inte har, kan du gå vidare och skapa ett VisualStudio.Extensibility-tillägg som körs i så att du kan komma åt VSSDK-API:er. Med tiden kan du eliminera VSSDK API-användning eftersom VisualStudio.Extensibility lägger till stöd och flytta dina tillägg till att köras utanför processen.
      • Om tillägget behöver stöd för nednivåversioner av Visual Studio som inte har stöd för VisualStudio.Extensibility rekommenderar vi att du omstrukturerar kodbasen. Hämta all vanlig kod som kan delas mellan Visual Studio-versioner till ett eget bibliotek och skapa separata VSIX-projekt som riktar sig mot olika utökningsmodeller. Om ditt tillägg till exempel behöver stöd för Visual Studio 2019 och Visual Studio 2022 kan du använda följande projektstruktur i din lösning:
        • MyExtension-VS2019 (det här är ditt VSSDK-baserade VSIX-containerprojekt som riktar sig mot Visual Studio 2019)
        • MyExtension-VS2022 (det här är ditt VSSDK+VisualStudio.Extensibility-baserade VSIX-containerprojekt som riktar sig till Visual Studio 2022)
        • VSSDK-CommonCode (det här är det vanliga biblioteket som används för att anropa Visual Studio-API:er via VSSDK. Båda dina VSIX-projekt kan referera till det här biblioteket för att dela kod.)
        • MyExtension-BusinessLogic (det här är det gemensamma biblioteket som innehåller all kod som är relevant för tilläggets affärslogik. Båda dina VSIX-projekt kan referera till det här biblioteket för att dela kod.)

Nästa steg

Vår rekommendation är att utökare börjar med VisualStudio.Extensibility när du skapar nya tillägg eller förbättrar befintliga och använder VSSDK eller Community Toolkit om du stöter på scenarier som inte stöds. För att komma igång med VisualStudio.Extensibility bläddrar du igenom dokumentationen som visas i det här avsnittet. Du kan också referera till GITHub-lagringsplatsen VSExtensibility för för exempel eller filfel .