(翻譯)LearnVSXNow!-#2 創建一個空的VS Package
- 2019 年 10 月 5 日
- 筆記
為了熟悉Visual Studio Package的概念,在這篇文章中我們將創建一個空的Visual Studio 2008 package。
打開Visual Studio 2008,新建項目,在項目類型對話框中選擇「其他項目類型」/「擴展性」/Visual Studio Integration Package,如下圖:(譯者註:原文的圖片無法打開,本系列的圖是由譯者另外截圖的,並非原圖)

如上圖所示,我們把Framework的版本設為.NET Framework 2.0(譯者註:實際上,不一定非得選擇.NET Framework 2.0作為framework的版本,採用默認的3.5版本即可),項目名稱設為EmptyPackage,點擊「確定」後,將彈出Visual Studio Integration Package嚮導,如下圖:

點擊Next按鈕,開始定義我們的Package。

如上圖所示,我們選擇C#作為該Package的開發語言,另外,由於VS Package必須被強命名,所以我們需要一個key文件來給我們的package程式集進行簽名,在這裡我們利用嚮導幫我們自動生成一個key文件。點擊「Next」,將在下一步中設置Package的基本資訊。

請根據上圖填入相應的內容。VSPackage Name, VSPackage Version and Detailed information用於顯示我們的package,Minimum Visual Studio value選項代表我們的package支援哪個版本的vs。
這個選項非常重要,因為不同版本的Visual Studio有不同的service。如果我們的package用到了VS專業版才有的service,那麼在VS標準版中將不能用我們的package。同時,我們也應該根據到自己的Visual Studio的版本和許可證來設置該選項。
下一步是設置VS Package的選項。

嚮導可以幫助我們創建一個菜單命令(menu command)、一個工具窗口(tool window)和一個自定義編輯器(custom editor),但是由於我們這次只是創建一個空的package,所以在這裡一個框都不要勾選。點擊Next按鈕後,會轉到嚮導的最後一步,在這裡我們可以為我們的package添加測試項目。

像上一步一樣,為了創建一個最簡單的package,在這裡我們也不要勾選任何選項(事實上,我們需要勾掉它們,因為它們默認是被勾中的)。點擊Finish按鈕,Visual Studio會在幾秒鐘內幫我們創建該package的項目。成功創建項目後,在解決方案瀏覽器中,我們將看到下面的結構:

可以看到,在項目引用中,包含很多interop assembly,這些程式集幫助我們與Visual Studio IDE中的COM對象交互,並提供package需要的service。你也許發現了項目引用中的System.Core.dll,這個程式集是.Net Framework 3.5的一部分,這和我們一開始創建項目的時候選擇的.NET Framework 2.0有些矛盾,不過沒關係,現在我們先忽略它。
Package的文件
在我們的項目中,最重要的文件是一個資源文件和兩個cs文件,如下:
文件名 | 描述 |
|---|---|
EmptyPackagePackage.cs | 該文件定義了可以被Visual Studio載入的EmptyPackagePackage類。 |
Guids.cs | 就像COM世界充滿GUID一樣,我們的package也用GUID來標識自己。這個文件用於定義這些GUID |
VSPackage.resx | 資源文件,保存我們package用到的字元串和圖片 |
嚮導也生成了一些「並不重要」的文件:
文件名 | 描述 |
|---|---|
AssemblyInfo.cs | 定義程式集的資訊 |
Package.ico | 該package的圖標 |
Resources.resx | package級別的資源文件(初始的時候是空文件) |
GlobalSupressions.cs | Global static code analysis rule suppressions(譯者註:用於取消報告特定的靜態分析工具規則衝突) |
測試這個Package
如果是一個「Hello, world」程式的話,測試它是否正確是很簡單的:只需要運行它使他顯示資訊即可。但是對於這個空的Package來說,只有一個地方可以證明這個Package註冊成功了並且被IDE識別了:在「幫助|關於」菜單下,所有的packages都會被列出。如果運行我們的package(Ctrl+F5),將會啟動Visual Studio 2008實驗室(Microsoft Visual Studio 2008 Experimental hive),通過點擊「幫助|關於」菜單,就可以看到我們的package:

它是如何工作的?
現在是時候去查看這些程式碼並弄清楚我們的package是如何工作的了。為了使程式碼的可讀性更好,我將忽略掉注釋和不重要的部分。
讓我們從Guid.cs文件開始:
1: using System; 2: namespace MyCompany.EmptyPackage 3: { 4: static class GuidList 5: { 6: public const string guidEmptyPackagePkgString = "223643a0-af7c-4741-99df-e9641691af50"; 7: 8: public const string guidEmptyPackageCmdSetString = "cecdc1f1-2fb2-40e4-88e8-ae8c85287a7c"; 9: 10: public static readonly Guid guidEmptyPackageCmdSet = new Guid(guidEmptyPackageCmdSetString); 11: 12: }; 13: }這個文件定義了GuidList類,該類負責定義我們的package用到的GUID。第一個字元串guidEmptyPackagePkgString是我們的package的GUID,第二個字元串是我們的package的命令集(Command Set)的標識。由於我們只是做一個空的package,並沒有任何命令(Command),所以我們可以忽略掉第二個GUID。
Package定義在EmptyPackagePackage.cs文件中:
1: using System; 2: using System.Diagnostics; 3: using System.Globalization; 4: using System.Runtime.InteropServices; 5: using Microsoft.VisualStudio.Shell; 6: namespace MyCompany.EmptyPackage 7: { 8: [PackageRegistration(UseManagedResourcesOnly = true)] 9: [DefaultRegistryRoot("Software\Microsoft\VisualStudio\9.0")] 10: [InstalledProductRegistration(false, "#110", "#112", "1.0", 11: IconResourceID = 400)] 12: [ProvideLoadKey("Standard", "1.0", "EmptyPackage", "MyCompany", 1)] 13: [Guid(GuidList.guidEmptyPackagePkgString)] 14: public sealed class EmptyPackagePackage : Package 15: { 16: public EmptyPackagePackage() 17: { 18: Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, 19: "Entering constructor for: {0}", this.ToString())); 20: } 21: protected override void Initialize() 22: { 23: Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, 24: "Entering Initialize() of: {0}", this.ToString())); 25: base.Initialize(); 26: } 27: } 28: }EmptyPackagePackage類定義上面有一些屬性(Attribute),並且繼承於Package抽象類,Package抽象類實現了IVsPackage介面。只要一個類實現了IVsPackage介面並註冊到Visual Studio shell中,這個類就是一個package。標註在EmptyPackagePackage類上面的屬性(Attribute)描述了怎樣去註冊package:
屬性 | 描述 |
|---|---|
PackageRegistration | regpkg.exe命令發現到類定義中有PackageRegistration這個Attribute時,會把該類當作一個package。例如把這個Attribute加到我們的類定義上面,regpkg.exe就會把我們的EmptyPackagePackage類當作一個package,並且根據該類上面含有的其他Attribute來註冊我們的類。另外,在我們的例子中,我們把PackageRegistration的UseManagedResourcesOnly設成了true,這意味著我們的package中的所有資源都會定義在可管理的package中(managed package),而不是定義在衛星程式集里(statelite.dll) |
DefaultRegistryRoot | VS提供了一個簡單的方法去開發和調試Visual Studio組件:在運行devenv.exe(也就是VS IDE)時,可以指定一個註冊表的根。當我們在調試模式下運行我們的VS組件時,我們的組件實際上會運行在Visual Studio實驗室下(Microsoft Visual Studio 2008 Experimental hive)。實驗室模式下的VS和我們平時的開發環境應用了不同的設置。(譯者註:有兩種方式啟動Visual Studio實驗室,1、在開發package的VS IDE點擊調試/開始執行或Ctrl+F5。2、通過開始-》所有程式-》Microsoft Visual Studio 2008 SDK-》Start Microsoft Visual Studio 2008 SP1 under Experimental hive) 當我們在VS中執行「開始調試」時(譯者註:應該是利用VS進行編譯時),VS會執行regpkg.exe命令,並且為該命令指定參數,以便註冊我們的package到VS實驗室環境中。 如果regpkg.exe命令在運行時並沒有指定參數,那麼就會用到DefaultRegistryRoot屬性里指定的註冊表的根。在我們的例子中,我們指定了「普通」的VS 2008 IDE用到的註冊表根。 (譯者註:利用VS進行編譯時,查看輸出窗口,可以看到有這麼一條命令:RegPkg.exe /root:SoftwareMicrosoftVisualStudio9.0Exp /ranu /codebase "路徑EmptyPackagebinDebugEmptyPackage.dll",在這條命令里,通過/root開關指定了註冊表的根為9.0Exp,也就是說我們通過DefaultRegistryRoot指定的根並沒有用到。所以在通過VS進行編譯時,會把package註冊到Experimental hive中。原文作者的意思是如果不指定RegPkg命令的/root開關的話,就會用到DefaultRegistryRoot指定的註冊表根) |
InstalledProductRegistration | 這個Attribute提供的資訊會顯示在VS IDE的「幫助|關於」對話框里。它的構造函數需要四個參數: --第一個參數是false,表示我們並不提供自己的介面去顯示package資訊。 --第二和第三個參數分別表示package的名字和描述。字元「#」表明名字和描述的值需要在資源文件中讀出,資源名就是#號後面的ID。 --第四個參數「1.0」是產品ID(版本號) --第五個參數(IconResourceID)代表package的圖標。 資源(名字、描述和圖標)定義在VSPackage.resx文件中。 |
ProvideLoadKey | 每一個VS組件都應該用所謂的package load key(PLK)進行簽名,Visual Studio用PLK去檢查package的合法性。不過,如果你安裝了Visual studio SDK的話,會安裝一個VSIP的許可證,通過它,package可以在沒有PLK的情況下運行。 但是,我們的package的最終用戶很可能沒有VSIP的許可證,所以我們需要PLK。ProvideLoadKey屬性用於定義PLK和生成PLK的基礎資訊。通過比較基礎資訊和PLK是否一致,VS可以驗證package並決定是否允許package載入運行。 ProvideLoadKey屬性構造函數的前4個參數分別表示如下含義: --預計的最小版本 --產品(Package)的版本號 --產品(Package)的名字 --公司名稱(所有者/開發者) 第5個參數是資源文件中定義PLK的資源ID。 (譯者註:PLK需要到微軟網站上http://msdn.microsoft.com/en-us/vsx/cc655795.aspx去申請) |
Guid | 這個Attribute定義了我們package的GUID。GUID是我們package的唯一標識,被用作COM註冊、在IDE里得到我們package的引用,等等。 |
對於定義一個空的package來說,這些Attribute已經足夠了。為了使package正常工作,必須初始化它。有兩個地方可以放置初始化程式碼:
— package類的構造函數可以初始化任何不需要放到VS IDE中的東西。當package的構造函數執行的時候,package雖然已經被實例化了,但是還沒有和VS IDE關聯起來。所以在構造函數里,我們不能訪問到VS IDE的service和VS IDE的對象。
— 當我們的package實例和VS IDE關聯起來的時候,VS會調用Package類的虛方法Initialize。我們可以重寫這個方法,並且在這個方法里去初始化任何需要訪問到VS IDE的service的對象。
總結
我們創建了一個最小功能的和VS IDE集成的VS package(並且證明了它可以在關於對話框中顯示)。這個package繼承了Package類,Package類實現了IVsPackage介面。同時,我們為這個package類加了一下Attribute的標記,這些Attribute被regpkg.exe命令用來註冊package。
下一步,是時候為我們的package增加一些實用的功能了。