包支持框架是一个开源工具包,可帮助你将修补程序应用于现有桌面应用程序(无需修改代码),以便它可以在 MSIX 容器中运行。 包支持框架可帮助应用程序遵循新式运行时环境的最佳做法。
本文详细介绍了包支持框架的每个组件,并逐步介绍了如何使用它。
了解包支持框架中的内容
包支持框架包含可执行文件、运行时管理器 DLL 和一组运行时修复。
下面是该过程:
- 创建一个配置文件,指定要应用于应用程序的修补程序。
- 修改包以指向包支持框架 (PSF) 启动器可执行文件。
当用户启动应用程序时,包支持框架启动器是运行的第一个可执行文件。 它会读取配置文件,并将运行时修补程序和运行时管理器 DLL 注入应用程序进程。 当应用程序需要在 MSIX 容器中运行时,运行时管理器会应用修补程序。
步骤 1:识别打包的应用程序兼容性问题
首先,为应用程序创建包。 然后,安装它,运行它,并观察其行为。 可能会收到有助于识别兼容性问题的错误消息。 还可以使用 进程监视器 来识别问题。 常见问题与有关工作目录和程序路径权限的应用程序假设有关。
使用进程监视器识别问题
进程监视器 是观察应用的文件和注册表作及其结果的强大实用工具。 这有助于了解应用程序兼容性问题。 打开进程监视器后,添加筛选器(筛选器 > 筛选器...),以仅包含来自应用程序可执行文件的事件。
将显示事件列表。 对于其中许多事件, SUCCESS 一词将显示在 “结果 ”列中。
(可选)可以筛选事件以仅显示失败。
如果怀疑文件系统访问失败,请在 System32/SysWOW64 或包文件路径下搜索失败的事件。 筛选器也可以在此处提供帮助。 从此列表底部开始,向上滚动。 最新发生的失败显示在列表底部。 最注意包含字符串的错误,例如“拒绝访问”和“未找到路径/名称”,并忽略看起来不可疑的内容。 PSFSample 有两个问题。 可以在下图中显示的列表中看到这些问题。
在此映像中显示的第一个问题中,应用程序无法从位于“C:\Windows\SysWOW64”路径的“Config.txt”文件中读取。 应用程序不太可能尝试直接引用该路径。 最有可能的是,它尝试使用相对路径从该文件中读取,默认情况下,“System32/SysWOW64”是应用程序的工作目录。 这表明应用程序预期其当前工作目录将设置为包中的某个位置。 在 appx 内部查看,可以看到该文件与可执行文件位于同一目录中。
下图显示了第二个问题。
在此问题中,应用程序无法将.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.PackageSupportFramework 或 PSF 以查找 Nuget.org 上的包。然后,安装它。
将包支持框架文件添加到包
将所需的 32 位和 64 位 PSF DLL 和可执行文件添加到包目录中。 使用下表作为指南。 你还需要包含所需的任何运行时修复。 在我们的示例中,我们需要文件重定向运行时修复。
| 应用程序可执行文件为 x64 | 应用程序可执行文件为 x86 |
|---|---|
| PSFLauncher64.exe | PSFLauncher32.exe |
| PSFRuntime64.dll | PSFRuntime32.dll |
| PSFRunDll64.exe | PSFRunDll32.exe |
程序包内容现在应如下所示。
修改包清单
在文本编辑器中打开包清单,然后将元素的属性Executable设置为 Application PSF Launcher 可执行文件的名称。 如果知道目标应用程序的体系结构,请选择相应的版本,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 架构的指南:
| 数组 | 钥匙 | 价值 |
|---|---|---|
| 应用程序 | id | 使用包清单中 Id 元素 Application 属性的值。 |
| 应用程序 | 可执行 | 要启动的可执行文件的包相对路径。 在大多数情况下,可以在修改包清单文件之前从包清单文件获取此值。
Executable元素的Application属性的值。 |
| 应用程序 | workingDirectory | (可选)要用作启动的应用程序的工作目录的包相对路径。 如果未设置此值,作系统将使用 System32 目录作为应用程序的工作目录。 |
| 进程 | 可执行 | 在大多数情况下,这将是上面配置项去掉路径和文件扩展名后的 executable 的名称。 |
| 修正 | dll | 修复的包相对路径,要加载的 .msix/.appx。 |
| 修正 | 配置 | (可选)控制修复 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
运行应用程序并观察应用了运行时修复的行为。 根据需要重复诊断和打包步骤。
检查包支持框架是否正在运行
可以检查运行时修补程序是否正在运行。 执行此作的一种方法是打开 任务管理器 并单击 “更多详细信息”。 查找应用包支持框架的应用,并展开应用详细信息,以便获取更多详细信息。 你应该能看到包支持框架正在运行。
使用 Trace 修复
诊断打包应用程序兼容性问题的替代方法是使用 Trace Fixup。 此 DLL 包含在 PSF 中,并提供应用行为的详细诊断视图,类似于进程监视器。 它专为揭示应用程序兼容性问题而设计。 欲使用跟踪修复,首先将 DLL 添加到软件包中,在 config.json中添加以下代码片段,最后打包并安装您的应用程序。
{
"dll": "TraceFixup.dll",
"config": {
"traceLevels": {
"filesystem": "allFailures"
}
}
}
默认情况下,Trace Fixup 跟踪修复功能会筛选出可能被视为“预期”的失败。 例如,应用程序可能尝试无条件删除文件,而无需检查该文件是否已存在,忽略结果。 这会导致某些意外的失败可能会被过滤掉,为了避免这种情况,在上面的示例中,我们选择接收来自文件系统函数的所有失败。 我们这样做是因为我们事先知道,尝试从 Config.txt 文件读取时会失败,并显示“找不到文件”这个消息。 这是经常观察到的失败,通常不假定是意外的。 在实践中,最好只筛选出意外失败,如果仍有无法识别的问题,则退回到查看所有失败。
默认情况下,Trace Fixup 的输出将发送到附加的调试器。 对于此示例,我们不会附加调试器,而是使用 SysInternals 中的 DebugView 程序查看其输出。 运行应用后,我们可以看到和之前一样的故障,这提示我们需要进行相同的运行时错误修复建议。
调试、扩展或创建运行时修复
可以使用 Visual Studio 调试运行时修补程序、扩展运行时修补程序或从头开始创建修补程序。 你需要完成这些事情才能取得成功。
- 添加打包项目
- 为运行时修正添加项目
- 添加启动 PSF 启动器可执行文件的项目
- 配置打包项目
完成后,解决方案将如下所示。
让我们看一下此示例中的每个项目。
| 项目 | 目的 |
|---|---|
| 桌面应用程序包 | 此项目基于 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++ Dynamic-Link 库(DLL) 项目添加到解决方案。
右键单击该项目,然后选择 “属性”。
在属性页中,找到 C++语言标准 字段,然后在该字段旁边的下拉列表中,选择 ISO C++17 Standard (/std:c++17) 选项。
右键单击该项目,然后在上下文菜单中,选择“ 管理 Nuget 包 ”选项。 确保“ 包源 ”选项设置为“ 全部 ”或 “nuget.org”。
单击该字段旁边的设置图标。
搜索 PSF* Nuget 包,然后安装此项目。
如果要调试或扩展现有运行时修补程序,请使用本指南的 “查找运行时修复 ”部分中所述的指导添加你获取的运行时修补程序文件。
如果您打算创建全新修复,请不要向此项目添加任何内容。 本指南稍后将帮助你将正确的文件添加到此项目。 目前,我们将继续设置您的解决方案。
添加启动 PSF 启动器可执行文件的项目
将C++ 空项目 项目添加到解决方案。
使用上一部分所述的相同指南将 PSF Nuget 包添加到此项目。
打开项目的属性页,然后在 “常规 设置”页中,将 “目标名称 ”属性设置为 PSFLauncher32 或 PSFLauncher64,具体取决于应用程序的体系结构。
在解决方案中添加对运行时修复项目的项目引用。
右键单击引用,然后在 “属性” 窗口中应用这些值。
| 资产 | 价值 |
|---|---|
| 复制本地 | 真 实 |
| 复制本地附属程序集 | 真 实 |
| 引用程序集输出 | 真 实 |
| 链接库依赖项 | 假 |
| 链接库依赖项输入 | 假 |
配置打包项目
在打包项目中,右键单击 “应用程序” 文件夹,然后选择“ 添加引用”。
选择 PSF 启动器项目和桌面应用程序项目,然后选择“ 确定 ”按钮。
注释
如果没有应用程序的源代码,只需选择 PSF 启动器项目即可。 我们将介绍如何在创建配置文件时引用可执行文件。
在 “应用程序 ”节点中,右键单击 PSF 启动器应用程序,然后选择“ 设置为入口点”。
将一个名为 config.json 的文件添加到您的打包项目中,然后将以下 json 文本复制并粘贴到该文件中。 将 包操作 属性设置为 内容。
{
"applications": [
{
"id": "",
"executable": "",
"workingDirectory": ""
}
],
"processes": [
{
"executable": "",
"fixups": [
{
"dll": "",
"config": {
}
}
]
}
]
}
为每个键提供一个值。 使用此表作为指南。
| 数组 | 钥匙 | 价值 |
|---|---|---|
| 应用程序 | id | 使用包清单中 Id 元素 Application 属性的值。 |
| 应用程序 | 可执行 | 要启动的可执行文件的包相对路径。 在大多数情况下,可以在修改包清单文件之前从包清单文件获取此值。
Executable元素的Application属性的值。 |
| 应用程序 | workingDirectory | (可选)要用作启动的应用程序的工作目录的包相对路径。 如果未设置此值,作系统将使用 System32 目录作为应用程序的工作目录。 |
| 进程 | 可执行 | 在大多数情况下,这将是上面配置项去掉路径和文件扩展名后的 executable 的名称。 |
| 修正 | dll | 要加载的修正 DLL 的包相对路径。 |
| 修正 | 配置 | (可选)控制修复 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 技术社区网站上的 包支持框架 对话空间上询问我们。