Wybieranie odpowiedniego modelu rozszerzalności programu Visual Studio
Program Visual Studio można rozszerzyć przy użyciu trzech głównych modeli rozszerzalności, VSSDK, Community Toolkit i VisualStudio.Extensibility. W tym artykule omówiono zalety i wady poszczególnych elementów. Używamy prostego przykładu, aby wyróżnić różnice architektury i kodu między modelami.
VSSDK
Zestaw VSSDK (lub zestaw Visual Studio SDK) to model, na podstawie którego opiera się większość rozszerzeń w witrynie Visual Studio Marketplace . Ten model jest oparty na samym programie Visual Studio. Jest to najbardziej kompletne i najbardziej zaawansowane, ale także najbardziej złożone, aby nauczyć się i używać poprawnie. Rozszerzenia korzystające z zestawu VSSDK są uruchamiane w tym samym procesie co sam program Visual Studio. Ładowanie w tym samym procesie co program Visual Studio oznacza, że rozszerzenie, które ma naruszenie dostępu, nieskończoną pętlę lub inne problemy, mogą ulec awarii lub zawiesić program Visual Studio i obniżyć wydajność środowiska klienta. Ponieważ rozszerzenia działają w tym samym procesie co program Visual Studio, można je skompilować tylko przy użyciu programu .NET Framework. Rozszerzenia, które chcą korzystać z bibliotek platformy .NET 5 lub nowszej, nie mogą tego zrobić przy użyciu zestawu VSSDK.
Interfejsy API w zestawie VSSDK zostały zagregowane na przestrzeni lat, ponieważ program Visual Studio sam przekształcił się i ewoluował. W jednym rozszerzeniu możesz samodzielnie zmagać się z interfejsami API opartymi na modelu COM ze starszego odcisku, przechodząc przez zwodniczą prostotę DTE i majstrując importami i eksportami MEF . Przyjrzyjmy się przykładowi pisania rozszerzenia, które odczytuje tekst z systemu plików i wstawia go na początku bieżącego aktywnego dokumentu w edytorze. Poniższy fragment kodu przedstawia kod, który można napisać do obsługi, gdy polecenie jest wywoływane w rozszerzeniu opartym na zestawie 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);
}
}
}
Ponadto należy również podać plik, który definiuje konfigurację .vsct
polecenia, na przykład gdzie umieścić go w interfejsie użytkownika, skojarzony tekst itd.:
<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 widać w przykładzie, kod może wydawać się niezamierzony i jest mało prawdopodobne, aby ktoś zaznajomiony z platformą .NET mógł łatwo odebrać. Istnieje wiele pojęć, które należy poznać, a wzorce interfejsu API umożliwiające dostęp do aktywnego tekstu edytora są przestarzałe. W przypadku większości rozszerzeń rozszerzenia VSSDK są tworzone z kopiowania i wklejania ze źródeł online, co może prowadzić do trudnych sesji debugowania, prób i błędów oraz frustracji. W wielu przypadkach rozszerzenia VSSDK mogą nie być najprostszym sposobem osiągnięcia celów rozszerzenia (choć czasami są one jedynym wyborem).
Community Toolkit
Community Toolkit to oparty na społeczności model rozszerzalności typu open source dla programu Visual Studio, który opakowuje zestaw VSSDK w celu ułatwienia programowania. Ponieważ jest on oparty na zestawie VSSDK, podlega tym samym ograniczeniom co zestaw VSSDK (czyli tylko .NET Framework, bez izolacji od reszty programu Visual Studio itd.). Kontynuując ten sam przykład pisania rozszerzenia, które wstawia tekst odczytany z systemu plików przy użyciu zestawu narzędzi Community Toolkit, rozszerzenie zostanie zapisane w następujący sposób dla programu obsługi poleceń:
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);
}
Wynikowy kod jest znacznie ulepszony z zestawu VSSDK pod względem prostoty i intuicyjności! Nie tylko znacznie zmniejszyliśmy liczbę wierszy, ale wynikowy kod również wygląda rozsądnie. Nie ma potrzeby zrozumienia, jaka jest różnica między SVsTextManager
i IVsTextManager
. Interfejsy API wyglądają i czują się bardziej . Przyjazne dla sieci, obejmujące typowe wzorce nazewnictwa i asynchroniczne, wraz z priorytetyzacji typowych operacji. Jednak zestaw narzędzi Community Toolkit jest nadal oparty na istniejącym modelu VSSDK, a więc elementy bazowej struktury krwawią. Na przykład .vsct
plik jest nadal konieczny. Podczas gdy zestaw narzędzi Community Toolkit doskonale upraszcza interfejsy API, jest on powiązany z ograniczeniami zestawu VSSDK i nie ma możliwości uproszczenia konfiguracji rozszerzenia.
VisualStudio.Extensibility
VisualStudio.Extensibility to nowy model rozszerzalności, w którym rozszerzenia działają poza głównym procesem programu Visual Studio. Ze względu na tę podstawową zmianę architektury nowe wzorce i możliwości są teraz dostępne dla rozszerzeń, które nie są możliwe w zestawie narzędzi VSSDK lub Community Toolkit. Program VisualStudio.Extensibility oferuje zupełnie nowy zestaw interfejsów API, które są spójne i łatwe w użyciu, umożliwia rozszerzenia docelowe platformy .NET, izoluje błędy wynikające z rozszerzeń z pozostałej części programu Visual Studio i umożliwia użytkownikom instalowanie rozszerzeń bez ponownego uruchamiania programu Visual Studio. Jednak ponieważ nowy model jest oparty na nowej architekturze bazowej, nie ma jeszcze zakresu, który ma zestaw VSSDK i zestaw narzędzi Community Toolkit. Aby wypełnić tę lukę, możesz uruchomić rozszerzenia VisualStudio.Extensibility w procesie, co umożliwia kontynuowanie korzystania z interfejsów API zestawu VSSDK. Jednak oznacza to, że rozszerzenie może być przeznaczone tylko dla programu .NET Framework, ponieważ współudzieli ten sam proces co program Visual Studio, który jest oparty na programie .NET Framework.
Kontynuując ten sam przykład pisania rozszerzenia, które wstawia tekst z pliku przy użyciu rozszerzenia VisualStudio.Extensibility, rozszerzenie zostanie napisane w następujący sposób w celu obsługi poleceń:
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);
}
}
Aby skonfigurować polecenie do umieszczania, tekstu itd., nie trzeba już podawać .vsct
pliku. Zamiast tego odbywa się to za pomocą kodu:
public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};
Ten kod jest łatwiejszy do zrozumienia i śledzenia. W większości przypadków można napisać to rozszerzenie wyłącznie za pośrednictwem edytora za pomocą funkcji IntelliSense, nawet w przypadku konfiguracji poleceń.
Porównanie różnych modeli rozszerzalności programu Visual Studio
W tym przykładzie można zauważyć, że w programie obsługi poleceń istnieje więcej wierszy kodu niż zestaw narzędzi Community Toolkit. Community Toolkit to doskonała łatwa w użyciu otoka w oparciu o rozszerzenia kompilowania z zestawem VSSDK; istnieją jednak pułapki, które nie są od razu oczywiste, co doprowadziło do rozwoju programu VisualStudio.Extensibility. Aby zrozumieć przejście i potrzebę, zwłaszcza gdy wydaje się, że zestaw narzędzi Community Toolkit również zapewnia łatwy do pisania i zrozumienia kod, przejrzyjmy przykład i porównajmy, co dzieje się w głębszych warstwach kodu.
Możemy szybko odpakować kod w tym przykładzie i zobaczyć, co jest rzeczywiście wywoływane po stronie zestawu VSSDK. Skupimy się wyłącznie na fragmencie wykonywania poleceń, ponieważ istnieje wiele szczegółów, których potrzebuje zestaw VSSDK, który zestaw narzędzi Community Toolkit dobrze się ukrywa. Jednak po zapoznaniu się z podstawowym kodem zrozumiesz, dlaczego prostota w tym miejscu jest kompromisem. Prostota ukrywa niektóre podstawowe szczegóły, co może prowadzić do nieoczekiwanego zachowania, błędów, a nawet problemów z wydajnością i awarii. Poniższy fragment kodu przedstawia niezapisany kod zestawu narzędzi Community Toolkit w celu wyświetlenia wywołań zestawu NARZĘDZI 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);
}
}
});
}
Istnieje kilka problemów, aby dostać się tutaj, a wszystkie obracają się wokół wątków i kodu asynchronicznego. Szczegółowo omówimy każdy z nich.
Asynchroniczne wykonywanie kodu a asynchronicznego
Pierwszą rzeczą, którą należy zwrócić uwagę, jest to, że ExecuteAsync
metoda w zestawie narzędzi Community Toolkit jest opakowanym wywołaniem asynchronicznego fire-and-forget w zestawie VSSDK:
package.JoinableTaskFactory.RunAsync(async delegate
{
…
});
Sam zestaw VSSDK nie obsługuje asynchronicznego wykonywania poleceń z perspektywy podstawowego interfejsu API. Oznacza to, że po wykonaniu polecenia zestaw VSSDK nie ma możliwości wykonania kodu programu obsługi poleceń w wątku w tle, oczekiwania na zakończenie i zwrócenia użytkownika do oryginalnego kontekstu wywołującego z wynikami wykonywania. Tak więc, mimo że interfejs API ExecuteAsync w zestawie narzędzi Community Toolkit jest syntaktycznie asynchroniczny, nie jest to prawdziwe wykonywanie asynchroniczne. Ze względu na to, że jest to ogień i zapominanie o sposobie wykonywania asynchronicznego, możesz wywołać funkcję ExecuteAsync przez i ponownie bez konieczności oczekiwania na ukończenie poprzedniego wywołania. Zestaw narzędzi Community Toolkit zapewnia lepsze środowisko w zakresie ułatwiania rozszerzeń odnajdywania sposobu implementowania typowych scenariuszy, ale ostatecznie nie może rozwiązać podstawowych problemów z zestawem VSSDK. W takim przypadku podstawowy interfejs API zestawu VSSDK nie jest asynchroniczny, a metody pomocnika fire-and-forget dostarczone przez zestaw narzędzi Community Toolkit nie mogą prawidłowo rozwiązać problemu asynchronicznego uzyskiwania i pracy ze stanem klienta; może ukryć pewne potencjalne problemy z trudnym do debugowania.
Wątek interfejsu użytkownika a wątek tła
Drugi opad z tego opakowanego wywołania asynchronicznego z zestawu narzędzi Community Toolkit polega na tym, że sam kod jest nadal wykonywany z wątku interfejsu użytkownika i jest w deweloperze rozszerzenia, aby dowiedzieć się, jak poprawnie przełączyć się do wątku w tle, jeśli nie chcesz ryzykować zamrożenia interfejsu użytkownika. Tak samo jak zestaw narzędzi Community Toolkit może ukrywać szum i dodatkowy kod zestawu VSSDK, nadal wymaga zrozumienia złożoności wątków w programie Visual Studio. A jedną z pierwszych lekcji, które nauczysz się w wątkowaniu programu VS, jest to, że nie wszystko może być uruchamiane z wątku w tle. Innymi słowy, nie wszystko jest bezpieczne wątkiem, szczególnie wywołania, które przechodzą do składników COM. W powyższym przykładzie widać, że istnieje wywołanie przełączenia do wątku głównego (UI):
await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));
Możesz oczywiście wrócić do wątku w tle po tym wywołaniu. Jednak jako rozszerzenie korzystające z zestawu narzędzi Community Toolkit należy zwrócić szczególną uwagę na wątek, na którym znajduje się kod, i określić, czy ma ryzyko zamarzania interfejsu użytkownika. Wątkowanie w programie Visual Studio jest trudne do uzyskania właściwego zastosowania JoinableTaskFactory
, aby uniknąć zakleszczenia. Walka o napisanie kodu, który prawidłowo zajmuje się wątkowaniem, była stałym źródłem usterek, nawet dla naszych wewnętrznych inżynierów programu Visual Studio. Z drugiej strony funkcja VisualStudio.Extensibility pozwala całkowicie uniknąć tego problemu, uruchamiając rozszerzenia poza procesem i opierając się na asynchronicznych interfejsach API.
Proste pojęcia dotyczące interfejsu API a proste
Ponieważ zestaw narzędzi Community Toolkit ukrywa wiele zawiłości zestawu NARZĘDZI VSSDK, może dać rozszerzenie fałszywe poczucie prostoty. Kontynuujmy korzystanie z tego samego przykładowego kodu. Jeśli rozszerzenie nie wiedziało o wymaganiach wątkowych programowania w programie Visual Studio, może założyć, że kod jest uruchamiany z wątku w tle przez cały czas. Nie będą mieli problemu z faktem, że wywołanie odczytu pliku z tekstu jest synchroniczne. Jeśli znajduje się on w wątku w tle, interfejs użytkownika nie zostanie zamrożony, jeśli plik, o których mowa, jest duży. Jednak gdy kod jest niezapisany do zestawu VSSDK, zda sobie sprawę, że tak nie jest. Dlatego interfejs API z zestawu narzędzi Community Toolkit z pewnością wydaje się prostszy do zrozumienia i bardziej spójny do zapisu, ponieważ jest powiązany z zestawem VSSDK, podlega ograniczeniom zestawu VSSDK. Uproszczone zagadnienia mogą dotyczyć ważnych pojęć, które, jeśli rozszerzenia nie rozumieją, mogą spowodować większą szkodę. Rozszerzenie VisualStudio.Extensibility pozwala uniknąć wielu problemów spowodowanych przez zależności wątku głównego, koncentrując się na modelu poza procesem i asynchronicznych interfejsów API jako podstawy. Podczas wyczerpania procesu uprościłoby najwięcej wątków, wiele z tych korzyści jest przenoszonych na rozszerzenia, które są również uruchamiane w procesie. Na przykład polecenia VisualStudio.Extensibility są zawsze wykonywane w wątku w tle. Interakcja z interfejsami API zestawu VSSDK nadal wymaga dogłębnej wiedzy na temat działania wątków, ale przynajmniej nie zapłacisz za przypadkowe zawieszenie, jak w tym przykładzie.
Porównanie
Aby podsumować szczegóły przedstawione w poprzedniej sekcji, w poniższej tabeli przedstawiono szybkie porównanie:
VSSDK | Community Toolkit | Rozszerzalność programu VisualStudio.Extensibility | |
---|---|---|---|
Obsługa środowiska uruchomieniowego | .NET Framework | .NET Framework | .NET |
Izolacja z programu Visual Studio | ❌ | ❌ | ✅ |
Prosty interfejs API | ❌ | ✅ | ✅ |
Wykonywanie asynchroniczne i interfejs API | ❌ | ❌ | ✅ |
Szerokość scenariusza programu VS | ✅ | ✅ | ⏳ |
Możliwość instalacji bez ponownego uruchamiania | ❌ | ❌ | ✅ |
Obsługuje programy VS 2019 i starsze | ✅ | ✅ | ❌ |
Aby ułatwić zastosowanie porównania do potrzeb rozszerzalności programu Visual Studio, poniżej przedstawiono przykładowe scenariusze i nasze zalecenia dotyczące tego, którego modelu użyć:
- Dopiero zaczynam programować rozszerzenia programu Visual Studio i chcę, aby najłatwiejsze środowisko dołączania tworzyło rozszerzenie wysokiej jakości i muszę obsługiwać tylko program Visual Studio 2022 lub nowszy.
- W takim przypadku zalecamy użycie rozszerzenia VisualStudio.Extensibility.
- Chcę napisać rozszerzenie przeznaczone dla programu Visual Studio 2022 lub nowszego. Jednak rozszerzenie VisualStudio.Extensibility nie obsługuje wszystkich potrzebnych funkcji.
- W tym przypadku zalecamy zastosowanie hybrydowej metody łączenia visualStudio.Extensibility i VSSDK. Możesz utworzyć rozszerzenie VisualStudio.Extensibility, które jest uruchamiane w procesie, co umożliwia dostęp do interfejsów API zestawu narzędzi VSSDK lub Community Toolkit.
- Mam istniejące rozszerzenie i chcę zaktualizować je w celu obsługi nowszych wersji. Chcę, aby moje rozszerzenie obsługiwało jak najwięcej wersji programu Visual Studio.
- Ponieważ rozszerzenie VisualStudio.Extensibility obsługuje tylko program Visual Studio 2022 lub nowszy, zestaw NARZĘDZI VSSDK lub Community Toolkit jest najlepszą opcją dla tego przypadku.
- Mam istniejące rozszerzenie, które chcę przeprowadzić migrację do programu VisualStudio.Extensibility, aby móc korzystać z platformy .NET i instalować bez ponownego uruchamiania.
- Ten scenariusz jest nieco bardziej zniuansowany, ponieważ rozszerzenie VisualStudio.Extensibility nie obsługuje wersji podrzędnych programu Visual Studio.
- Jeśli istniejące rozszerzenie obsługuje tylko program Visual Studio 2022 i ma wszystkie potrzebne interfejsy API, zalecamy ponowne zapisywanie rozszerzenia w celu używania rozszerzenia VisualStudio.Extensibility. Jeśli jednak rozszerzenie wymaga interfejsów API, których rozszerzenie VisualStudio.Extensibility nie ma jeszcze, utwórz rozszerzenie VisualStudio.Extensibility, które działa w procesie , aby uzyskać dostęp do interfejsów API vsSDK. W miarę upływu czasu można wyeliminować użycie interfejsu API vsSDK, ponieważ rozszerzenie VisualStudio.Extensibility dodaje obsługę i przenosi rozszerzenia, aby zabrakło procesu.
- Jeśli rozszerzenie musi obsługiwać wersje na poziomie podrzędnym programu Visual Studio, które nie obsługują programu VisualStudio.Extensibility, zalecamy przeprowadzenie refaktoryzacji w bazie kodu. Pobierz cały wspólny kod, który można udostępnić w różnych wersjach programu Visual Studio do własnej biblioteki, i utwórz oddzielne projekty VSIX przeznaczone dla różnych modeli rozszerzalności. Jeśli na przykład rozszerzenie musi obsługiwać programy Visual Studio 2019 i Visual Studio 2022, możesz wdrożyć następującą strukturę projektu w rozwiązaniu:
- MyExtension-VS2019 (jest to projekt kontenera VSIX oparty na zestawie VSSDK przeznaczony dla programu Visual Studio 2019)
- MyExtension-VS2022 (jest to projekt kontenera VSSDK+VisualStudio.Extensibility oparty na programie VSIX przeznaczony dla programu Visual Studio 2022)
- VSSDK-CommonCode (jest to wspólna biblioteka, która służy do wywoływania interfejsów API programu Visual Studio za pomocą zestawu VSSDK. Oba projekty VSIX mogą odwoływać się do tej biblioteki, aby udostępnić kod).
- MyExtension-BusinessLogic (jest to wspólna biblioteka zawierająca cały kod, który jest trafny dla logiki biznesowej rozszerzenia. Oba projekty VSIX mogą odwoływać się do tej biblioteki, aby udostępnić kod).
- Ten scenariusz jest nieco bardziej zniuansowany, ponieważ rozszerzenie VisualStudio.Extensibility nie obsługuje wersji podrzędnych programu Visual Studio.
Następne kroki
Naszym zaleceniem jest to, że rozszerzenia zaczynają się od visualStudio.Extensibility podczas tworzenia nowych rozszerzeń lub ulepszania istniejących, a także używają zestawu narzędzi VSSDK lub Community Toolkit, jeśli wystąpią nieobsługiwane scenariusze. Aby rozpocząć pracę z programem VisualStudio.Extensibility, przejrzyj dokumentację przedstawioną w tej sekcji. Możesz również odwołać się do repozytorium GitHub vsExtensibility dla przykładów lub problemów z plikami.