开始使用包支持框架

包支持框架是一个开放源代码工具包,有助于将修复程序应用于现有桌面应用程序(无需修改代码),以便其在 MSIX 容器中运行。 包支持框架可帮助应用程序遵循新式运行时环境的最佳做法。

本文深入介绍了包支持框架的每个组件,并提供了使用它的分步指南。

了解包支持框架中的内容

包支持框架包含一个可执行文件、一个运行时管理器 DLL 和一组运行时修复程序。

Package Support Framework

下面介绍了该过程:

  1. 创建一个配置文件,用于指定要应用到应用程序的修复程序。
  2. 将包修改为指向包支持框架 (PSF) 启动器可执行文件。

当用户启动该应用程序时,包支持框架启动器是运行的第一个可执行文件。 该启动器将读取配置文件,并将运行时修复程序和运行时管理器 DLL 注入应用程序进程。 如果应用程序需要使用该修复程序才能在 MSIX 容器中运行,则运行时管理器会应用该修复程序。

Package Support Framework DLL Injection

步骤 1:识别打包的应用程序兼容性问题

首选,为你的应用程序创建包。 然后,安装它,运行它,并观察其行为。 收到的错误消息可能会帮助你识别兼容性问题。 也可以使用进程监视器来识别问题。 常见问题与有关工作目录和程序路径权限的应用程序假设有关。

使用进程监视器识别问题

进程监视器是观测应用的文件和注册表操作及其结果的强大实用工具。 这可以帮助你了解应用程序兼容性问题。 打开进程监视器后,添加筛选器(筛选器 > 筛选器...),以仅包含来自应用程序可执行文件的事件。

ProcMon App Filter

将显示事件列表。 对于其中许多事件,SUCCESS 一词将显示在“结果”列中。

ProcMon Events

(可选)你可以筛选事件以仅显示失败。

ProcMon Exclude Success

如果怀疑文件系统访问失败,请在 System32/SysWOW64 或包文件路径下搜索失败的事件。 筛选器也可以在此处提供帮助。 从此列表底部开始,向上滚动。 此列表底部出现的失败是最近发生的。 请重点关注包含“拒绝访问”和“未找到路径/名称”等字符串的错误,并忽略看起来不可疑的内容。 PSFSample 存在两个问题。 你可以在下图中显示的列表中看到这些问题。

ProcMon Config.txt

在此图像中显示的第一个问题中,应用程序无法从位于 "C:\Windows\SysWOW64" 路径的 "Config.txt" 文件中读取。 应用程序不太可能尝试直接引用该路径。 最有可能的是,它尝试使用相对路径读取该文件,默认情况下,"System32/SysWOW64" 是应用程序的工作目录。 这表明应用程序预期当前工作目录将设置为包中的某个位置。 在 appx 内部查看,我们可以看到该文件与可执行文件位于同一目录中。

App Config.txt

第二个问题如下图所示。

ProcMon Logfile

在此问题中,应用程序无法将 .log 文件写入包路径。 这表明文件重定向修复可能会有所帮助。

步骤 2:查找运行时修复程序

PSF 包含可以立即使用的运行时修复程序,例如文件重定向修复。

文件重定向修复

可以使用文件重定向修复来重定向尝试在无法从 MSIX 容器中运行的应用程序访问的目录中写入或读取数据。

例如,如果应用程序写入与应用程序可执行文件位于同一目录中的日志文件,则你可以使用文件重定向修复在另一个位置(例如本地应用数据存储)中创建该日志文件。

社区中的运行时修复程序

请务必查看对 GitHub 页的社区贡献。 其他开发人员可能已解决类似于你的问题,并共享了运行时修复程序。

步骤 3:应用运行时修复程序

你可以使用 Windows SDK 中的一些简单工具应用现有运行时修复程序,并按照以下步骤操作。

  • 创建包布局文件夹
  • 获取包支持框架文件
  • 将它们添加到包
  • 修改包清单
  • 创建配置文件

让我们完成每个任务。

创建包布局文件夹

如果已有 .msix(或 .appx)文件,你可以将其内容解压缩到布局文件夹中,该文件夹将充当包的暂存区域。 可以使用 MakeAppx 工具通过命令提示符执行此操作,根据 SDK 的安装路径,你可以在 Windows 10 PC:x86:C:\Program Files (x86)\Windows Kits\10\bin\x86\makeappx.exe x64: C:\Program Files (x86)\Windows Kits\10\bin\x64\makeappx.exe 中找到 makeappx.exe 工具

makeappx unpack /p PSFSamplePackage_1.0.60.0_AnyCPU_Debug.msix /d PackageContents

这将为你提供如下所示的内容。

Package Layout

如果没有要从头开始的 .msix(或 .appx)文件,可以从头开始创建包文件夹和文件。

获取包支持框架文件

可以使用独立的 Nuget 命令行工具或通过 Visual Studio 获取 PSF Nuget 包。

使用命令行工具获取包

从以下位置安装 Nuget 命令行工具:https://www.nuget.org/downloads。 然后,从 Nuget 命令行运行以下命令:

nuget install Microsoft.PackageSupportFramework

或者,可以将包扩展名重命名为 .zip,然后将其解压缩。 所需的所有文件都将在 /bin 文件夹下。

使用 Visual Studio 获取包

在 Visual Studio 中,右键单击你的解决方案或项目节点,然后选取其中一个“管理 NuGet 包”命令。 搜索 Microsoft.PackageSupportFrameworkPSF 以在 Nuget.org 上查找该包。然后,安装它。

将包支持框架文件添加到包

将所需的 32 位和 64 位 PSF DLL 和可执行文件添加到包目录。 使用下表作为指南。 你还需要包含所需的任何运行时修复程序。 在我们的示例中,我们需要文件重定向运行时修复程序。

应用程序可执行文件为 x64 应用程序可执行文件为 x86
PSFLauncher64.exe PSFLauncher32.exe
PSFRuntime64.dll PSFRuntime32.dll
PSFRunDll64.exe PSFRunDll32.exe

包内容现在应该如下所示。

Package Binaries

修改包清单

在文本编辑器中打开包清单,然后将 Executable 元素的 Application 属性设置为 PSF 启动器可执行文件的名称。 如果知道目标应用程序的体系结构,请选择合适的版本 PSFLauncher32.exe 或 PSFLauncher64.exe。 否则,PSFLauncher32.exe 在所有情况下都将正常运行。 下面是一个示例。

<Package ...>
  ...
  <Applications>
    <Application Id="PSFSample"
                 Executable="PSFLauncher32.exe"
                 EntryPoint="Windows.FullTrustApplication">
      ...
    </Application>
  </Applications>
</Package>

创建配置文件

创建文件名 config.json,并将该文件保存到包的根文件夹中。 修改 config.json 文件的声明的应用 ID,以指向刚替换的可执行文件。 利用你通过使用进程监视器获得的知识,还可以设置工作目录,并使用文件重定向修复将读取/写入重定向到包相对 "PSFSampleApp" 目录下的 .log 文件。

{
    "applications": [
        {
            "id": "PSFSample",
            "executable": "PSFSampleApp/PSFSample.exe",
            "workingDirectory": "PSFSampleApp/"
        }
    ],
    "processes": [
        {
            "executable": "PSFSample",
            "fixups": [
                {
                    "dll": "FileRedirectionFixup.dll",
                    "config": {
                        "redirectedPaths": {
                            "packageRelative": [
                                {
                                    "base": "PSFSampleApp/",
                                    "patterns": [
                                        ".*\\.log"
                                    ]
                                }
                            ]
                        }
                    }
                }
            ]
        }
    ]
}

以下是 config.json 架构指南:

数组 key
applications id 使用包清单中 Application 元素的 Id 属性的值。
applications 可执行文件 要启动的可执行文件的包相对路径。 在大多数情况下,可以在修改包清单文件之前从中获取此值。 它是 Application 元素的 Executable 属性值。
applications workingDirectory (可选)要用作启动的应用程序之工作目录的包相对路径。 如果未设置此值,操作系统将使用 System32 目录作为应用程序的工作目录。
进程 可执行文件 在大多数情况下,这是上面配置的 executable 的名称,只是删除了路径和文件扩展名。
修复 dll 要加载的修复 .msix/.appx 的包相对路径。
修复 config (可选)控制修复 dll 的行为方式。 此值的确切格式因修复而异,因为每个修复都可以根据自己的需要解释此 "blob"。

applicationsprocessesfixups 键是数组。 这意味着你可以使用 config.json 文件指定多个应用程序、进程和修复 DLL。

打包并测试应用

接下来,创建一个包。

makeappx pack /d PackageContents /p PSFSamplePackageFixup.msix

然后,对其进行签名。

signtool sign /a /v /fd sha256 /f ExportedSigningCertificate.pfx PSFSamplePackageFixup.msix

有关详细信息,请参阅如何创建包签名证书如何使用 signtool 对包进行签名

使用 Powershell 安装该包。

注意

请记得先卸载包。

powershell Add-AppPackage .\PSFSamplePackageFixup.msix

运行应用程序并观测应用了运行时修复程序的行为。 根据需要重复诊断和打包步骤。

检查包支持框架是否正在运行

你可以检查运行时修复程序是否正在运行。 执行此操作的一种方法是打开“任务管理器”并单击“更多详细信息”。 查找应用了包支持框架的应用,并展开应用详细信息以查看更多详细信息。 你应该能够看到包支持框架正在运行。

使用跟踪修复

诊断打包的应用程序兼容性问题的替代方法是使用跟踪修复。 此 DLL 包含在 PSF 中,并提供应用行为的详细诊断视图,类似于进程监视器。 它专为揭示应用程序兼容性问题而设计。 要使用跟踪修复,请将 DLL 添加到包,将以下片段添加到 config.json,然后打包并安装应用程序。

{
    "dll": "TraceFixup.dll",
    "config": {
        "traceLevels": {
            "filesystem": "allFailures"
        }
    }
}

默认情况下,跟踪修复会筛选掉可能视为“预期”的失败。 例如,应用程序可能尝试无条件删除文件,而不检查该文件是否已存在,并忽略结果。 这会带来不良的后果,一些意外的失败可能会被筛选掉,因此在上面的示例中,我们选择从文件系统函数接收所有失败。 我们这样做是因为我们之前知道尝试读取 Config.txt 文件会失败,并显示消息“找不到文件”。 这是经常观测到的失败,通常不会认为是意外。 在实践中,最好开始只筛选意外失败,然后如果仍然无法识别问题,则回退到所有失败。

默认情况下,来自跟踪修复的输出将发送到附加的调试程序。 对于此示例,我们不会附加调试程序,而是使用 SysInternals 中的 DebugView 程序查看输出。 运行应用后,可以看到与之前相同的失败,这会将我们指向相同的运行时修复程序。

TraceShim File Not Found

TraceShim Access Denied

调试、扩展或创建运行时修复程序

可以使用 Visual Studio 调试运行时修复程序、扩展运行时修复程序或从头开始创建修复程序。 需要执行这些操作才能成功。

  • 添加打包项目
  • 为运行时修复程序添加项目
  • 添加启动 PSF 启动器可执行文件的项目
  • 配置打包项目

完成后,解决方案将如下所示。

Completed solution

让我们看看此示例中的每个项目。

项目 目的
DesktopApplicationPackage 此项目基于 Windows 应用程序打包项目,并输出 MSIX 包。
Runtimefix 这是一个 C++ 动态链接库项目,其中包含一个或多个用作运行时修复程序的替换函数。
PSFLauncher 这是 C++ 空项目。 此项目是收集包支持框架的运行时可分发文件的位置。 它输出可执行文件。 该可执行文件是启动解决方案时运行的第一个项。
WinFormsDesktopApplication 此项目包含桌面应用程序的源代码。

要查看包含所有这些类型项目的完整示例,请参阅 PSFSample

让我们逐步完成在解决方案中创建和配置其中每个项目的步骤。

创建打包解决方案

如果还没有桌面应用程序的解决方案,请在 Visual Studio 中创建新的空白解决方案

Blank solution

你可能还需添加你拥有的任何应用程序项目。

添加打包项目

如果还没有“Windows 应用程序打包项目”,请创建一个项目并将其添加到解决方案。

Package project template

有关 Windows 应用程序打包项目的详细信息,请参阅使用 Visual Studio 打包应用程序

在“解决方案资源管理器”中,右键单击打包项目,选择“编辑”,然后将其添加到项目文件的底部:

<Target Name="PSFRemoveSourceProject" AfterTargets="ExpandProjectReferences" BeforeTargets="_ConvertItems">
<ItemGroup>
  <FilteredNonWapProjProjectOutput Include="@(_FilteredNonWapProjProjectOutput)">
  <SourceProject Condition="'%(_FilteredNonWapProjProjectOutput.SourceProject)'=='<your runtime fix project name goes here>'" />
  </FilteredNonWapProjProjectOutput>
  <_FilteredNonWapProjProjectOutput Remove="@(_FilteredNonWapProjProjectOutput)" />
  <_FilteredNonWapProjProjectOutput Include="@(FilteredNonWapProjProjectOutput)" />
</ItemGroup>
</Target>

为运行时修复程序添加项目

将 C++ 动态链接库 (DLL) 项目添加到解决方案。

Runtime fix library

右键单击该项目,然后选择“属性”

在属性页中,找到“C++ 语言标准”字段,然后在该字段旁边的下拉列表中,选择“ISO C++17 标准 (/std:c++17)”选项。

ISO 17 Option

右键单击该项目,然后在上下文菜单中,选择“管理 Nuget 包”选项。 确保“包源”选项设置为“全部”或 "nuget.org"

单击该字段旁边的设置图标。

搜索 PSF* Nuget 包,然后为此项目安装它。

nuget package

如果要调试或扩展现有运行时修复程序,请使用本指南的“查找运行时修复程序”部分中所述的指导添加你获取的运行时修复程序文件。

如果打算创建全新的修复程序,请不要向此项目添加任何内容。 本指南稍后部分将帮助你将正确的文件添加到此项目。 目前,我们将继续设置解决方案。

添加启动 PSF 启动器可执行文件的项目

将 C++ 空项目项目添加到解决方案。

Empty project

使用上一部分所述的相同指导将 PSF Nuget 包添加到此项目。

打开项目的属性页,然后在“常规”设置页中,根据应用程序的体系结构将“目标名称”属性设置为 PSFLauncher32PSFLauncher64

PSF Launcher reference

在解决方案中将项目引用添加到运行时修复程序项目。

runtime fix reference

右键单击引用,然后在“属性”窗口中应用这些值。

属性 Value
复制本地 True
复制本地附属程序集 True
引用程序集输出 True
链接库依赖项 False
链接库依赖项输入 False

配置打包项目

在打包项目中,右键单击“应用程序”文件夹,然后选择“添加引用”

Add Project Reference

选择 PSF 启动器项目和你的桌面应用程序项目,然后选择“确定”按钮。

Desktop project

注意

如果没有应用程序的源代码,只需选择 PSF 启动器项目即可。 我们将介绍如何在创建配置文件时引用可执行文件。

在“应用程序”节点中,右键单击 PSF 启动器应用程序,然后选择“设置为入口点”

Set entry point

将一个名为 config.json 的文件添加到打包项目,然后将以下 json 文本复制并粘贴到文件中。 将“包操作”属性设置为“内容”

{
    "applications": [
        {
            "id": "",
            "executable": "",
            "workingDirectory": ""
        }
    ],
    "processes": [
        {
            "executable": "",
            "fixups": [
                {
                    "dll": "",
                    "config": {
                    }
                }
            ]
        }
    ]
}

为每个键提供一个值。 使用此表作为指南。

数组 key
applications id 使用包清单中 Application 元素的 Id 属性的值。
applications 可执行文件 要启动的可执行文件的包相对路径。 在大多数情况下,可以在修改包清单文件之前从中获取此值。 它是 Application 元素的 Executable 属性值。
applications workingDirectory (可选)要用作启动的应用程序之工作目录的包相对路径。 如果未设置此值,操作系统将使用 System32 目录作为应用程序的工作目录。
进程 可执行文件 在大多数情况下,这是上面配置的 executable 的名称,只是删除了路径和文件扩展名。
修复 dll 要加载的修复 DLL 的包相对路径。
修复 config (可选)控制修复 DLL 的行为方式。 此值的确切格式因修复而异,因为每个修复都可以根据自己的需要解释此 "blob"。

完成后,config.json 文件将如下所示。

{
  "applications": [
    {
      "id": "DesktopApplication",
      "executable": "DesktopApplication/WinFormsDesktopApplication.exe",
      "workingDirectory": "WinFormsDesktopApplication"
    }
  ],
  "processes": [
    {
      "executable": ".*App.*",
      "fixups": [ { "dll": "RuntimeFix.dll" } ]
    }
  ]
}

注意

applicationsprocessesfixups 键是数组。 这意味着你可以使用 config.json 文件指定多个应用程序、进程和修复 DLL。

调试运行时修复程序

在 Visual Studio 中按 F5 启动调试器。 首先启动的是 PSF 启动器应用程序,然后启动目标桌面应用程序。 要调试目标桌面应用程序,必须通过选择“调试”->“附加到进程”,然后选择应用程序进程来手动附加到桌面应用程序进程。 要允许使用本机运行时修复程序 DLL 调试 .NET 应用程序,请选择托管和本机代码类型(混合模式调试)。

设置完成后,你可以在桌面应用程序代码和运行时修复程序项目中的代码行旁边设置断点。 如果没有应用程序的源代码,则只能在运行时修复程序项目中的代码行旁边设置断点。

由于 F5 调试通过从包布局文件夹路径部署松散文件来运行应用程序,而不是从 .msix/.appx 包进行安装,因此布局文件夹通常没有与已安装的包文件夹相同的安全限制。 因此,在应用运行时修复程序之前,可能无法重现包路径访问拒绝错误。

要解决此问题,请使用 .msix/.appx 包部署,而不是 F5 松散文件部署。 要创建 .msix/.appx 包文件,请使用 Windows SDK 中的 MakeAppx 实用工具,如上所述。 或者,在 Visual Studio 中,右键单击应用程序项目节点并选择“应用商店”->“创建应用包”

Visual Studio 的另一个问题是,它没有内置支持附加到调试程序启动的任何子进程。 这使得很难在目标应用程序的启动路径中调试逻辑,目标应用程序必须在启动后由 Visual Studio 手动附加。

要解决此问题,请使用支持子进程附加的调试程序。 请注意,通常无法将实时 (JIT) 调试器附加到目标应用程序。 这是因为大多数 JIT 技术都涉及通过 ImageFileExecutionOptions 注册表项启动调试器来代替目标应用。 这会导致 PSFLauncher.exe 用来将 FixupRuntime.dll 注入目标应用的绕行机制失败。 WinDbg 包含在 "Debugging Tools for Windows" 中,并从“Windows SDK”获取,支持子进程附加。 现在还支持直接启动和调试 UWP 应用

要将目标应用程序启动调试为子进程,请启动 WinDbg

windbg.exe -plmPackage PSFSampleWithFixup_1.0.59.0_x86__7s220nvg1hg3m -plmApp PSFSample

WinDbg 提示符下,启用子调试并设置适当的断点。

.childdbg 1
g

(在目标应用程序启动并中断调试器之前执行)

sxe ld fixup.dll
g

(在加载修复 DLL 之前执行)

bp ...

注意

PLMDebug 还可用于在启动时将调试程序附加到应用,并且也包含在 "Debugging Tools for Windows" 中。 但是,它的使用比 WinDbg 现在提供的直接支持更复杂。

支持

有问题? 请在 MSIX 技术社区网站上的包支持框架对话空间上询问我们。