动态链接库重定向

DLL 加载程序 是作系统(OS)的一部分,它解析对 DLL 的引用、加载和链接它们。 动态链接库 (DLL) 重定向是一种可影响 DLL 加载程序的行为的技术之一,并控制它实际加载的几个候选 DLL 之一。

此功能的其他名称包括 .localDot LocalDotLocalDot Local Debugging

DLL 版本控制问题

如果应用程序依赖于共享 DLL 的特定版本,并且另一个应用程序随该 DLL 的较新版本或较旧版本一起安装,则这可能会导致兼容性问题和不稳定;它可能会导致应用启动失败。

DLL 加载程序在调用进程从(可执行文件的文件夹)加载,然后才能在其他文件系统位置中查找。 因此,一种解决方法是在可执行文件的文件夹中安装应用所需的 DLL。 这有效地使 DLL 成为私有的。

但这并不能解决 COM 的问题。 可以安装并注册两个不兼容版本的 COM 服务器(即使在不同的文件系统位置),但只有一个用于注册 COM 服务器的位置。 因此,只会激活最新的已注册 COM 服务器。

可以使用重定向来解决这些问题。

加载和测试专用二进制文件

DLL 加载程序遵循的规则可确保从 Windows 系统位置加载系统 DLL,例如系统文件夹(%SystemRoot%\system32)。 这些规则避免了种植攻击:攻击者将编写的代码置于可以写入的位置,然后说服一些进程加载和执行它。 但是,加载程序的规则也使得在 OS 组件上工作更加困难,因为运行它们需要更新系统;这是一个非常有影响力的变化。

但是,可以使用重定向来加载 DLL 的专用副本(例如测试或测量代码更改的性能影响)。

如果要在公共 WindowsAppSDK GitHub 存储库中参与源代码,则需要测试更改。 同样,这是一种方案,你可以使用重定向来加载 DLL 的专用副本,而不是随 Windows 应用 SDK 一起提供的版本。

选项

事实上,有两种方法可以确保应用使用所需的 DLL 版本:

提示

如果你是开发人员或管理员,则应对现有应用程序使用 DLL 重定向。 这是因为它不需要对应用本身进行任何更改。 但是,如果要创建新应用或更新现有应用,并且想要将应用与潜在问题隔离开来,则创建并排组件。

可选:配置注册表

若要启用 DLL 重定向计算机范围,必须创建新的注册表值。 在键 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options下,使用名称 DevOverrideEnable创建新的 DWORD 值。 将值设置为 1,然后重新启动计算机。 或使用以下命令(并重新启动计算机)。

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" /v DevOverrideEnable /t REG_DWORD /d 1

设置注册表值后,即使应用具有应用程序清单,也遵循 DotLocal DLL 重定向。

创建重定向文件或文件夹

若要使用 DLL 重定向,你将创建 重定向文件重定向文件夹(具体取决于你拥有的应用类型),如本主题后面的部分所示。

如何重定向打包应用的 DLL

打包的应用需要 DLL 重定向的特殊文件夹结构。 以下路径是启用重定向时加载程序将查找的位置:

<Drive>:\<path_to_package>\microsoft.system.package.metadata\application.local\

如果能够编辑 .vcxproj 文件,则使用包创建和部署该特殊文件夹的一种简便方法是在 .vcxproj中为生成添加一些额外的步骤:

<ItemDefinitionGroup>
    <PreBuildEvent>
        <Command>
            del $(FinalAppxManifestName) 2&gt;nul
            <!-- [[Using_.local_(DotLocal)_with_a_packaged_app]] This makes the extra DLL deployed via F5 get loaded instead of the system one. -->
            if NOT EXIST $(IntDir)\microsoft.system.package.metadata\application.local MKDIR $(IntDir)\microsoft.system.package.metadata\application.local
            if EXIST "&lt;A.dll&gt;" copy /y "&lt;A.dll&gt;" $(IntDir)\microsoft.system.package.metadata\application.local 2&gt;nul
            if EXIST "&lt;B.dll&gt;" copy /y "&lt;B.dll&gt;" $(IntDir)\microsoft.system.package.metadata\application.local 2&gt;nul
        </Command>
    </PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
    <!-- Include any locally built system experience -->
    <Media Include="$(IntDir)\microsoft.system.package.metadata\application.local\**">
        <Link>microsoft.system.package.metadata\application.local</Link>
    </Media>
</ItemGroup>

让我们演练一些配置的作用。

  1. 为 Visual Studio 启动而不调试(或 启动调试)体验设置 PreBuildEvent

    <ItemDefinitionGroup>
      <PreBuildEvent>
    
  2. 确保中间目录中具有正确的文件夹结构。

    <!-- [[Using_.local_(DotLocal)_with_modern_apps]] This makes the extra DLL deployed via Start get loaded instead of the system one. -->
    if NOT EXIST $(IntDir)\microsoft.system.package.metadata\application.local MKDIR $(IntDir)\microsoft.system.package.metadata\application.local
    
  3. 将本地生成的任何 DLL(并希望优先使用系统部署的 DLL)复制到 application.local 目录中。 可以从任意位置选取 DLL(我们建议为 .vcxproj使用可用的宏)。 只需确保这些 DLL 在该项目之前生成;否则,它们将丢失。 此处显示了两个 模板 复制命令;根据需要使用任意数量的占位符,并编辑 <path-to-local-dll> 占位符。

      if EXIST "<path-to-local-dll>" copy /y "<path-to-local-dll>" $(IntDir)\microsoft.system.package.metadata\application.local 2&gt;nul
      if EXIST "<path-to-local-dll>" copy /y "<path-to-local-dll>" $(IntDir)\microsoft.system.package.metadata\application.local 2&gt;nul
      </Command>
    </PreBuildEvent>
    
  4. 最后,指示你想要在部署的包中包含特殊目录及其内容。

    <ItemGroup>
      <!-- Include any locally built system experience -->
      <Media Include="$(IntDir)\microsoft.system.package.metadata\application.local\**">
        <Link>microsoft.system.package.metadata\application.local</Link>
      </Media>
    </ItemGroup>
    

此处所述的方法(使用中间目录)使源代码控制登记保持干净,并减少意外提交已编译二进制文件的可能性。

接下来,只需(重新)部署项目。 若要获得干净、完整(重新)的部署,可能还需要卸载/清除目标设备上的现有部署。

手动复制二进制文件

如果无法按照上面所示的方式使用 .vcxproj,则可以使用几个简单的步骤在目标设备上实现相同的目的。

  1. 确定包的安装文件夹。 可以在 PowerShell 中发出命令 Get-AppxPackage,并查找返回的 InstallLocation

  2. 使用该 InstallLocation 更改 ACL,以允许自己创建文件夹/复制文件。 编辑此脚本中的 <InstallLocation> 占位符,然后运行脚本:

    cd <InstallLocation>\Microsoft.system.package.metadata
    takeown /F . /A
    icacls  . /grant Administrators:F
    md <InstallLocation>\Microsoft.system.package.metadata\application.local
    
  3. 最后,手动将本地生成的任何 DLL(并希望优先使用系统部署的 DLL)复制到 application.local 目录中,然后 [re]启动应用。

验证一切是否正常工作

若要确认在运行时加载正确的 DLL,可以将 Visual Studio 与附加的调试器一起使用。

  1. 打开 模块 窗口(调试>Windows>模块)。
  2. 找到 DLL,并确保 路径 指示重定向的副本,而不是系统部署的版本。
  3. 确认只加载给定 DLL 的一个副本。

如何重定向未打包应用的 DLL

重定向文件必须命名为 <your_app_name>.local。 因此,如果应用的名称 Editor.exe,请将重定向文件命名为 Editor.exe.local。 必须在可执行文件的文件夹中安装重定向文件。 还必须在可执行文件的文件夹中安装 DLL。

重定向文件 内容将被忽略;它仅存在会导致 DLL 加载程序在加载 DLL 时先检查可执行文件的文件夹。 为了缓解 COM 问题,该重定向同时应用于完整路径和部分名称加载。 因此,无论指定 LoadLibrary 路径还是 LoadLibraryEx,重定向都会在 COM 案例中发生。 如果在可执行文件的文件夹中找不到 DLL,则加载遵循其通常的搜索顺序。 例如,如果应用 C:\myapp\myapp.exe 使用以下路径调用 LoadLibrary

C:\Program Files\Common Files\System\mydll.dll

如果同时存在 C:\myapp\myapp.exe.localC:\myapp\mydll.dll,则 LoadLibrary 加载 C:\myapp\mydll.dll。 否则,LoadLibrary 加载 C:\Program Files\Common Files\System\mydll.dll

或者,如果名为 C:\myapp\myapp.exe.local 的文件夹存在,并且它包含 mydll.dll,则 LoadLibrary 加载 C:\myapp\myapp.exe.local\mydll.dll

如果使用 DLL 重定向,并且应用无权访问搜索顺序中的所有驱动器和目录,则 LoadLibrary 拒绝访问后立即停止搜索。 如果 未使用 DLL 重定向LoadLibrary 会跳过无法访问的目录,然后继续搜索。

最好在包含应用的同一文件夹中安装应用 DLL;即使未使用 DLL 重定向。 这可确保安装应用不会覆盖 DLL 的其他副本(从而导致其他应用失败)。 此外,如果遵循此良好做法,则其他应用不会覆盖 DLL 的副本(并且不会导致应用失败)。