为了熟悉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有些矛盾,不过没关系,现在我们先忽略它。
在我们的项目中,最重要的文件是一个资源文件和两个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(译者注:用于取消报告特定的静态分析工具规则冲突) |
如果是一个“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:Software\Microsoft\VisualStudio\9.0Exp /ranu /codebase "路径\EmptyPackage\bin\Debug\EmptyPackage.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增加一些实用的功能了。