(翻譯)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。這些擴展可以是:

  1. 服務(Service)。服務是一些對象,它們提供功能供開發者或者其他package調用。例如,C#語言服務(顧名思義)是一個服務。
  2. 介面元素。例如菜單、工具欄、窗口等,開發者可以用它們在用戶介面上執行一些動作,顯示消息、資訊和圖片等等。
  3. 編輯器。在開發過程中,我們通過編寫程式去創建應用程式。編寫程式這項任務是由編輯器負責的。Visual Studio 2008有它自己的核心編輯器,但是我們也可以在VSPackage中創建我們自己的編輯器。
  4. 設計器。應用程式的創建不只是簡單的敲入文本這麼簡單。我們擁有很多被稱為設計器的可視化工具,我們可以利用他們來設計模組、組件、零部件、甚至整個應用系統。著名的例子是WinForm設計器,我們可以用它來創建WinForm的用戶介面。
  5. 項目。當開發應用程式的時候,我們一般會面向一大堆的文件。項目用來組織這些源文件和資源,並且不是簡單的存儲這些文件這麼簡單,它還可以用來編譯、調試和發布由源文件創建的產品。

在後面的文章中,我們將逐一探討這些擴展的細節,今天在這裡我先給大家一個基本概述來說明它們是什麼,以及它們如何在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。在上圖中,VSPackage1VSPackage3是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方法去得到服務實例)為服務模型帶來了一些「特性」和結果。

  1. 按需載入。如果一個package的服務沒有被使用,它是不需要被載入到記憶體的。所以,VS IDE只會載入那些服務被調用的package(譯者註:作者的意思應該是指service provider)。實際上,package和service provider是兩個不同的概念。理論上任何實現了IServiceProvider的對象都可以提供服務。如果我們繼承了抽象類Package(就像我們在前幾個篇文章中的例子那樣),我們的package會自動成為一個service provider,這是因為基類Package實現了IServiceProvider介面以及很多其他介面。
  2. Siting VSPackages. IDE為所有的VSPackage提供了一個service provider,利用它可以獲得由其他package或service provider提供的服務實例。當package載入到記憶體的時候,Visual Studio傳遞給package一個service provider的引用,可以用這個引用來訪問全局服務(global service)。所以我們稱呼它為siting VSPackage,如果一個package沒有被site,它就不能夠通過服務的實例來和Visual Studio交互。
  3. 訪問全局服務(global service)。如果我們有一個已經被site的對象(例如一個VSPackage或實現了IVsPackage介面的對象),可以很簡單的利用GetService方法去訪問全局服務;如果我們的對象沒有被site,但是我們可以通過它得到它所在的VSPackage的實例,我們依然可以用GetService方法;然而,在某些情況下我們的對象沒有被site,並且沒辦法得到所在的package的實例,此時我們可以用Package.GetGlobalService這個靜態方法。
註冊服務

了解了關於服務的這麼多資訊之後,如果我告訴你在使用服務前必須要註冊它們,我猜你們都不會感到奇怪。

要想註冊服務,需要在Package的類定義文件上面加上ProvideServiceAttributeregpkg.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程式集的引用,這些引用有:

  1. Microsoft.VisualStudio.OLE.Interop
  2. Microsoft.VisualStudio.Shell.9.0
  3. Microsoft.VisualStudio.Shell.Interop
  4. Microsoft.VisualStudio.Shell.Interop.8.0
  5. Microsoft.VisualStudio.Shell.Interop.9.0
  6. Microsoft.VisualStudio.TextManager.Interop

如果你需要其他的interop或者MPF程式集,你可以自己再添加引用。

總結

在這篇文章中,我們VSX的基本概念和最重要的細節做了一些探討。

  1. VSPackage是Visual Studio的基礎結構、安全、部署和許可認證中的基本單元。Visual Studio它自己也是建立在一系列的VSPackage之上的。
  2. VSPackage可以為其他package提供全局服務;服務是隱蔽的、不容易發現的;在使用服務前必須先註冊它們;Visual Studio提供了一種按需載入的模式去查找和載入service provider。
  3. VSPackage是基於COM技術的。Visual Studio提供了interop程式集來訪問COM類型;MPF(Managed Package Framework)對interop程式集做了一層包裝,允許用「本地化」的託管程式碼開發Package。

當然,VSX中還有很多其他的重要的概念,但對於繼續我們的學習來說,今天討論的這些已經足夠了。

在下一篇,我們繼續用程式碼示例來探討VSX的開發。