在前几篇文章中,我们只是通过创建和“分析”三个非常小的、由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接口的引用:
typeof(SVsUIShell));
2: uiShell.ShowMessageBox(...);