(翻譯)LearnVSXNow!-#5 VSX的基本概念
- 2019 年 10 月 5 日
- 筆記
在前幾篇文章中,我們只是通過創建和「分析」三個非常小的、由VSPackage嚮導生成的package來管中窺豹地見識了一下VSX。這些例子有助於我們熟悉創建小的package的基本步驟。但是,我們必須更深入一些, 看一下Visual Studo IDE是怎樣工作的,以及它是怎樣集成package的。
在我們涉及到其他細節之前,我們先要整理一下對VSX的認識。在本篇文章里,我們不會創建任何程式碼,只是試圖去搞清楚和VSX相關的概念。
警告:「底層用的是COM」
我在前面幾篇文章中多次提到過,Visual Studio擴展性開發是基於COM技術的。package中的對象和實體(例如命令、菜單、工具欄、窗口、編輯器、項目等)都是COM對象。當然,如果我們用的是託管程式碼(例如C#、VB.NET),我們看到這些類和實例是託管的.NET類型和實例。但如果我們用了非託管程式碼,我們不得不處理COM對象和實例。
在開發VSX的程式碼時,之所以可以用很多模式和特性,是因為VSX里里外外都用了COM。我假設你對COM沒有太深入的理解(我自己也不是一個COM專家),但我待會會告訴你一些必須要了解的基礎知識。
什麼是Visual Studio Package?
在前幾篇文章中,我們創建了幾個簡單的Visual Studio Package,所以我們已經對VSPackage有了一個初步的認識,現在讓我們更深入的探討一下它。
VSPackage是構建Visual Studio的一個基本的單元。實際上,Visual Studio是由一系列的VSPackage協同工作而成的,就像一個生態系統一樣。一個Package,不論是從VS體系結構上來看,還是從部署、安全和許可認證方面來看,它都是VS的一個基本單元。另外,在物理上,一個或多個package可以存在於同一個程式集中。
開發者(包括Visual Studio的開發者)通過創建VSPackage來擴展VS IDE。這些擴展可以是:
- 服務(Service)。服務是一些對象,它們提供功能供開發者或者其他package調用。例如,C#語言服務(顧名思義)是一個服務。
- 介面元素。例如菜單、工具欄、窗口等,開發者可以用它們在用戶介面上執行一些動作,顯示消息、資訊和圖片等等。
- 編輯器。在開發過程中,我們通過編寫程式去創建應用程式。編寫程式這項任務是由編輯器負責的。Visual Studio 2008有它自己的核心編輯器,但是我們也可以在VSPackage中創建我們自己的編輯器。
- 設計器。應用程式的創建不只是簡單的敲入文本這麼簡單。我們擁有很多被稱為設計器的可視化工具,我們可以利用他們來設計模組、組件、零部件、甚至整個應用系統。著名的例子是WinForm設計器,我們可以用它來創建WinForm的用戶介面。
- 項目。當開發應用程式的時候,我們一般會面向一大堆的文件。項目用來組織這些源文件和資源,並且不是簡單的存儲這些文件這麼簡單,它還可以用來編譯、調試和發布由源文件創建的產品。
在後面的文章中,我們將逐一探討這些擴展的細節,今天在這裡我先給大家一個基本概述來說明它們是什麼,以及它們如何在VS中使用。
另外,一個package可以在Visual Studio的啟動介面里或在關於對話框里顯示它自己的資訊。
一個package可以把它的狀態和配置資訊保存在持久化存儲設備中,並且可以讀取這些配置。例如文本編輯器可以設置語法高亮、字體、顏色、標籤等。
每個package必須被所謂的package load key(PLK)簽名,Visual Studio通過它來檢查package的合法性。Visual Studio只會載入擁有合法PLK的package。另外,從技術上來說,Package是實現了IVsPackage介面的類型。這一次我們不會深入討論IVsPackage,但在後面的文章中,我們將通過程式碼來測試它的細節。
什麼是服務(Service)?
一般來講,我們不會為了開發package而開發package。我們創建package是因為它們不但可以為我們自己提供功能(此時,我們是消費者),也可以為其他的package提供功能(此時,其他package是消費者)。例如,假設我們的package提供了一個工具窗去查找特定方法的引用,我們就是這個窗口的消費者。如果這個package不僅為這個工具窗提供查找功能,也作為「可調用的方法」為其他package服務,那麼其他package就是這個服務的消費者。
所以,服務是package之間或package和與它相關的對象(當我說「package的對象」時,我指的是窗口、命令、設計器等這些被package自己創建的東西)之間的契約。
下圖說明了VSPackage和服務之間的概念:
(譯者註:非常遺憾,這裡缺圖。原文中的圖片鏈接已經無效,聯繫了原文作者但一直沒有回應,以後如果找到這個圖片一定補上。)
VSPackage可以包含服務,這些package被稱為service provider。在上圖中,VSPackage1和VSPackage3是service provider,而VSPackage2不是。能給其他package調用的服務被稱為全局服務(global service)。VsPackage1和VsPackage3都包含global service,這些服務可以被VSPackage2調用(當然也可以被其他的package調用)。package也可以包含只能被自己調用或者只能被package的對象調用的服務。這種服務被稱為本地服務(local service)。VSPackage1和VSPackage3都包含local service,它們被對象調用(例如被VSPackage1中的編輯器和VSPackage3中的工具窗)。
使用Service
關於VSX中的服務,有一個壞消息:它們是隱蔽的,不容易被發現。這意味著我們不能猜測出一個package(或其他對象)中能提供哪些服務。
所以,如果你想使用一個服務,你必須「通過它的名字調用它」,這意味著你必須知道這個服務的名字。要知道服務的名字,唯一的方法是去查閱這些服務所在package提供的文檔。VSX的文檔里列出了大概130個服務。
一般來說,服務被定義成介面。大部分服務只實現一個介面,但也有一部分服務實現了多個。所以,當我們想使用一個服務的時候,我們必須要知道兩個「名字」:服務的名字和介面的名字。
你也許注意到了,我在「名字」這裡用了引號。這是因為所有的服務都是對象。如果我們用的是interop類型,「名字」就是它們的.NET類型;如果我們用的是COM對象(非託管程式碼),「名字」就是這些COM類型的GUID。
讓我們用一個例子來更清楚的說明它!在SimpleCommand里,我們使用SVsUIShell服務去顯示一個消息框,我們用GetService方法去獲得一個IVsUIShell介面的引用:
1: IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); 2: uiShell.ShowMessageBox(...);
當我們得到一個服務的引用後,我們就可以使用它提供的方法和屬性了。在上面的例子中,我們調用了這個服務實例uiShell的ShowMessageBox方法。
載入package和訪問服務
VSPackage訪問服務的方式(現在我只是指通過GetService方法去得到服務實例)為服務模型帶來了一些「特性」和結果。
- 按需載入。如果一個package的服務沒有被使用,它是不需要被載入到記憶體的。所以,VS IDE只會載入那些服務被調用的package(譯者註:作者的意思應該是指service provider)。實際上,package和service provider是兩個不同的概念。理論上任何實現了IServiceProvider的對象都可以提供服務。如果我們繼承了抽象類Package(就像我們在前幾個篇文章中的例子那樣),我們的package會自動成為一個service provider,這是因為基類Package實現了IServiceProvider介面以及很多其他介面。
- Siting VSPackages. IDE為所有的VSPackage提供了一個service provider,利用它可以獲得由其他package或service provider提供的服務實例。當package載入到記憶體的時候,Visual Studio傳遞給package一個service provider的引用,可以用這個引用來訪問全局服務(global service)。所以我們稱呼它為siting VSPackage,如果一個package沒有被site,它就不能夠通過服務的實例來和Visual Studio交互。
- 訪問全局服務(global service)。如果我們有一個已經被site的對象(例如一個VSPackage或實現了IVsPackage介面的對象),可以很簡單的利用GetService方法去訪問全局服務;如果我們的對象沒有被site,但是我們可以通過它得到它所在的VSPackage的實例,我們依然可以用GetService方法;然而,在某些情況下我們的對象沒有被site,並且沒辦法得到所在的package的實例,此時我們可以用Package.GetGlobalService這個靜態方法。
註冊服務
了解了關於服務的這麼多資訊之後,如果我告訴你在使用服務前必須要註冊它們,我猜你們都不會感到奇怪。
要想註冊服務,需要在Package的類定義文件上面加上ProvideServiceAttribute。regpkg.exe會利用這個屬性來註冊服務。
雖然還有很多關於服務的有趣的東西,但就目前來說,這些已經足夠了。以後我們會通過程式碼去深入研究它。
Interoperability程式集和Managed Package Framework
.NET開發人員更喜歡用託管的.NET類型,因為它們可以利用底層運行環境的強大特性。然而,由於歷史原因(.NET時代之前的VS版本),Visual Studio的主體部分是建立在非託管程式碼上的,並支援COM類和介面。為了訪問到COM對象,.NET提供了一種被稱為interoperability程式集的東西,簡單來講,就是用.NET類型來包裝了COM類型。我們有兩種主要的方法去使用VSX中的COM對象:創建非託管程式碼(例如用C++);或者利用interoperability程式集中來編寫託管程式碼(用c#或VB.NET)。
對於我來說,我更喜歡託管程式碼(並且我猜大部分.NET開發人員也是如此),所以我會用interop程式集去做我的示例程式碼。在一些常見的任務中,COM使用了不同的模式,例如在類型標識、記憶體分配、異常管理等方面,另外,COM不支援繼承。
如果只使用interop程式集的話,我們的程式碼會變得非常冗長,並且不能夠使用.NET和C#提供的語言和一些運行時的功能。微軟在Visual Studio的COM interoperability程式集之上創建了一個框架,叫做Managed Package Framework(MPF),可以幫助我們用「本土化」的託管程式碼來創建VSPackage。
VSX中Interop程式集
GAC中安裝了一堆的VSX的interop程式集,你也可以在VS SDK的安裝目錄(例如在C:Program FilesMicrosoft Visual Studio 2008 SDK)下的下VisualStudioIntegrationCommonAssemblies子目錄中找到它們。這些interop程式集的名字以Microsoft.VisualStudio開頭,但不是所有以這個開頭的程式集都是interop程式集。在這個文件夾下面,你可以看到差不多100個程式集文件。其中,interop程式集如下(我省略了Microsoft.VisualStudio前綴):
程式集 |
描述 |
---|---|
~.Shell.Interop |
這個程式集定義了幾百個核心的interop類型(包括介面,結構,枚舉,類,等等) |
~.Shell.Interop.8.0~.Shell.Interop.9.0 |
在VS 2005和VS 2008中,有不同的COM類型,這些不同的COM類型定義在這兩個程式集中,其中8.0是給VS 2005用的,9.0是給VS 2008用的。 |
~.OLE.Interop |
這個程式集包裝了幾百個標準OLE類型。 |
~.TextManager.Interop~.TextManager.Interop.8.0 |
Visual Studio有一個很好的內置編輯器。這兩個程式集用來訪問編輯器介面。其中,8.0是針對Visual Studio 2005和2008中新增的介面類型的。 |
~.Debugger.Interop |
如果你想訪問VS IDE提供的內置調試器中的介面和調試功能,你可以用這個程式集。 |
Managed Package Framework中的程式集
MPF程式集與interop程式集(以及其他的VSX相關的程式集)在同一個文件夾中,並且也是以Microsoft.VisualStudio開頭的。其中,最重要的程式集如下:
程式集 |
描述 |
---|---|
~.Shell and~.Shell.9.0 |
這兩個程式集定義了MPF的核心類型。以9.0結尾的程式集是針對於VS 2008的,如果你用VS 2008開發,你應該用這個程式集,以便regpkg.exe可以註冊你編譯後的package。 |
~.Shell.Design(譯者註:原文中的~.Shell.Desing應該屬於筆誤) |
這個程式集中定義的類型可以用來擴展Visual studio的設計器。 |
|
|
|
|
|
|
VSPackage中需要引用的程式集
如果用VS 2008創建一個新的VSPackage,嚮導會幫我們添加一些對interop程式集和MPF程式集的引用,這些引用有:
- Microsoft.VisualStudio.OLE.Interop
- Microsoft.VisualStudio.Shell.9.0
- Microsoft.VisualStudio.Shell.Interop
- Microsoft.VisualStudio.Shell.Interop.8.0
- Microsoft.VisualStudio.Shell.Interop.9.0
- Microsoft.VisualStudio.TextManager.Interop
如果你需要其他的interop或者MPF程式集,你可以自己再添加引用。
總結
在這篇文章中,我們VSX的基本概念和最重要的細節做了一些探討。
- VSPackage是Visual Studio的基礎結構、安全、部署和許可認證中的基本單元。Visual Studio它自己也是建立在一系列的VSPackage之上的。
- VSPackage可以為其他package提供全局服務;服務是隱蔽的、不容易發現的;在使用服務前必須先註冊它們;Visual Studio提供了一種按需載入的模式去查找和載入service provider。
- VSPackage是基於COM技術的。Visual Studio提供了interop程式集來訪問COM類型;MPF(Managed Package Framework)對interop程式集做了一層包裝,允許用「本地化」的託管程式碼開發Package。
當然,VSX中還有很多其他的重要的概念,但對於繼續我們的學習來說,今天討論的這些已經足夠了。
在下一篇,我們繼續用程式碼示例來探討VSX的開發。