Výběr správného modelu rozšiřitelnosti sady Visual Studio pro vás
Visual Studio můžete rozšířit pomocí tří hlavních modelů rozšiřitelnosti, VSSDK, Community Toolkit a VisualStudio.Extensibility. Tento článek popisuje výhody a nevýhody každého z nich. Jednoduchý příklad používáme ke zvýraznění rozdílů v architektuře a kódu mezi modely.
VSSDK
VSSDK (nebo Visual Studio SDK) je modelem, na kterém je založena většina rozšíření na Visual Studio Marketplace. Tento model je základ, na kterém je postavena samotná sada Visual Studio. Je to nejúplnější a nejvýkonnější, ale také nejsložitější naučit se a používat správně. Rozšíření, která používají sadu VSSDK, běží ve stejném procesu jako samotná sada Visual Studio. Načítání ve stejném procesu jako Visual Studio znamená, že rozšíření, které má porušení přístupu, nekonečnou smyčku nebo jiné problémy, může způsobit zhroucení nebo zablokování Visual Studio a zhoršení uživatelského zážitku. A protože rozšíření běží ve stejném procesu jako Visual Studio, lze je sestavit pouze pomocí rozhraní .NET Framework. Rozšiřovatelé, kteří chtějí používat nebo začlenit knihovny, které používají .NET 5 a novějších, k tomu nemůžou použít VSSDK.
Rozhraní API v sadě VSSDK bylo v průběhu let agregováno, jak se Visual Studio samo transformovalo a vyvíjelo. V rámci jednoho rozšíření můžete narazit na rozhraní API založená na modelu COMze starší verze otisku, snadno projít klamnou jednoduchostí DTEa pohrávat si s MEF importy a exporty. Podívejme se na příklad zápisu přípony, která čte text ze systému souborů a vloží ho na začátek aktuálního aktivního dokumentu v editoru. Následující fragment kódu ukazuje kód, který byste napsali pro zpracování při vyvolání příkazu v rozšíření založeném na VSSDK:
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);
}
}
}
Kromě toho byste také museli zadat soubor .vsct
, který definuje konfiguraci příkazu, například kam ho umístit do uživatelského rozhraní, text přidružený atd.:
<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>
Jak vidíte v ukázce, může se kód zdát neintuitivní a je nepravděpodobné, že by někdo obeznámený s prostředím .NET jej snadno pochopil. Existuje mnoho konceptů, které se dají naučit a vzory rozhraní API pro přístup k aktivnímu textu editoru jsou antiquatované. U většiny rozšiřujících modulů se rozšíření VSSDK vytvářejí kopírováním a vkládáním z online zdrojů, což může vést k obtížnému ladění, metodě pokusů a omylů a frustraci. V mnoha případech nemusí být rozšíření VSSDK nejjednodušším způsobem, jak dosáhnout cílů rozšíření (i když někdy jsou jedinou volbou).
Nástrojová sada pro komunitu
Community Toolkit je komunitní open-sourceový model rozšiřitelnosti pro Visual Studio, který obaluje sadu VSSDK pro snadnější vývojové prostředí. Vzhledem k tomu, že je založená na sadě VSSDK, podléhá stejným omezením jako VSSDK (tj. pouze rozhraní .NET Framework, žádná izolace od zbytku sady Visual Studio atd.). Pokračováním ve stejném příkladu, jak vytvořit rozšíření, které vloží text přečtený ze souborového systému pomocí sady Community Toolkit, by toto rozšíření pro obslužnou rutinu příkazu bylo zapsáno následovně:
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);
}
Výsledný kód je z VSSDK mnohem vylepšen z hlediska jednoduchosti a intuitivnosti. Nejen že jsme výrazně snížili počet řádků, ale výsledný kód vypadá rozumně i. Není nutné pochopit, jaký je rozdíl mezi SVsTextManager
a IVsTextManager
. Rozhraní API jsou nyní více přátelská k .NET, zahrnují běžné vzory pojmenování a asynchronní vzory, spolu se stanovením priorit běžných operací. Komunitní sada nástrojů je ale stále postavená na stávajícím modelu VSSDK, a tudíž se projevují zbytky základní struktury. Například soubor .vsct
je stále nutný. Komunitní sada nástrojů sice skvěle zjednodušuje rozhraní API, ale je vázána na omezení sady VSSDK a nemá způsob, jak zjednodušit konfiguraci rozšíření.
VisualStudio.Extensibility
VisualStudio.Extensibility je nový model rozšiřitelnosti, kde rozšíření běží mimo hlavní proces sady Visual Studio. Vzhledem k tomuto zásadnímu architektonickému posunu jsou teď nové vzory a možnosti dostupné pro rozšíření, která nejsou možná v sadě VSSDK nebo Community Toolkit. VisualStudio.Extensibility nabízí zcela novou sadu rozhraní API, která jsou konzistentní a snadno použitelná, umožňuje rozšíření cílit na .NET, izoluje chyby, které vznikají z rozšíření ze zbytku sady Visual Studio, a umožňuje uživatelům instalovat rozšíření bez restartování sady Visual Studio. Vzhledem k tomu, že nový model je založený na nové základní architektuře, ještě nemá šířku, kterou má sada VSSDK a Community Toolkit. K přemostění této mezery můžete spustit rozšíření VisualStudio.Extensibility v procesu, což vám umožní pokračovat v používání rozhraní API sady VSSDK. To však znamená, že vaše rozšíření může cílit pouze na .NET Framework, protože sdílí stejný proces jako Visual Studio, který je založený na rozhraní .NET Framework.
Pokračujeme ve stejném příkladu vytváření rozšíření, které vloží text ze souboru pomocí VisualStudio.Extensibility. Rozšíření by bylo napsáno následujícím způsobem pro zpracování příkazů:
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);
}
}
Pokud chcete nakonfigurovat příkaz pro umístění, text atd., už nemusíte zadávat .vsct
soubor. Místo toho se provádí prostřednictvím kódu:
public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};
Tento kód je jednodušší na pochopení a sledování. Ve většině případů můžete toto rozšíření napsat čistě prostřednictvím editoru pomocí Technologie IntelliSense, a to i pro konfiguraci příkazů.
Porovnání různých modelů rozšiřitelnosti sady Visual Studio
Z ukázky si můžete všimnout toho, že při použití VisualStudio.Extensibility je v obslužné rutině příkazů více řádků kódu než při použití Community Toolkit. Community Toolkit je vynikající obálka snadného použití nad rozšířeními budov pomocí VSSDK; Existují však nástrahy, které nejsou okamžitě zřejmé, což je to, co vedlo k vývoji VisualStudio.Extensibility. Abychom pochopili přechod a potřebu, zejména když se zdá, že komunitní sada nástrojů umožňuje snadné psaní a porozumění kódu, podívejme se na příklad a porovnejme, co se děje v hlubších vrstvách kódu.
Kód v této ukázce můžeme rychle analyzovat a zjistit, jaké funkce se skutečně volají v rámci VSSDK. Zaměříme se výhradně na fragment kódu provádění příkazů, protože existuje mnoho podrobností, které VSSDK potřebuje, což Community Toolkit pěkně skryje. Jakmile se ale podíváme na základní kód, pochopíte, proč je tady jednoduchost kompromisem. Jednoduchost skryje některé základní podrobnosti, což může vést k neočekávanému chování, chybám a dokonce problémům s výkonem a chybovým ukončením. Následující fragment kódu ukazuje kód sady Community Toolkit, který se rozbalil a zobrazil volání VSSDK:
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);
}
}
});
}
Existuje několik problémů, které je třeba prozkoumat, a všechny se točí kolem vláknování a asynchronního kódu. Projdeme si jednotlivé podrobnosti.
Asynchronní rozhraní API versus asynchronní spouštění kódu
První věcí, kterou je potřeba poznamenat, je, že metoda ExecuteAsync
v komunitní sadě Nástrojů představuje zabalené asynchronní volání fire-and-forget v VSSDK:
package.JoinableTaskFactory.RunAsync(async delegate
{
…
});
Samotný VSSDK nepodporuje asynchronní spouštění příkazů z pohledu základního rozhraní API. To znamená, že když se spustí příkaz, VSSDK nemá způsob, jak spustit kód obslužné rutiny příkazu ve vlákně na pozadí, počkat na dokončení a vrátit uživatele do původního volajícího kontextu s výsledky spuštění. I když je rozhraní API ExecuteAsync v komunitní sadě Nástrojů syntakticky asynchronní, není to skutečné asynchronní spuštění. A protože se jedná o přístup „fire and forget“ pro asynchronní spuštění, můžete volat ExecuteAsync znovu a znovu, aniž byste museli čekat na dokončení předchozího volání. Komunitní sada nástrojů sice poskytuje lepší prostředí z hlediska pomoci rozšiřujícím uživatelům zjistit, jak implementovat běžné scénáře, ale nakonec nedokáže vyřešit základní problémy s VSSDK. V tomto případě základní rozhraní API sady VSSDK není asynchronní a pomocné metody typu fire-and-forget poskytované Community Toolkit nemohou správně zpracovávat asynchronní uvolňování a práci se stavem klienta; mohou skrývat některé potenciální těžko laditelné problémy.
Vlákno uživatelského rozhraní versus vlákno na pozadí
Další problém s tímto obaleným asynchronním voláním z Community Toolkitu je, že samotný kód se stále spouští z vlákna uživatelského rozhraní a je na vývojáři rozšíření, aby zjistil, jak správně přepnout na vlákno na pozadí, pokud nechce ohrozit zmrazení uživatelského rozhraní. Stejně jako Community Toolkit může skrýt šum a další kód VSSDK, stále vyžaduje, abyste porozuměli složitostem práce s vlákny v sadě Visual Studio. Jedna z prvních lekcí, které se naučíte při práci s vlákny ve Visual Studiu, je, že ne všechno lze spustit z pozadí. Jinými slovy, neplatí, že všechno je vláknově bezpečné, zejména volání, která přecházejí do COM komponent. V předchozím příkladu tedy vidíte, že probíhá přepnutí na hlavní vlákno uživatelského rozhraní (UI):
await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));
Po tomto volání můžete samozřejmě přepnout zpět na vlákno na pozadí. Jako rozšiřovatel pomocí nástroje Community Toolkit ale budete muset věnovat úzkou pozornost tomu, přes které běží vaše kódy vlákno, a určit, zda existuje riziko zamrznutí uživatelského rozhraní. Vícevláknové programování ve Visual Studiu je obtížné a vyžaduje správné použití JoinableTaskFactory
, aby nedocházelo k zablokování. Zápas o napsání kódu, který se správně zabývá vlákny, byl stálým zdrojem chyb, dokonce i pro naše interní inženýry Visual Studio. VisualStudio.Extensibility se na druhé straně tomuto problému úplně vyhýbá tím, že rozšiřující moduly běží mimo proces a spoléhá na asynchronní rozhraní API od začátku do konce.
Jednoduché rozhraní API a jednoduché koncepty
Vzhledem k tomu, že Komunitní sada nástrojů skrývá mnoho složitých funkcí sady VSSDK, mohla by rozšiřujícím objektům poskytnout falešný smysl pro jednoduchost. Pojďme pokračovat se stejným vzorovým kódem. Pokud extender nevěděl o požadavcích na zpracování vláken při vývoji v sadě Visual Studio, mohl by předpokládat, že je jejich kód spouštěn z vlákna na pozadí celou dobu. Nebudou mít žádný problém se skutečností, že volání pro čtení souboru z textu je synchronní. Pokud běží na pozadí, uživatelské rozhraní se nezablokuje, pokud je daný soubor velký. Když se ale kód rozbalí do sady VSSDK, zjistí, že tomu tak není. I když rozhraní API z Community Toolkit vypadá rozhodně jednodušeji k pochopení a kohezivněji k psaní, protože je sdružené s VSSDK, podléhá jeho omezením. Jednoduchosti mohou přehlédnout důležité koncepty, které pokud jsou nepochopeny rozšiřovateli, mohou způsobit větší škody. VisualStudio.Extensibility zabraňuje mnoha problémům způsobeným závislostmi na hlavním vlákně tím, že se soustředí na model mimo proces a asynchronní rozhraní API jako základ. I když by všechna vlákna běžela mimo hlavní proces, což by jejich zpracování nejvíce zjednodušilo, mnoho z těchto výhod se přenáší také na rozšíření běžící v rámci procesu. Příkazy VisualStudio.Extensibility se například vždy spouštějí na vlákně na pozadí. Interakce s rozhraními API VSSDK stále vyžaduje podrobné znalosti o tom, jak funguje práce s vlákny, ale aspoň nebudete čelit riziku náhodného zablokování, jako v tomto příkladu.
Srovnávací graf
Pokud chcete shrnout, co jsme podrobně probrali v předchozí části, ukazuje následující tabulka rychlé porovnání:
VSSDK | Community Toolkit | VisualStudio.Extensibility | |
---|---|---|---|
Podpora běhového prostředí | .NET Framework | .NET Framework | .NET |
Izolace ze sady Visual Studio | ❌ | ❌ | ✅ |
Jednoduché rozhraní API | ❌ | ✅ | ✅ |
asynchronní spouštění a rozhraní API | ❌ | ❌ | ✅ |
Šířka scénáře VS | ✅ | ✅ | ⏳ |
instalovatelné bez restartování | ❌ | ❌ | ✅ |
podporujeVS 2019 aníže | ✅ | ✅ | ❌ |
Abychom vám pomohli s porovnáním potřeb rozšiřitelnosti sady Visual Studio, tady jsou některé ukázkové scénáře a naše doporučení k použití modelu:
-
s vývojem rozšíření pro Visual Studio teprve začínám a chci nejsnadnější způsob, jak začít a vytvořit vysoce kvalitní rozšíření, apotřebujipodporovat Visual Studio 2022 nebo novější.
- V tomto případě doporučujeme použít VisualStudio.Extensibility.
-
chci napsat rozšíření, které cílí na Visual Studio 2022 a novější.VisualStudio.Extensibility ale nepodporuje všechny funkce, které potřebuji.
- V tomto případě doporučujeme použít hybridní metodu kombinování sady VisualStudio.Extensibility a VSSDK. Můžete vytvořit rozšíření VisualStudio.Extensibility, které běží v procesu, což umožňuje přístup k rozhraním API sady VSSDK nebo Community Toolkit.
-
mám existující rozšíření a chci ho aktualizovat tak, aby podporoval novější verze. Chci, aby moje rozšíření podporovalo co nejvíce verzí sady Visual Studio.
- Vzhledem k tomu, že VisualStudio.Extensibility podporuje pouze Visual Studio 2022 a novější, je v tomto případě nejlepší volbou sada VSSDK nebo Community Toolkit.
-
mám existující rozšíření, které chci migrovat naVisualStudio.Extensibility, aby bylo možné využít výhod .NET a nainstalovat bez restartování.
- Tento scénář je trochu nuančí, protože VisualStudio.Extensibility nepodporuje verze sady Visual Studio nižší úrovně.
- Pokud vaše stávající rozšíření podporuje jenom Visual Studio 2022 a má všechna potřebná rozhraní API, doporučujeme přepsat rozšíření tak, aby používalo VisualStudio.Extensibility. Pokud ale vaše rozšíření potřebuje rozhraní API, která visualStudio.Extensibility ještě nemá, pokračujte vytvořením rozšíření VisualStudio.Extensibility, které běží v procesu,, abyste měli přístup k rozhraním API sady VSSDK. Postupem času můžete eliminovat využití rozhraní API VSSDK, jak VisualStudio.Extensibility přidává podporu, a přesunout vaše rozšíření, aby běžela mimo proces.
- Pokud vaše rozšíření potřebuje podporovat verze sady Visual Studio nižší úrovně, které nepodporují VisualStudio.Extensibility, doporučujeme provést refaktoring v základu kódu. Stáhněte si veškerý společný kód, který lze sdílet napříč verzemi sady Visual Studio, do vlastní knihovny a vytvořte samostatné projekty VSIX, které cílí na různé modely rozšiřitelnosti. Pokud například vaše rozšíření potřebuje podporovat Visual Studio 2019 a Visual Studio 2022, můžete ve svém řešení přijmout následující strukturu projektu:
- MyExtension-VS2019 (toto je projekt kontejneru VSIX založený na VSSDK, který cílí na Visual Studio 2019)
- MyExtension-VS2022 (to je váš kontejner VSSDK+VisualStudio.Extensibility založený na VSIX, který cílí na Visual Studio 2022)
- VSSDK-CommonCode (jedná se o běžnou knihovnu, která se používá k volání rozhraní API sady Visual Studio prostřednictvím sady VSSDK. Oba projekty VSIX můžou odkazovat na tuto knihovnu a sdílet kód.)
- MyExtension-BusinessLogic (jedná se o společnou knihovnu, která obsahuje veškerý kód, který je relevantní pro obchodní logiku vašeho rozšíření. Oba projekty VSIX můžou odkazovat na tuto knihovnu a sdílet kód.)
- Tento scénář je trochu nuančí, protože VisualStudio.Extensibility nepodporuje verze sady Visual Studio nižší úrovně.
Další kroky
Naším doporučením je, aby vývojáři rozšíření začínali s VisualStudio.Extensibility při vytváření nových rozšíření nebo vylepšení existujících a v případě, že narazíte na nepodporované scénáře, použijte sadu VSSDK nebo Community Toolkit. Začněte tím, že s visualStudio.Extensibility přejdete do dokumentace uvedené v této části. Můžete také odkazovat na úložiště VSExtensibility na GitHubu pro ukázky nebo problémy.