.NET 项目中 Security.dll 导致的间歇性连接错误
结论
不必因为 ABCsecurity.dll 的文件名以 security.dll 结尾而再次重命名。Windows 加载本机 DLL 时会匹配完整文件名,因此加载 security.dll 时不会误选 ABCsecurity.dll。
这个异常说明进程实际加载了一个名为 security.dll 的模块,但该模块没有导出 EnumerateSecurityPackagesW。常见原因是应用目录、bin 目录、临时部署目录或 PATH 中存在同名 DLL,导致 Windows 系统 DLL 被遮蔽。也有可能是部分服务器或工作进程仍在使用旧版本文件。故障之所以表现为“间歇性”,通常是因为请求被分配到了不同节点或不同应用池进程,或者各进程加载 DLL 的状态不一致。
为什么会出现连接错误
EnumerateSecurityPackagesW 是 Windows SSPI 的相关接口。.NET 建立需要 Windows 身份验证或安全协商的网络连接时,可能会间接调用这个接口。
如果加载器找到的是错误的 security.dll,调用入口点时就会抛出 EntryPointNotFoundException。由于异常发生在网络请求期间,外层通常会将其包装成:
The underlying connection was closed:
An unexpected error occurred on a receive.
表面上看,这是一次网络接收失败,实际原因却是本机 DLL 解析错误。
ABCsecurity.dll 不会因为后缀相同而被当作 security.dll。对加载器来说,下面是两个完全不同的文件名:
security.dll
ABCsecurity.dll
不过,仍需确认 ABCsecurity.dll 本身或其安装包是否额外部署了一个名为 security.dll 的依赖项。
建议的排查和处理步骤
1. 检查所有部署位置
在每台应用服务器上检查以下位置:
- 应用根目录
bin目录- 发布包和安装目录
- ASP.NET 临时编译或影子复制目录
- 服务的当前工作目录
PATH包含的第三方目录- 插件及支付组件目录
重点查找文件名完全等于 security.dll 的文件,不要查找所有以这个字符串结尾的文件。
如果使用了负载均衡,需要逐台检查服务器。只要有一个节点残留同名 DLL,就可能引发间歇性故障。
2. 确认进程实际加载的文件路径
可以通过诊断代码记录当前进程已经加载的模块:
using System;
using System.Diagnostics;
public static class NativeModuleDiagnostics
{
public static void LogSecurityModules()
{
using Process process = Process.GetCurrentProcess();
foreach (ProcessModule module in process.Modules)
{
if (module.ModuleName.Equals(
"security.dll",
StringComparison.OrdinalIgnoreCase) ||
module.ModuleName.Equals(
"secur32.dll",
StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(
$"{module.ModuleName}: {module.FileName}");
}
}
}
}
在权限受限、进程位数不同或某些托管环境中,模块枚举可能失败。诊断代码不应干扰正常的支付流程:
try
{
NativeModuleDiagnostics.LogSecurityModules();
}
catch (Exception ex)
{
Console.WriteLine($"Unable to enumerate native modules: {ex.Message}");
}
还可以使用 Process Explorer 或 Process Monitor,查看工作进程加载 security.dll 时最终解析到哪个路径。这通常是定位问题最直接的办法。
3. 清理同名文件并重启进程
如果应用目录中存在非系统的 security.dll:
- 确认该文件属于哪个组件,以及这个组件是否仍在使用。
- 更新该组件的部署方式和依赖配置。
- 清理所有节点及临时部署目录中的旧文件。
- 回收应用池或重启服务,确保旧模块不再驻留。
- 再次检查新进程加载的 DLL 路径。
DLL 被加载后,仅替换磁盘上的文件通常不会改变当前进程中已经加载的模块,因此必须启动新进程。
4. 检查自己的 P/Invoke 声明
如果业务代码直接调用了 EnumerateSecurityPackagesW,应按照对应的 Windows API 声明系统模块。该接口通常应从 Secur32.dll 解析:
using System;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
[DllImport(
"Secur32.dll",
EntryPoint = "EnumerateSecurityPackagesW",
CharSet = CharSet.Unicode,
SetLastError = true)]
internal static extern int EnumerateSecurityPackages(
out int packageCount,
out IntPtr packageInfo);
}
函数签名还必须与目标 Windows API、进程位数及结构体定义完全一致。如果调用来自 .NET Framework 内部或第三方库,不要直接修改框架文件,而应升级相关组件或解决 DLL 名称冲突。
是否应该彻底重命名 ABCsecurity.dll
按照 DLL 的解析规则,没有这个必要。security.dll 和 ABCsecurity.dll 之间不存在模糊匹配。
如果它是自有组件,也可以改用含义更明确、不易与系统组件混淆的名称,例如:
ABC.Payment.SecurityProvider.dll
这样更便于维护,但不能代替根因排查。真正需要清除的是部署目录中那个文件名完全等于 security.dll 的非系统模块,以及所有仍会依赖或复制该文件的安装流程。
不要尝试将系统目录中的 security.dll 或 Secur32.dll 复制到应用的 bin 目录。系统 DLL 应由 Windows 从系统目录加载,手动复制可能造成版本、体系结构或安全更新不一致,引出新的问题。
备注:内容仅供参考。
