动态链接库重定向

DLL 加载器是操作系统 (OS) 的一部分,用于解析对 DLL 的引用、加载和链接 DLL。 有很多技术可影响 DLL 加载器的行为,并控制它实际加载几个候选 DLL 中的哪一个,而动态链接库 (DLL) 重定向就是这种技术之一。

此功能还有其他名称,例如 .local、Dot Local、DotLocal 和 Dot Local Debugging。

DLL 版本控制问题

如果你的应用程序依赖于共享 DLL 的特定版本,而另一个应用程序随该 DLL 的更高版本或更低版本一起安装,这可能会导致兼容性问题和不稳定性,即它可能会导致应用启动失败。

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

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

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

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

DLL 加载器遵循的规则可确保从 Windows 系统位置(例如系统文件夹 %SystemRoot%\system32)加载系统 DLL。 这些规则可避免植入式攻击;在这种攻击中,攻击者将他们编写的代码放到他们可写入的位置,然后说服一些进程加载并执行它。 但是,加载器的规则也使得在 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 目录中,并(重新)启动应用。

验证所有内容是否正常工作

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

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

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

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

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

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

如果同时存在 C:\myapp\myapp.exe.localC:\myapp\mydll.dllLoadLibrary 会加载 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 副本(而且不会导致应用失败)。