PAYATHON 2026

.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:

  1. 确认该文件属于哪个组件,以及这个组件是否仍在使用。
  2. 更新该组件的部署方式和依赖配置。
  3. 清理所有节点及临时部署目录中的旧文件。
  4. 回收应用池或重启服务,确保旧模块不再驻留。
  5. 再次检查新进程加载的 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 从系统目录加载,手动复制可能造成版本、体系结构或安全更新不一致,引出新的问题。

备注:内容仅供参考。