Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Anda dapat memperluas Visual Studio menggunakan tiga model ekstensibilitas utama, VSSDK, Community Toolkit, dan VisualStudio.Extensibility. Artikel ini membahas pro dan kontra masing-masing. Kami menggunakan contoh sederhana untuk menyoroti perbedaan arsitektur dan kode antara model.
VSSDK
VSSDK (atau Visual Studio SDK) adalah model yang menjadi dasar bagi sebagian besar ekstensi di Visual Studio Marketplace. Model ini adalah dasar dari pembangunan Visual Studio itu sendiri. Ini adalah yang paling lengkap dan paling kuat, tetapi juga yang paling kompleks untuk belajar dan menggunakan dengan benar. Ekstensi yang menggunakan VSSDK berjalan dalam proses yang sama dengan Visual Studio itu sendiri. Memuat dalam proses yang sama dengan Visual Studio berarti bahwa ekstensi yang memiliki pelanggaran akses, perulangan tak terbatas, atau masalah lain dapat menyebabkan Visual Studio mogok atau menggantung, dan memburuknya pengalaman pengguna. Dan karena ekstensi berjalan dalam proses yang sama dengan Visual Studio, ekstensi hanya dapat dibuat menggunakan .NET Framework. Extender yang ingin menggunakan atau menggabungkan pustaka yang menggunakan .NET 5 dan yang lebih baru tidak dapat melakukannya menggunakan VSSDK.
API di VSSDK telah diagregasi selama bertahun-tahun karena Visual Studio itu sendiri diubah dan berkembang. Dalam satu ekstensi, Anda mungkin menemukan diri Anda bergulat dengan API berbasis COMdari jejak warisan, berkedip melalui kesederhanaan DTE, dan mengutak-atik MEF impor dan ekspor. Mari kita ambil contoh penulisan ekstensi yang membaca teks dari sistem file dan menyisipkannya ke awal dokumen aktif saat ini dalam editor. Cuplikan berikut menunjukkan kode yang akan Anda tulis untuk ditangani saat perintah dipanggil dalam ekstensi berbasis 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);
}
}
}
Selain itu, Anda juga perlu menyediakan file .vsct
, yang menentukan konfigurasi perintah, seperti tempat menempatkannya di UI, teks yang terkait, dan sebagainya:
<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>
Seperti yang Anda lihat dalam sampel, kode mungkin tampak tidak intuitif dan tidak mungkin bagi seseorang yang akrab dengan .NET untuk mengambil dengan mudah. Ada banyak konsep untuk dipelajari dan pola API untuk mengakses teks editor aktif sudah kuno. Untuk sebagian besar pengembang ekstensi, ekstensi VSSDK dibangun dari menyalin dan menempel dari sumber daring, yang dapat menyebabkan sesi pemecahan masalah yang rumit, percobaan dan kesalahan, serta frustrasi. Dalam banyak kasus, ekstensi VSSDK mungkin bukan cara yang paling mudah untuk mencapai tujuan ekstensi (meskipun terkadang, itu merupakan satu-satunya pilihan).
Toolkit Komunitas
Community Toolkit adalah model ekstensibilitas berbasis komunitas sumber terbuka untuk Visual Studio yang membungkus VSSDK untuk pengalaman pengembangan yang lebih mudah. Karena didasarkan pada VSSDK, itu tunduk pada batasan yang sama dengan VSSDK (yaitu, .NET Framework saja, tidak ada isolasi dari seluruh Visual Studio, dan sebagainya). Melanjutkan dengan contoh penulisan ekstensi yang sama yang menyisipkan teks yang dibaca dari sistem file, menggunakan Community Toolkit, ekstensi akan ditulis sebagai berikut untuk handler perintah:
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);
}
Kode yang dihasilkan jauh ditingkatkan dari VSSDK dalam hal kesederhanaan dan intuitif! Kami tidak hanya mengurangi jumlah baris secara signifikan, tetapi kode yang dihasilkan juga terlihat masuk akal. Tidak perlu memahami apa perbedaannya antara SVsTextManager
dan IVsTextManager
. API terlihat dan terasa lebih ramah .NET, merangkul pola penamaan umum dan pola asinkron, serta memprioritaskan operasi umum. Namun, Community Toolkit masih dibangun pada model VSSDK yang ada dan oleh karenanya, sisa-sisa struktur mendasar terlihat. Misalnya, file .vsct
masih diperlukan. Meskipun Community Toolkit melakukan pekerjaan yang bagus untuk menyederhanakan API, itu terikat pada batasan VSSDK dan tidak memiliki cara untuk membuat konfigurasi ekstensi lebih sederhana.
VisualStudio.Extensibility
VisualStudio.Extensibility adalah model ekstensibilitas baru di mana ekstensi berjalan di luar proses Visual Studio utama. Karena pergeseran arsitektur mendasar ini, pola dan kemampuan baru sekarang tersedia untuk ekstensi yang tidak dimungkinkan dengan VSSDK atau Toolkit Komunitas. VisualStudio.Extensibility menawarkan serangkaian API yang sama sekali baru yang konsisten dan mudah digunakan, memungkinkan ekstensi untuk menargetkan .NET, mengisolasi bug yang muncul dari ekstensi dari seluruh Visual Studio, dan memungkinkan pengguna menginstal ekstensi tanpa memulai ulang Visual Studio. Namun, karena model baru dibangun berdasarkan arsitektur dasar baru, model ini belum memiliki luas yang dimiliki VSSDK dan Community Toolkit. Untuk menjembatani kesenjangan itu, Anda dapat menjalankan ekstensi VisualStudio.Extensibility dalam proses, yang memungkinkan Anda untuk terus menggunakan API VSSDK. Namun, melakukannya berarti ekstensi Anda hanya dapat menargetkan .NET Framework karena berbagi proses yang sama dengan Visual Studio, yang didasarkan pada .NET Framework.
Melanjutkan dengan contoh penulisan ekstensi yang sama yang menyisipkan teks dari file, menggunakan VisualStudio.Extensibility, ekstensi akan ditulis sebagai berikut untuk penanganan perintah:
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);
}
}
Untuk mengonfigurasi perintah penempatan, teks, dan sebagainya, Anda tidak perlu lagi menyediakan file .vsct
. Sebaliknya, itu dilakukan melalui kode:
public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};
Kode ini lebih mudah dipahami dan diikuti. Sebagian besar, Anda dapat menulis ekstensi ini murni melalui editor dengan bantuan IntelliSense, bahkan untuk konfigurasi perintah.
Membandingkan model ekstensibilitas Visual Studio yang berbeda
Dari sampel, Anda mungkin melihat bahwa menggunakan VisualStudio.Extensibility, ada lebih banyak baris kode daripada Community Toolkit di handler perintah. Community Toolkit adalah lapisan antarmuka yang memudahkan penggunaan dalam membangun ekstensi dengan VSSDK; namun, ada perangkap yang tidak segera jelas yang menyebabkan pengembangan VisualStudio.Extensibility. Untuk memahami transisi dan kebutuhan, terutama ketika sepertinya Community Toolkit juga menghasilkan kode yang mudah ditulis dan dipahami, mari kita tinjau contoh dan bandingkan apa yang terjadi di lapisan kode yang lebih dalam.
Kita dapat dengan cepat membuka bungkus kode dalam sampel ini dan melihat apa yang sebenarnya dipanggil di sisi VSSDK. Kita hanya akan fokus pada cuplikan eksekusi perintah karena ada banyak detail yang dibutuhkan VSSDK, yang disembunyikan oleh Community Toolkit dengan baik. Tetapi setelah kita melihat kode dasarnya, Anda akan memahami mengapa kesederhanaan di sini adalah sebuah kompromi. Kesederhanaan menyembunyikan beberapa detail yang mendasar, yang dapat menyebabkan perilaku tak terduga, bug, dan bahkan masalah performa dan crash. Cuplikan kode berikut menunjukkan kode Toolkit Komunitas yang dibongkar untuk menampilkan panggilan 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);
}
}
});
}
Ada beberapa masalah yang perlu dibahas di sini, yang semuanya berkaitan dengan pemrograman multi-threading dan kode asinkron. Kita akan membahas masing-masing secara rinci.
API asinkron versus eksekusi kode asinkron
Hal pertama yang perlu diperhatikan adalah bahwa metode ExecuteAsync
di Community Toolkit adalah panggilan asinkron fire-and-forget yang terbungkus dalam VSSDK.
package.JoinableTaskFactory.RunAsync(async delegate
{
…
});
VSSDK sendiri tidak mendukung eksekusi perintah asinkron dari perspektif API inti. Artinya, ketika perintah dijalankan, VSSDK tidak memiliki cara untuk menjalankan kode handler perintah pada utas latar belakang, tunggu hingga selesai, dan kembalikan pengguna ke konteks panggilan asli dengan hasil eksekusi. Jadi, meskipun ExecuteAsync API di Community Toolkit secara sintaksis asinkron, itu bukan eksekusi asinkron sebenarnya. Dan karena ini adalah metode eksekusi asinkron "fire and forget", Anda dapat memanggil ExecuteAsync berulang kali tanpa harus menunggu hingga panggilan sebelumnya selesai terlebih dahulu. Meskipun Community Toolkit memberikan pengalaman yang lebih baik dalam hal membantu extender menemukan cara menerapkan skenario umum, itu pada akhirnya tidak dapat menyelesaikan masalah mendasar dengan VSSDK. Dalam kasus ini, API VSSDK yang mendasari tidak asinkron, dan metode pembantu 'fire-and-forget' yang disediakan oleh Community Toolkit tidak dapat menangani pengembalian async dan bekerja dengan status klien dengan benar; ini mungkin menyembunyikan beberapa masalah yang sulit untuk di-debug.
Utas UI versus utas latar belakang
Dampak lain dengan panggilan asynchronous yang dibungkus ini dari Community Toolkit adalah bahwa kode itu sendiri masih dijalankan dari utas UI, dan merupakan tanggung jawab pengembang ekstensi untuk mencari tahu cara beralih dengan benar ke utas latar belakang jika Anda tidak ingin berisiko membekukan UI. Meskipun Community Toolkit dapat menyamarkan kebisingan dan kode tambahan VSSDK, Anda tetap harus memahami kompleksitas pemrograman utas di Visual Studio. Dan salah satu pelajaran pertama yang Anda pelajari dalam penjadwalan utas di VS adalah bahwa tidak semuanya dapat dijalankan dari utas latar belakang. Dengan kata lain, tidak semuanya aman utas, terutama panggilan yang masuk ke komponen COM. Jadi, dalam contoh di atas, Anda dapat melihat bahwa terdapat panggilan untuk beralih ke utas utama (UI):
await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));
Anda tentu saja dapat kembali beralih ke thread latar belakang setelah panggilan ini. Namun, saat Anda berperan sebagai pengembang yang memperluas menggunakan Community Toolkit, pastikan Anda memperhatikan dengan seksama utas tempat kode Anda berjalan dan menentukan apakah terdapat risiko membekukan antarmuka pengguna (UI). Threading di Visual Studio sulit diluruskan dan memerlukan penggunaan JoinableTaskFactory
yang tepat untuk menghindari kebuntuan. Perjuangan untuk menulis kode yang menangani threading dengan benar telah menjadi sumber bug yang konstan, bahkan bagi insinyur Visual Studio kami yang internal. VisualStudio.Extensibility, di sisi lain, menghindari masalah ini sama sekali dengan menjalankan ekstensi di luar proses dan mengandalkan API asinkron secara end-to-end.
API sederhana versus konsep sederhana
Karena Community Toolkit menyembunyikan banyak seluk-beluk VSSDK, hal itu bisa memberikan pengembang ekstensi kesan kesederhanaan yang keliru. Mari kita lanjutkan dengan kode sampel yang sama. Jika extender tidak tahu tentang persyaratan threading dalam pengembangan Visual Studio, kemungkinan mereka akan berasumsi bahwa kode mereka dijalankan di utas latar belakang sepanjang waktu. Mereka tidak akan mempermasalahkan kenyataan bahwa panggilan untuk membaca file dari teks bersifat sinkron. Jika berada di latar belakang, tidak akan membekukan UI jika file yang dimaksud besar. Namun, ketika kode dibongkar menjadi VSSDK, mereka akan menyadari bahwa bukan itu masalahnya. Jadi, sementara API dari Community Toolkit tentu terlihat lebih sederhana untuk dipahami dan lebih kohesif untuk ditulis, karena terikat dengan VSSDK, itu tunduk pada batasan VSSDK. Kesederhanaan dapat mengabaikan konsep penting yang, jika tidak dimengerti oleh extender, dapat menyebabkan lebih banyak bahaya. VisualStudio.Extensibility menghindari banyak masalah yang disebabkan oleh dependensi utas utama dengan berfokus pada model di luar proses dan API asinkron sebagai fondasi kami. Meskipun menjalankan proses di luar sistem akan menyederhanakan threading paling banyak, banyak manfaat ini juga berlaku pada ekstensi yang dijalankan dalam sistem. Misalnya, perintah VisualStudio.Extensibility selalu dijalankan pada utas latar belakang. Berinteraksi dengan API VSSDK masih memerlukan pemahaman mendalam tentang cara kerja utas, tetapi setidaknya Anda tidak akan menghadapi risiko hang yang tidak disengaja, seperti yang terjadi pada contoh ini.
Bagan perbandingan
Untuk meringkas apa yang kami bahas secara rinci di bagian sebelumnya, tabel berikut ini memperlihatkan perbandingan cepat:
VSSDK | Toolkit Komunitas | VisualStudio.Extensibility | |
---|---|---|---|
Dukungan Runtime | .NET Framework | .NET Framework | .NET |
Isolasi dari Visual Studio | ❌ | ❌ | ✅ |
API Sederhana | ❌ | ✅ | ✅ |
Eksekusi Asinkron dan API | ❌ | ❌ | ✅ |
Luas Skenario vs | ✅ | ✅ | ⏳ |
Dapat Diinstal tanpa Menghidupkan ulang | ❌ | ❌ | ✅ |
MendukungVS 2019 danDi Bawah | ✅ | ✅ | ❌ |
Untuk membantu Anda menerapkan perbandingan dengan kebutuhan ekstensibilitas Visual Studio Anda, berikut adalah beberapa skenario sampel dan rekomendasi kami tentang model mana yang akan digunakan:
-
saya baru dalam pengembangan ekstensi Visual Studio, dan saya ingin pengalaman onboarding termudah untuk membuat ekstensi berkualitas tinggi, dan saya hanyaperlumendukung Visual Studio 2022 atau versi yang lebih tinggi.
- Dalam hal ini, kami sarankan Anda menggunakan VisualStudio.Extensibility.
-
Saya ingin menulis ekstensi yang menargetkan Visual Studio 2022 ke atas. Namun,VisualStudio.Extensibility tidak mendukung semua fungsionalitasyang saya butuhkan.
- Kami menyarankan agar dalam hal ini, Anda harus mengadopsi metode hibrid untuk menggabungkan VisualStudio.Extensibility dan VSSDK. Anda dapat membuat ekstensi VisualStudio.Extensibility yang berjalan di dalam proses, yang memungkinkan Anda mengakses VSSDK atau Community Toolkit API.
-
saya memiliki ekstensi yang sudah ada dan ingin memperbaruinya untuk mendukung versi yang lebih baru. Saya ingin ekstensi saya mendukung versi Visual Studio sebanyak mungkin.
- Karena VisualStudio.Extensibility hanya mendukung Visual Studio 2022 ke atas, VSSDK atau Community Toolkit adalah opsi terbaik untuk kasus ini.
-
saya memiliki ekstensi yang sudah ada yang ingin saya migrasikan keVisualStudio.Extensibility untuk memanfaatkan .NET dan menginstal tanpa menghidupkan ulang.
- Skenario ini sedikit lebih bernuansa karena VisualStudio.Extensibility tidak mendukung versi Visual Studio tingkat bawah.
- Jika ekstensi yang ada hanya mendukung Visual Studio 2022 dan memiliki semua API yang Anda butuhkan, kami sarankan Anda menulis ulang ekstensi Anda untuk menggunakan VisualStudio.Extensibility. Tetapi jika ekstensi Anda membutuhkan API yang belum dimiliki VisualStudio.Extensibility, lanjutkan dan buat ekstensi VisualStudio.Extensibility yang berjalan dalam proses sehingga Anda dapat mengakses API VSSDK. Seiring waktu, Anda dapat menghilangkan penggunaan VSSDK API karena VisualStudio.Extensibility menambahkan dukungan dan memindahkan ekstensi Anda untuk berjalan di luar proses.
- Jika ekstensi Anda perlu mendukung versi Visual Studio tingkat bawah yang tidak memiliki dukungan untuk VisualStudio.Extensibility, kami sarankan Anda melakukan beberapa pemfaktoran ulang di basis kode Anda. Tarik semua kode umum yang dapat dibagikan di seluruh versi Visual Studio ke pustakanya sendiri, dan buat proyek VSIX terpisah yang menargetkan model ekstensibilitas yang berbeda. Misalnya, jika ekstensi Anda perlu mendukung Visual Studio 2019 dan Visual Studio 2022, Anda dapat mengadopsi struktur proyek berikut dalam solusi Anda:
- MyExtension-VS2019 (ini adalah proyek kontainer VSIX berbasis VSSDK Anda yang menargetkan Visual Studio 2019)
- MyExtension-VS2022 (ini adalah proyek kontainer VSIX berbasis VSSDK+VisualStudio.Extensibility yang menargetkan Visual Studio 2022)
- VSSDK-CommonCode (ini adalah pustaka umum yang digunakan untuk memanggil VISUAL Studio API melalui VSSDK. Kedua proyek VSIX Anda dapat mereferensikan pustaka ini untuk berbagi kode.)
- MyExtension-BusinessLogic (ini adalah pustaka umum yang berisi semua kode yang berkaitan dengan logika bisnis ekstensi Anda. Kedua proyek VSIX Anda dapat mereferensikan pustaka ini untuk berbagi kode.)
- Skenario ini sedikit lebih bernuansa karena VisualStudio.Extensibility tidak mendukung versi Visual Studio tingkat bawah.
Langkah berikutnya
Rekomendasi kami adalah agar pengembang memulai dengan VisualStudio.Extensibility saat membuat ekstensi baru atau meningkatkan ekstensi yang ada, dan menggunakan VSSDK atau Community Toolkit jika menghadapi skenario yang tidak didukung. Untuk memulai, dengan VisualStudio.Extensibility, telusuri dokumentasi yang disajikan di bagian ini. Anda juga dapat mereferensikan repositori GitHub vsExtensibility untuk sampel atau untuk mengajukan masalah .