包支持框架入门

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

本文详细介绍了包支持框架的每个组件,并分步介绍了如何使用它。

了解包支持框架中的内容

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

包支持框架

过程如下:

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

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

包支持框架 DLL 注入

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

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

使用进程监视器识别问题

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

ProcMon 应用筛选器

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

ProcMon 事件

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

ProcMon 排除成功

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

ProcMon Config.txt

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

应用Config.txt

下图显示了第二个问题。

ProcMon Logfile

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

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

PSF 包含现在可以使用的运行时修复,例如文件重定向修复。

文件重定向修复

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

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

社区中的运行时修复

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

步骤 3:应用运行时修复

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

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

让我们完成每个任务。

创建包布局文件夹

如果已有 .msix (或 .appx) 文件,则可以将其内容解压缩到布局文件夹中,该文件夹将充当包的暂存区域。 可以使用 MakeAppx 工具从命令提示符处执行此操作,具体取决于 SDK 的安装路径,你可以在Windows 10电脑上找到makeappx.exe工具: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 unpack /p PSFSamplePackage_1.0.60.0_AnyCPU_Debug.msix /d PackageContents

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

包布局

如果没有 .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

程序包内容现在应如下所示。

包二进制文件

修改包清单

在文本编辑器中打开包清单,然后将元素的属性Application设置为 Executable 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 架构指南:

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

processesfixupsapplications键是数组。 这意味着可以使用 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 中,并提供应用行为的详细诊断视图,类似于进程监视器。 它专门设计用于揭示应用程序兼容性问题。 若要使用 Trace Fixup,请将 DLL 添加到包,将以下片段添加到 config.json,然后打包并安装应用程序。

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

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

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

找不到 TraceShim 文件

TraceShim 访问被拒绝

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

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

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

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

已完成的解决方案

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

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

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

让我们演练在解决方案中创建和配置每个项目的步骤。

创建包解决方案

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

空白解决方案

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

添加打包项目

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

包项目模板

有关 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) 项目添加到解决方案。

运行时修复库

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

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

ISO 17 选项

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

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

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

nuget 包

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

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

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

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

空项目

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

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

PSF 启动器参考

在解决方案中添加对运行时修复项目的项目引用。

运行时修复参考

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

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

配置打包项目

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

添加项目引用

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

桌面项目

注意

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

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

设置入口点

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

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

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

Array key
applications id 使用包清单中元素的属性Application的值Id
applications 可执行文件 要启动的可执行文件的包相对路径。 在大多数情况下,可以在修改包清单文件之前从包清单文件中获取此值。 它是元素的属性Application的值Executable
applications workingDirectory (可选) 要用作启动的应用程序的工作目录的包相对路径。 如果未设置此值,操作系统将使用 System32 目录作为应用程序的工作目录。
进程 可执行文件 在大多数情况下,这是上面配置的路径和文件扩展名的名称 executable
fixups dll 要加载的修复 DLL 的包相对路径。
fixups 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 包含在 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 还可用于在启动时将调试器附加到应用,并且也包含在 Windows 调试工具中。 但是,使用比 WinDbg 现在提供的直接支持更为复杂。

支持

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