在一些 Windows 7 系统上,根据 dotnet 官方文档,需要安装上 KB2533623 补丁,才能运行 dotnet core 或 .NET 5 等应用。尽管非所有的设备都需要安装此,但这也让应用的分发不便,安装包上都需要带上补丁给用户安装。此补丁同时也要求安装完成之后重启系统,这对用户端来说,也是较不方便。本文来聊聊为什么 dotnet core 一系的框架依赖于此补丁
特别感谢 lsj 给我讲解 Win32 调用部分的知识和帮我调查具体的原因,我只是记录的工具人
补丁
开始之前,先来理一下所需补丁的情况,不想看补丁细节还请跳到下文主题这章。 准确来说,在当前 2021.12.25 官方推荐 Win7 系统打上的补丁是 KB3063858 补丁
也有伙伴推荐给我的是安装 KB4457144 补丁。但是 KB4457144 补丁太大了,包含太多内容,带上这个补丁没什么优势
在 GitHub 上的 Security update KB2533623 no longer available · Issue #20459 · dotnet/docs 讨论上,有大佬表示 KB3063858 或 KB4457144 包含了 KB2533623 补丁内容:
值得一说的是对需要安装 KB3063858 补丁的系统来说,大多数都需要额外加上证书,参阅 https://www.microsoft.com/pkiops/Docs/Repository.htm 因此我认为对于客户端分发来说,打上 KB2533623 补丁似乎更好。但是 KB2533623 当前被微软下架了,请看 Security update KB2533623 no longer available · Issue #20459 · dotnet/docs
这是 KB2533623 的下载地址: http://www.microsoft.com/download/details.aspx?familyid=c79c41b0-fbfb-4d61-b5d8-cadbe184b9fc
另外,在刚推送 dotnet core 3.0 的预览版本时,有伙伴在 WPF 官方仓库反馈说需要加上 KB2999226 补丁。此 KB2999226 补丁是 Windows 中的 Universal C Runtime 更新
的内容,参阅 https://github.com/dotnet/wpf/issues/2009#issuecomment-543872453
也许可以使用 runtime.win7-x64.Microsoft.NETCore.Windows.ApiSets NuGet 库 代替 KB2999226 补丁内容,只需要将 api-xxxxx.dll
这些文件拷贝到输出路径即可。或者是解包 VC++ 2015 的分发包里的文件,将 api-xxxxx.dll
和 ucrtbase.dll
拷贝到输出路径即可
因此,对于客户端分发来说,似乎采用 KB2533623 最小补丁,然后在输出路径上拷贝好 api-xxxxx.dll
这些文件到输出路径是最佳方法
下载地址:
- KB2533623 x86
- MD5: EDF1D538C85F24EC0EF0991E6B27F0D7
- SHA1: 25BECC0815F3E47B0BA2AE84480E75438C119859
- KB2533623 x64
- MD5: 0A894C59C245DAD32E6E48ECBDBC8921
- SHA1: 8A59EA3C7378895791E6CDCA38CC2AD9E83BEBFF
- KB3063858 32-bit
- KB3063858 64-bit
主题
清理好了各个补丁的关系之后,咱回到主题。为什么在 dotnet core 一系都有此要求?而且还不是对所有 Win7 系统都有此要求,这是为什么?回答这两个问题,可以从 dotnet core 的 dotnet host core run 开始聊起
在 Windows 下,咱双击运行的 dotnet core 的可执行 exe 文件,其实是一个 AppHost 文件。咱编写的 Main 函数,在非单文件模式下,是放在同名的 dll 里面。详细关于 AppHost 请参阅 dotnet core 应用是如何跑起来的 通过AppHost理解运行过程
在 dotnet host core run 里,对应代码是 src\coreclr\hosts\corerun\corerun.hpp
文件,在这里需要拉起 hostpolicy.dll
组件。加载此组件的代码如下,不过代码内容不重要哈
inline bool try_load_hostpolicy(pal::string_t mock_hostpolicy_value)
{
const char_t* hostpolicyName = W("hostpolicy.dll");
pal::mod_t hMod = (pal::mod_t)::GetModuleHandleW(hostpolicyName);
if (hMod != nullptr)
return true;
// Check if a hostpolicy exists and if it does, load it.
if (pal::does_file_exist(mock_hostpolicy_value))
hMod = (pal::mod_t)::LoadLibraryExW(mock_hostpolicy_value.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
if (hMod == nullptr)
pal::fprintf(stderr, W("Failed to load mock hostpolicy at path '%s'. Error: 0x%08x\n"), mock_hostpolicy_value.c_str(), ::GetLastError());
return hMod != nullptr;
}
以上最关键的只有这一行代码 LoadLibraryExW(mock_hostpolicy_value.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
以上代码通过 LoadLibraryExW 函数进行加载库。调用时传入了 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
参数。根据官方文档的描述,调用此函数,如果加入了 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
参数,将要求 KB2533623 补丁
除以上逻辑之外,在 dotnet 仓库里还可以找到其他的各个部分的 LoadLibraryExW 函数上,也都带上了 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
参数,列表如下:
src\coreclr\dlls\dbgshim\dbgshim.cpp
: CreateDebuggingInterfaceFromVersion2 函数src\coreclr\hosts\corerun\corerun.hpp
: try_load_hostpolicy 函数src\coreclr\hosts\coreshim\CoreShim.cpp
: TryLoadHostPolicy 函数src\coreclr\vm\nativelibrary.cpp
: DetermineLibNameVariations 函数src\native\corehost\hostmisc\pal.windows.cpp
: load_library 函数
看起来文件不多,就看哪个伙伴想不开就去改改挖坑吧
此 LoadLibraryExW 函数是放在 Kernel32.dll 的,相同的需求,在 dotnet core 里也间接依赖于 AddDllDirectory 和 SetDefaultDllDirectories 和 RemoveDllDirectory 等方法。如官方文档描述,刚好这些方法也都相同依赖 KB2533623 补丁
通过如上描述,可以了解到,在 dotnet core 需要补丁的原因是调用了 Kernel32.dll 的新(大约10年前加的)函数,对于一些 Win7 旧设备上,没有更新 Kernel32.dll 加上函数
因此,判断 dotnet core 所依赖环境可以有两个方法,第一个方法是判断 KB2533623 补丁是否有安装,另一个方法是判断 Kernel32.dll 里是否存在 AddDllDirectory 函数。第一个方法判断是不靠谱的,因为不一定需要 KB2533623 补丁,如上文描述,换成其他几个补丁也可以。判断补丁安装可以使用下面命令行代码,更多请参阅 dotnet 通过 WMI 获取系统补丁 和 PowerShell 通过 WMI 获取补丁
BAT:
wmic qfe GET hotfixid | findstr /C:"KB2533623"
另外,判断是否存在 KB2533623 补丁,不存在则安装的 bat 脚本代码如下
echo off
cd /d %~dp0
wmic qfe GET hotfixid | findstr /C:"KB2533623"
if %errorlevel% equ 0 (Echo Patch KB2533623 installed) else (
wusa Windows6.1-KB2533623-x64.msu /quiet /norestart
wusa Windows6.1-KB2533623-x86.msu /quiet /norestart
exit /b 1
)
exit /b 0
第二个方法我比较推荐,判断代码如下:
using PInvoke;
using System;
namespace AddDllDirectoryDetectCs
{
class Program
{
static void Main(string[] args)
{
using (var hModule = Kernel32.LoadLibrary("Kernel32.dll"))
{
if (!hModule.IsInvalid)
{
IntPtr hFarProc = Kernel32.GetProcAddress(hModule, "AddDllDirectory");
if (hFarProc != IntPtr.Zero)
{
Console.WriteLine("Either running on Win8+, or KB2533623 is installed");
}
else
{
Console.Write("Likely running on Win7 or older OS, and KB2533623 is not installed");
}
}
}
// 以下是判断 Universal C Runtime 的逻辑,可以忽略
using (var hModule = Kernel32.LoadLibraryEx("UCRTBASE.dll", IntPtr.Zero, Kernel32.LoadLibraryExFlags.LOAD_LIBRARY_SEARCH_SYSTEM32))
{
if (!hModule.IsInvalid)
{
Console.WriteLine("UCRT is available - Either running on Win10+ or KB2999226 is installed");
}
else
{
Console.WriteLine("UCRT is not available - Likely running on OS older than Win10 and KB2999226 is not installed");
}
}
}
}
}
以上代码由 WPF 框架官方开发 Vatsan Madhavan 大佬提供,请看 vatsan-madhavan/checknetcoreprereqs: Helper utility to check .NET Core Prerequisites on Windows systems
收到 Vatsan Madhavan 大佬赞
参考
LoadLibraryExW function (libloaderapi.h) - Win32 apps Microsoft Docs
LoadLibraryExA function (libloaderapi.h) - Win32 apps Microsoft Docs
LoadLibraryW function (libloaderapi.h) - Win32 apps Microsoft Docs
windows - SetDllDirectory does not cascade, so dependency DLLs cannot be loaded - Stack Overflow
SetDllDirectoryA function (winbase.h) - Win32 apps Microsoft Docs
AddDllDirectory function (libloaderapi.h) - Win32 apps Microsoft Docs
Windows installer should warn about missing required updated · Issue #2452 · dotnet/runtime
RemoveDllDirectory function (libloaderapi.h) - Win32 apps
Unable to load DLL 'wpfgfx_cor3.dll' or one of its dependencies · Issue #2009 · dotnet/wpf
SqlClient: Unable to load DLL 'sni.dll' · Issue #16905 · dotnet/runtime
API Set Usage Question · Issue #5075 · dotnet/runtime
Cannot load CoreCLR.dll · Issue #5076 · dotnet/runtime
使用 C# 判断指定的 Windows 更新是否已安装-码农很忙
Windows 7 安装 msu 系统更新时的常见报错及解决办法-码农很忙