升级项目

将项目模型从一个版本的 Visual Studio 更改为下一个版本可能需要升级项目和解决方案,以便它们可以在较新版本上运行。 Visual Studio SDK 提供了可用于在自己的项目中实现升级支持的接口。

升级策略

若要支持升级,项目系统实现必须定义并实施升级策略。 在确定策略时,可以选择支持并行备份(SxS)备份、复制备份或同时支持这两者。

  • SxS 备份意味着项目仅复制需要就地升级的文件,并添加合适的文件名后缀,例如“.old”。

  • 复制备份意味着项目将所有项目项复制到用户提供的备份位置。 然后对原始项目存储位置的相关文件进行升级。

升级的工作原理

在较新版本的 Visual Studio 中创建的解决方案在较新版本中打开时,IDE 会检查解决方案文件以确定是否需要升级它。 如果需要升级,将自动启动 升级向导 ,引导用户完成升级过程。

当解决方案需要升级时,它会查询每个项目工厂的升级策略。 该策略确定项目工厂是否支持复制备份或 SxS 备份。 此信息将发送到 升级向导,该向导收集备份所需的信息,并向用户提供适用的选项。

多项目解决方案

如果解决方案包含多个项目和升级策略不同,例如当仅支持 SxS 备份的C++项目和仅支持复制备份的 Web 项目时,项目工厂必须协商升级策略。

解决方案查询每个项目工厂的IVsProjectUpgradeViaFactory。 然后,它会调用 UpgradeProject_CheckOnly 查看全局项目文件是否需要升级并确定支持的升级策略。 然后调用 升级向导

用户完成向导后, UpgradeProject 将在每个项目工厂上调用以执行实际升级。 为了便于备份,IVsProjectUpgradeViaFactory 方法提供 SVsUpgradeLogger 服务来记录升级过程的详细信息。 无法缓存此服务。

更新所有相关全局文件后,每个项目工厂可以选择实例化项目。 项目实现必须支持 IVsProjectUpgrade。 然后调用该方法 UpgradeProject 以升级所有相关项目项。

注释

该方法 UpgradeProject 不提供 SVsUpgradeLogger 服务。 可以通过调用 QueryService来获取此服务。

最佳做法

使用服务 SVsQueryEditQuerySave 检查是否可以在编辑文件之前对其进行编辑,并在保存之前保存该文件。 这将有助于您的备份和升级方案处理版本控制下的项目文件、权限不足的文件等。

请在备份和升级的所有阶段使用 SVsUpgradeLogger 服务,以提供关于升级过程成功或失败的信息。

有关备份和升级项目的详细信息,请参阅 vsshell2.idl 中 IVsProjectUpgrade 的注释。

升级自定义项目

如果更改产品不同 Visual Studio 版本之间的项目文件中保留的信息,则需要支持将项目文件从旧版本升级到新版本。 若要支持升级,以便您参与 Visual Studio 转换向导,请实现 IVsProjectUpgradeViaFactory接口。 此接口包含可用于复制升级的唯一机制。 当解决方案的某一部分打开时,项目将随之升级。 接口 IVsProjectUpgradeViaFactory 由项目工厂实现,或者至少应该可从项目工厂获取。

旧机制仍支持使用 IVsProjectUpgrade 接口,但在概念上在项目打开操作中升级了项目系统。 Visual Studio 环境因此调用IVsProjectUpgrade接口,甚至在IVsProjectUpgradeViaFactory接口被调用或实现时也是如此。 此方法允许你使用IVsProjectUpgradeViaFactory来实现升级中的复制和部分项目,并将其余工作委托给IVsProjectUpgrade接口以在就地完成(可能在新位置)。

有关示例实现 IVsProjectUpgrade,请参阅 VSSDK 示例

项目升级会出现以下情况:

  • 如果文件的格式高于项目可以支持的格式,则它必须返回一个错误,指出这一点。 这假定旧版产品包含用于检查版本的代码。

  • 如果PUVFF_SXSBACKUP标志在UpgradeProject方法中被指定,则在项目打开之前,升级会作为就地升级实现。

  • 如果在UpgradeProject方法中指定了PUVFF_COPYBACKUP标志,则该升级将实施为复制升级。

  • 如果在UpgradeProject调用中指定了UPF_SILENTMIGRATE标志,则在打开项目之后,环境会提示用户将项目文件升级为就地升级。 例如,当用户打开旧版解决方案时,环境会提示用户升级。

  • 如果未指定在UpgradeProject调用中使用的UPF_SILENTMIGRATE标志,则必须提示用户升级项目文件。

    下面是升级提示消息示例:

    “项目”%1“是使用较旧版本的 Visual Studio 创建的。 如果使用此版本的 Visual Studio 打开它,则可能无法使用较旧版本的 Visual Studio 打开它。 是否继续并打开此项目?

实现 IVsProjectUpgradeViaFactory

  1. 在项目工厂实现中实现IVsProjectUpgradeViaFactory接口中的方法,特别是UpgradeProject方法,或者确保这些实现可以从项目工厂实现中调用。

  2. 如果要在解决方案打开过程中进行就地升级,请在您的实现中将PUVFF_SXSBACKUP标志作为VSPUVF_FLAGS参数提供UpgradeProject

  3. 如果要在打开解决方案时进行原位升级,请在UpgradeProject实现中将PUVFF_COPYBACKUP标志作为VSPUVF_FLAGS参数提供。

  4. 对于步骤2和3,我们可以按照下面“实现IVsProjectUpgade”部分中所描述的步骤来使用IVsQueryEditQuerySave2进行实际文件升级,也可以将实际文件升级委托给IVsProjectUpgrade

  5. 使用 IVsUpgradeLogger 的方法,通过 Visual Studio 迁移向导为用户发布升级相关消息。

  6. IVsFileUpgrade 接口用于实现在项目升级过程中需要发生的任何类型的文件升级。 此接口不是从 IVsProjectUpgradeViaFactory中调用的,而是作为升级属于项目系统的一部分的文件的机制提供,但主项目系统可能无法直接识别。 例如,如果编译器相关的文件和属性不是由处理项目系统其余部分的同一开发团队处理,则可能会出现这种情况。

IVsProjectUpgrade 的实现

如果项目系统仅实现 IVsProjectUpgrade ,则无法参与 Visual Studio 转换向导。 但是,即使实现 IVsProjectUpgradeViaFactory 接口,你仍然可以将文件升级委托给 IVsProjectUpgrade 实现。

实现 IVsProjectUpgrade

  1. 在用户尝试打开项目时,环境在项目打开后调用UpgradeProject 方法,在用户对项目执行任何其他操作之前。 如果用户已提示升级解决方案,则会 UPF_SILENTMIGRATEgrfUpgradeFlags 参数中传递标志。 如果用户直接打开项目,例如使用 “添加现有项目” 命令,则 UPF_SILENTMIGRATE 不会传递该标志,并且项目需要提示用户升级。

  2. 为了响应 UpgradeProject 调用,项目必须评估项目文件是否已升级。 如果项目不需要将项目类型升级到新版本,则只需返回 S_OK 标志即可。

  3. 如果项目需要将项目类型升级到新版本,则必须通过调用QueryEditFiles该方法并传入参数值tagVSQueryEditFlagsrgfQueryEdit来确定是否可以修改项目文件。 然后,项目需要执行以下工作:

  4. 如果对项目文件的QueryEditFiles调用导致文件被签出并且最新版本被检索到,那么项目将被卸载并重新加载。 UpgradeProject创建项目的另一个实例后,将再次调用该方法。 在第二次调用时,可以将项目文件写入磁盘,建议项目以以前的格式保存项目文件的副本,使用.OLD扩展名,进行必要的升级更改,然后将项目文件保存为新格式。 同样,如果升级过程的任何部分失败,该方法必须通过返回 VS_E_PROJECTMIGRATIONFAILED来指示失败。 这会导致项目在解决方案资源管理器中卸载。

    了解在某种情况下,当调用指定 ReportOnly 值的QueryEditFiles方法时,返回QER_EditNotOKQER_ReadOnlyUnderScc标志,在环境中发生的完整过程是非常重要的。

  5. 用户尝试打开项目文件。

  6. 环境调用你的 CanCreateProject 实现。

  7. 如果 CanCreateProject 返回 true,则环境将调用 CanCreateProject 实现。

  8. 环境调用实现 Load 以打开文件并初始化项目对象,例如 Project1。

  9. 环境调用实现 IVsProjectUpgrade::UpgradeProject 以确定是否需要升级项目文件。

  10. 您调用QueryEditFiles并为rgfQueryEdit参数传入QEF_ReportOnly的值。

  11. 环境为VSQueryEditResult返回QER_EditNotOK,并且在VSQueryEditResultFlags中设置QER_ReadOnlyUnderScc位。

  12. 你的 IVsProjectUpgrade 实现调用 IVsQueryEditQuerySave::QueryEditFilesQEF_ForceEdit_NoPromptingQEF_DisallowInMemoryEdits) 。

此调用可能会导致签出您的项目文件的新副本并检索到最新版本,同时还需要重新加载项目文件。 此时,会发生以下两种情况之一:

  • 如果你管理自己的项目重载,则环境将调用你的ReloadItem(VSITEMID_ROOT)实现。 收到此调用时,请重新加载项目的第一个实例(Project1),并继续升级项目文件。 环境知道,如果你返回 trueGetPropertyVSHPROPID_HandlesOwnReload),你会处理自己的项目重新加载。

  • 如果您不处理自己的项目重新加载,则返回 false 以代替 GetProperty (VSHPROPID_HandlesOwnReload)。 在这种情况下,在 QueryEditFiles(QEF_ForceEdit_NoPrompting, QEF_DisallowInMemoryEdits) 返回之前,环境会创建您的项目的另一个新实例,例如 Project2,如下所示:

    1. 环境对第一个项目对象 Project1 调用 Close ,因此将此对象置于非活动状态。

    2. 环境调用 IVsProjectFactory::CreateProject 实现以创建项目 Project2 的第二个实例。

    3. 环境调用实现 IPersistFileFormat::Load 以打开文件并初始化第二个项目对象 Project2。

    4. 环境再次调用 IVsProjectUpgrade::UpgradeProject 以确定是否应升级项目对象。 这个调用是在项目 Project2 的第二个新实例上进行的。 这是在解决方案中打开的项目。

      注释

      在第一个项目 Project1 处于非活动状态的情况下,必须从您对UpgradeProject实现的第一次调用返回S_OK

    5. 您调用QueryEditFiles并为rgfQueryEdit参数传入QEF_ReportOnly的值。

    6. 环境返回了 QER_EditOK,因此升级可以继续,因为项目文件可以写入。

如果无法升级,请从VS_E_PROJECTMIGRATIONFAILED中返回IVsProjectUpgrade::UpgradeProject。 如果不需要升级,或者你选择不升级,请将 IVsProjectUpgrade::UpgradeProject 调用视为 no-op。 如果返回 VS_E_PROJECTMIGRATIONFAILED,则会将占位符节点添加到项目的解决方案中。

升级项目组件

如果在未实现的项目系统中添加或管理项,则可能需要参与项目升级过程。 Crystal Reports 是可添加到项目系统的项的一个示例。

通常,项目项实现者希望利用已完全实例化和升级的项目,因为它们需要知道项目引用是什么,还有哪些其他项目属性可以做出升级决策。

获取项目升级通知

  1. SolutionOrProjectUpgrading 项目项实现中设置标志(在 vsshell80.idl 中定义)。 这会导致项目项 VSPackage 在 Visual Studio shell 确定项目系统正在进行升级时自动加载。

  2. IVsSolutionEventsProjectUpgrade通过AdviseSolutionEvents方法为接口提供建议。

  3. 在项目系统实现完成其升级操作并创建新的升级项目后,将触发 IVsSolutionEventsProjectUpgrade 接口。 根据不同的方案,接口IVsSolutionEventsProjectUpgradeOnAfterOpenSolutionOnAfterOpenProjectOnAfterLoadProject方法之后触发。

升级项目条目文件

  1. 必须在项目项实现中仔细管理文件备份过程。 这尤其适用于并行备份,其中 fUpgradeFlag 方法的参数 UpgradeProject 设置为 PUVFF_SXSBACKUP,其中已备份的文件与指定为“.old”的并行文件一起放置。 在升级项目时,早于系统时间的备份文件可以指定为过时。 此外,除非你采取特定步骤来防止这种情况,否则它们可能会被覆盖。

  2. 在项目项收到项目升级的通知时, 仍会显示 Visual Studio 转换向导 。 因此,应使用 IVsUpgradeLogger 接口的方法向向导 UI 提供升级消息。