Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
U kunt Visual Studio uitbreiden met behulp van drie belangrijkste uitbreidbaarheidsmodellen, VSSDK, Community Toolkit en VisualStudio.Extensibility. In dit artikel worden de voor- en nadelen van elk artikel behandeld. We gebruiken een eenvoudig voorbeeld om de verschillen tussen de architectuur en code tussen de modellen te benadrukken.
VSSDK
VSSDK (of Visual Studio SDK) is het model waarop de meeste extensies op de Visual Studio Marketplace- zijn gebaseerd. Dit model is waarop Visual Studio zelf is gebouwd. Het is de meest complete en krachtigste, maar ook het meest complexe om correct te leren en te gebruiken. Extensies die gebruikmaken van VSSDK worden uitgevoerd in hetzelfde proces als Visual Studio zelf. Laden in hetzelfde proces als Visual Studio betekent dat een extensie met een toegangsfout, oneindige lus of andere problemen Visual Studio kan vastlopen of crashen, waardoor de klantervaring verslechtert. En omdat extensies worden uitgevoerd in hetzelfde proces als Visual Studio, kunnen ze alleen worden gebouwd met behulp van .NET Framework-. Extenders die bibliotheken willen gebruiken of opnemen die gebruikmaken van .NET 5 en hoger, kunnen dit niet doen met VSSDK.
API's in VSSDK zijn in de loop der jaren samengevoegd naarmate Visual Studio zelf is getransformeerd en ontwikkeld. In één uitbreiding vindt u misschien uzelf worstelend met COM-gebaseerde API's van een oude imprint, met gemak door de misleidende eenvoud van DTEen tinkeren met MEF importen en exporten. Laten we een voorbeeld nemen van het schrijven van een extensie die de tekst uit het bestandssysteem leest en deze invoegt aan het begin van het huidige actieve document in de editor. In het volgende codefragment ziet u de code die u moet schrijven om af te handelen wanneer een opdracht wordt aangeroepen in een op VSSDK gebaseerde extensie:
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);
}
}
}
Daarnaast moet u ook een .vsct-bestand opgeven, waarmee de opdrachtconfiguratie wordt gedefinieerd, zoals waar u deze in de gebruikersinterface wilt plaatsen, de bijbehorende tekst, enzovoort:
<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>
Zoals u in het voorbeeld kunt zien, lijkt de code misschien niet intuïtief en is het onwaarschijnlijk dat iemand die bekend is met .NET, deze gemakkelijk kan oppakken. Er zijn veel concepten om te leren en de API-patronen voor toegang tot de actieve editortekst zijn verouderd. Voor de meeste extenders worden VSSDK-extensies samengesteld uit het kopiëren en plakken van online bronnen, wat kan leiden tot moeilijke foutopsporingssessies, trial and error en frustratie. In veel gevallen zijn VSSDK-extensies mogelijk niet de eenvoudigste manier om de uitbreidingsdoelen te bereiken (hoewel soms wel de enige keuze).
Community Toolkit
Community Toolkit is het open source, community-gebaseerde uitbreidbaarheidsmodel voor Visual Studio dat de VSSDK verpakt voor een eenvoudigere ontwikkelervaring. Omdat deze is gebaseerd op de VSSDK, gelden dezelfde beperkingen als VSSDK (alleen .NET Framework, geen isolatie van de rest van Visual Studio, enzovoort). Als u doorgaat met hetzelfde voorbeeld van het schrijven van een extensie die, met behulp van Community Toolkit, de tekst invoegt die is gelezen uit het besturingssysteem, dan wordt de extensie als volgt geschreven voor een opdrachthandler:
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);
}
De resulterende code is veel verbeterd van VSSDK in termen van eenvoud en intuïtiefheid! Niet alleen hebben we het aantal regels aanzienlijk verlaagd, maar de resulterende code ziet er ook redelijk uit. Het is niet nodig om te begrijpen wat het verschil is tussen SVsTextManager en IVsTextManager. De API's zien er meer uit. NET-vriendelijk, waarbij algemene naamgeving en asynchrone patronen worden gebruikt, samen met prioriteitstelling van algemene bewerkingen. Community Toolkit is echter nog steeds gebouwd op het bestaande VSSDK-model, waardoor sporen van de onderliggende structuur doorschemeren. Een .vsct-bestand is bijvoorbeeld nog steeds nodig. Hoewel Community Toolkit een uitstekende taak biedt om de API's te vereenvoudigen, is het gebonden aan de beperkingen van VSSDK en is er geen manier om de configuratie van extensies eenvoudiger te maken.
VisualStudio.Extensibility
VisualStudio.Extensibility is het nieuwe uitbreidbaarheidsmodel waarbij extensies buiten het belangrijkste Visual Studio-proces worden uitgevoerd. Vanwege deze fundamentele architectonische verschuiving zijn nieuwe patronen en mogelijkheden nu beschikbaar voor extensies die niet mogelijk zijn met VSSDK of Community Toolkit. VisualStudio.Extensibility biedt een volledig nieuwe set API's die consistent en gebruiksvriendelijk zijn, waarmee extensies kunnen worden gericht op .NET, fouten worden geïsoleerd die voortvloeien uit extensies van de rest van Visual Studio en waarmee gebruikers extensies kunnen installeren zonder Visual Studio opnieuw op te starten. Omdat het nieuwe model echter is gebouwd op nieuwe onderliggende architectuur, heeft het nog niet de breedte die VSSDK en de Community Toolkit hebben. Als u deze kloof wilt overbruggen, kunt u uw VisualStudio.Extensibility-extensies in procesuitvoeren, zodat u VSSDK-API's kunt blijven gebruiken. Dit betekent echter dat uw extensie alleen gericht kan zijn op .NET Framework, omdat deze hetzelfde proces deelt als Visual Studio, dat is gebaseerd op .NET Framework.
Als u doorgaat met hetzelfde voorbeeld van het schrijven van een extensie waarmee de tekst uit een bestand wordt ingevoegd met visualStudio.Extensibility, wordt de extensie als volgt geschreven voor het verwerken van opdrachten:
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);
}
}
Als u de opdracht voor plaatsing, tekst enzovoort wilt configureren, hoeft u geen .vsct bestand meer op te geven. In plaats daarvan wordt dit gedaan via code:
public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};
Deze code is gemakkelijker te begrijpen en te volgen. Voor het grootste deel kunt u deze extensie uitsluitend schrijven via de editor met behulp van IntelliSense, zelfs voor de opdrachtconfiguratie.
De verschillende Visual Studio-uitbreidingsmodellen vergelijken
In het voorbeeld ziet u mogelijk dat er met behulp van VisualStudio.Extensibility meer regels code zijn dan Community Toolkit in de opdrachthandler. Community Toolkit is een uitstekende gebruiksvriendelijke wrapper bovenop het bouwen van extensies met de VSSDK; Er zijn echter valkuilen die niet onmiddellijk duidelijk zijn, wat heeft geleid tot de ontwikkeling van VisualStudio.Extensibility. Om inzicht te krijgen in de overgang en behoefte, met name wanneer het lijkt alsof de Community Toolkit ook code oplevert die gemakkelijk te schrijven en te begrijpen is, gaan we het voorbeeld bekijken en vergelijken wat er gebeurt in de diepere lagen van de code.
We kunnen de code in dit voorbeeld snel uitpakken en zien wat er daadwerkelijk aan de VSSDK-zijde wordt aangeroepen. We richten ons alleen op het fragment voor het uitvoeren van opdrachten, omdat er talloze details zijn die VSSDK nodig heeft, die Community Toolkit mooi verbergt. Maar zodra we de onderliggende code bekijken, begrijpt u waarom de eenvoud hier een compromis is. De eenvoud verbergt enkele onderliggende details, wat kan leiden tot onverwacht gedrag, bugs en zelfs prestatieproblemen en crashes. In het volgende codefragment wordt de Community Toolkit-code weergegeven die is uitgepakt om de VSSDK-aanroepen weer te geven:
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);
}
}
});
}
Er zijn slechts enkele problemen om hier op in te gaan, en ze draaien allemaal om multi-threading en asynchrone code. We gaan elk detail doornemen.
Asynchrone API versus asynchrone code-uitvoering
Het is belangrijk om te weten dat de ExecuteAsync-methode in de Community Toolkit een ingepakte asynchrone fire-and-forget-aanroep in VSSDK is.
package.JoinableTaskFactory.RunAsync(async delegate
{
…
});
VSSDK zelf biedt geen ondersteuning voor het uitvoeren van asynchrone opdrachten vanuit het perspectief van de kern-API. Als een opdracht wordt uitgevoerd, heeft VSSDK geen manier om de opdrachthandlercode uit te voeren op een achtergrondthread, te wachten tot deze is voltooid en de gebruiker terug te sturen naar de oorspronkelijke aanroepende context met uitvoeringsresultaten. Hoewel de ExecuteAsync-API in de Community Toolkit syntactisch asynchroon is, is het geen echte asynchrone uitvoering. En omdat het een manier is van asynchrone uitvoering zonder dat je verder hoeft na te denken, kunt u ExecuteAsync herhaaldelijk aanroepen zonder te wachten tot de vorige aanroep is voltooid. Hoewel Community Toolkit een betere ervaring biedt met betrekking tot het helpen van extenders om te ontdekken hoe algemene scenario's kunnen worden geïmplementeerd, kan het uiteindelijk de fundamentele problemen met VSSDK niet oplossen. In dit geval is de onderliggende VSSDK-API niet asynchroon, en kunnen de fire-and-forget-methoden van de Community Toolkit niet goed omgaan met asynchrone operaties en het beheren van clientstatus; dit kan potentieel moeilijk op te sporen problemen verhullen.
UI-thread versus achtergrond-thread
De andere fallout met deze verpakte asynchrone aanroep van Community Toolkit is dat de code zelf nog steeds wordt uitgevoerd vanuit de UI-thread en het is aan de ontwikkelaar van de extensie om erachter te komen hoe u correct kunt overschakelen naar een achtergrondthread als u geen risico wilt lopen dat de gebruikersinterface wordt geblokkeerd. Net zoals Community Toolkit de ruis en extra code van VSSDK kan verbergen, moet u nog steeds de complexiteit van threading in Visual Studio begrijpen. En een van de eerste lessen die u in VS threading leert, is dat niet alles kan worden uitgevoerd vanuit een achtergrondthread. Met andere woorden, niet alles is thread-safe, met name de aanroepen naar COM-onderdelen. In het bovenstaande voorbeeld ziet u dat er een aanroep is om over te schakelen naar de hoofdthread (UI):
await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));
U kunt na deze aanroep natuurlijk teruggaan naar een achtergrondthread. Als u als extender werkt met de Community Toolkit, moet u nauwlettend in de gaten houden op welke thread uw code zich bevindt en bepalen of er een risico is dat de gebruikersinterface bevriest. Threading in Visual Studio is moeilijk goed te doen en vereist het juiste gebruik van JoinableTaskFactory om deadlocks te voorkomen. De moeite om code te schrijven die correct threading verwerkt, vormt een constante bron van bugs, zelfs voor onze interne Visual Studio-ingenieurs. VisualStudio.Extensibility voorkomt dit probleem echter volledig door extensies buiten de hoofdprocessen uit te voeren en volledig te vertrouwen op asynchrone API's.
Eenvoudige API versus eenvoudige concepten
Omdat Community Toolkit veel van de complexiteit van VSSDK verbergt, kan het extenders een onwaar gevoel van eenvoud geven. Laten we doorgaan met dezelfde voorbeeldcode. Als een extender niet wist wat de threadingvereisten van Visual Studio-ontwikkeling zijn, kunnen ze ervan uitgaan dat hun code gedurende de hele tijd wordt uitgevoerd vanaf een achtergrondthread. Ze nemen geen probleem met het feit dat de aanroep om een bestand uit tekst te lezen synchroon is. Als het zich op een achtergrondthread bevindt, dan zal de gebruikersinterface niet blokkeren als het betreffende bestand groot is. Wanneer de code echter wordt uitgepakt voor VSSDK, realiseren ze zich dat dit niet het geval is. Dus hoewel de API van Community Toolkit er zeker eenvoudiger uitziet om te begrijpen en samenhangender is om te schrijven, omdat deze is gekoppeld aan VSSDK, is deze onderhevig aan VSSDK-beperkingen. De eenvoud kan belangrijke concepten verdoezelen die, als extenders het niet begrijpen, meer schade kunnen veroorzaken. VisualStudio.Extensibility voorkomt de vele problemen die worden veroorzaakt door hoofdthreadafhankelijkheden door te focussen op het out-of-process model en asynchrone API's als basis. Hoewel het uitvoeren van threading buiten het proces het meest zou vereenvoudigen, worden veel van deze voordelen ook overgedragen naar extensies die binnen het proces worden uitgevoerd. VisualStudio.Extensibility-opdrachten worden bijvoorbeeld altijd uitgevoerd op een achtergrondthread. Interactie met VSSDK-API's vereist nog steeds diepgaande kennis van de werking van threading, maar u betaalt in ieder geval niet de kosten van onbedoelde vastlopen, zoals in dit voorbeeld.
Vergelijkingsgrafiek
De volgende tabel bevat een snelle vergelijking om samen te vatten wat we in de vorige sectie hebben behandeld:
| VSSDK | Community Toolkit | VisualStudio.Extensibility | |
|---|---|---|---|
| Runtime-ondersteuning | .NET Framework | .NET Framework | .NET |
| Afzondering van Visual Studio | ❌ | ❌ | ✅ |
| Simple API- | ❌ | ✅ | ✅ |
| Asynchrone uitvoering en API- | ❌ | ❌ | ✅ |
| VS Scenario Breedte | ✅ | ✅ | ⏳ |
| kan worden geïnstalleerd zonder opnieuw op te starten | ❌ | ❌ | ✅ |
| ondersteuntVS 2019 enHieronder | ✅ | ✅ | ❌ |
Hier volgen enkele voorbeeldscenario's en onze aanbevelingen voor het gebruik van het model om de vergelijking toe te passen op uw visual Studio-uitbreidingsbehoeften:
-
ik geen ervaring heb met het ontwikkelen van Visual Studio-extensies en ik wil de eenvoudigste onboarding-ervaring om een extensie van hoge kwaliteit te maken, en ikalleen Visual Studio 2022 of hoger moetenondersteunen.
- In dit geval raden we u aan VisualStudio.Extensibility te gebruiken.
-
ik wil een extensie schrijven die is gericht op Visual Studio 2022 en hoger.VisualStudio.Extensibility biedt echter geen ondersteuning voor allefunctionaliteit die ik nodig heb.
- We raden u aan om in dit geval een hybride methode te gebruiken voor het combineren van VisualStudio.Extensibility en VSSDK. U kunt een VisualStudio.Extensibility-extensie maken die wordt uitgevoerd in proces-, waarmee u toegang hebt tot VSSDK- of Community Toolkit-API's.
-
ik een bestaande extensie heb en deze wil bijwerken om nieuwere versies te ondersteunen. Ik wil dat mijn extensie zoveel mogelijk versies van Visual Studio ondersteunt.
- Omdat VisualStudio.Extensibility alleen Visual Studio 2022 en hoger ondersteunt, is VSSDK of Community Toolkit de beste optie voor dit geval.
-
ik een bestaande extensie heb die ik wil migreren naarVisualStudio.Extensibility om te profiteren van .NET en te installeren zonder opnieuw op te starten.
- Dit scenario is iets genuanceerder omdat VisualStudio.Extensibility geen ondersteuning biedt voor downlevel versies van Visual Studio.
- Als uw bestaande extensie alleen Visual Studio 2022 ondersteunt en alle API's bevat die u nodig hebt, raden we u aan uw extensie opnieuw te schrijven om VisualStudio.Extensibility te gebruiken. Maar als uw extensie API's nodig heeft die VisualStudio.Extensibility nog niet heeft, gaat u verder met het maken van een VisualStudio.Extensibility-extensie die wordt uitgevoerd in proces zodat u toegang hebt tot VSSDK-API's. Na verloop van tijd kunt u het gebruik van de VSSDK-API elimineren, omdat VisualStudio.Extensibility ondersteuning toevoegt en uw extensies verplaatst om buiten het huidige proces te draaien.
- Als uw extensie ondersteuning moet bieden voor downlevel versies van Visual Studio die geen ondersteuning hebben voor VisualStudio.Extensibility, raden we u aan om een aantal herstructureringen uit te voeren in uw codebasis. Haal alle algemene code op die in Visual Studio-versies kan worden gedeeld naar een eigen bibliotheek en maak afzonderlijke VSIX-projecten die gericht zijn op verschillende uitbreidbaarheidsmodellen. Als uw extensie bijvoorbeeld Visual Studio 2019 en Visual Studio 2022 moet ondersteunen, kunt u de volgende projectstructuur in uw oplossing gebruiken:
- MyExtension-VS2019 (dit is uw VSSDK-containerproject op basis van VSIX dat is gericht op Visual Studio 2019)
- MyExtension-VS2022 (dit is uw VSSDK+VisualStudio.Extensibility-containerproject dat is gebaseerd op Visual Studio 2022)
- VSSDK-CommonCode (dit is de algemene bibliotheek die wordt gebruikt om Visual Studio-API's aan te roepen via VSSDK. Beide VSIX-projecten kunnen verwijzen naar deze bibliotheek om code te delen.)
- MyExtension-BusinessLogic (dit is de algemene bibliotheek die alle code bevat die relevant is voor de bedrijfslogica van uw extensie. Beide VSIX-projecten kunnen verwijzen naar deze bibliotheek om code te delen.)
- Dit scenario is iets genuanceerder omdat VisualStudio.Extensibility geen ondersteuning biedt voor downlevel versies van Visual Studio.
Volgende stappen
Onze aanbeveling is dat extenders beginnen met VisualStudio.Extensibility bij het maken van nieuwe extensies of het verbeteren van bestaande extensies en het gebruik van VSSDK of de Community Toolkit als u niet-ondersteunde scenario's uitvoert. Als u aan de slag wilt gaan, bladert u met VisualStudio.Extensibility door de documentatie die in deze sectie wordt weergegeven. U kunt ook verwijzen naar de GITHub-opslagplaats vsExtensibility voor voorbeelden of om problemenop te slaan.