【问题标题】:Why does LoadLibrary fail while DllImportAttribute works?为什么 LoadLibrary 在 DllImportAttribute 工作时失败?
【发布时间】:2018-09-10 12:30:41
【问题描述】:

我正在为一个使用其第三方系统之一执行 I/O 的客户端创建一个 .NET 应用程序。由于他们定期更改此系统的密码,我应该通过调用他们在专用目录中提供的本机 DLL 来动态检索它(除了我的 EXE 文件之外)。

但是,我无法使用LoadLibraryEx 动态加载 DLL。奇怪的是我可以使用DllImportAttribute 调用库。

这是我到目前为止所做的:

根据这个SO answer,我使用以下代码(在构造函数中)尝试动态加载DLL:

public PasswordProvider(string dllPath)
{
    if (!File.Exists(dllPath))
        throw new FileNotFoundException($"The DLL \"{dllPath}\" does not exist.");
    _dllHandle = NativeMethods.LoadLibraryEx(dllPath, IntPtr.Zero, LoadLibraryFlags.None);
    if (_dllHandle == IntPtr.Zero)
        throw CreateWin32Exception($"Could not load DLL from \"{dllPath}\".");

    var procedureHandle = NativeMethods.GetProcAddress(_dllHandle, GetPasswordEntryPoint);
    if (procedureHandle == IntPtr.Zero)
        throw CreateWin32Exception("Could not retrieve GetPassword function from DLL.");
    _getPassword = Marshal.GetDelegateForFunctionPointer<GetPasswordDelegate>(procedureHandle);
}
  • 调用 LoadLibraryEx 时,结果句柄为 null,错误代码为 126,通常表示 DLL or one of its dependencies could not be found。
  • 当我用DoNotResolveDllReferences 调用LoadLibraryEx 时,我得到了一个工作句柄,但之后我无法调用GetProcAddress(错误代码127)- 我怀疑我必须为此完全加载DLL。李>
  • 当我在Dependencies(本质上是Win10 的Dependency Walker)中打开本机DLL 时,我可以清楚地看到其中一个静态链接的DLL 丢失了
  • 但是,如果我复制 EXE 文件之外的 DLL 并使用 DllImportAttribute,我可以调用 DLL
[DllImport(DllPath, EntryPoint = GetPasswordEntryPoint, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern long GetPassword(long systemId, string user, byte[] password);

这怎么可能? 我还以为DllImportAttribute 背后的机制在内部也使用了LoadLibary。我的代码在哪里不同?我错过了什么明显的东西吗?

只是一些注释:

  • 我不能只使用DllImportAttribute,因为我不能指定以这种方式在专用目录中进行搜索(DLL 必须位于我的 EXE 文件旁边或位于常见的 Windows 位置才能正常工作)。
  • 我也尝试了LoadLibrary 而不是LoadLibraryEx,但结果相同。

Simons 评论后编辑: NativeMethods 定义如下:

private static class NativeMethods
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr LoadLibrary(string dllName);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr LoadLibraryEx(string dllFileName, IntPtr reservedNull, LoadLibraryFlags flags);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr moduleHandle);
}

[Flags]
private enum LoadLibraryFlags : uint
{
    None = 0,
    DoNotResolveDllReferences = 0x00000001,
    LoadIgnoreCodeAuthorizationLevel = 0x00000010,
    LoadLibraryAsDatafile = 0x00000002,
    LoadLibraryAsDatafileExclusive = 0x00000040,
    LoadLibraryAsImageResource = 0x00000020,
    LoadLibrarySearchApplicationDir = 0x00000200,
    LoadLibrarySearchDefaultDirs = 0x00001000,
    LoadLibrarySearchDllLoadDir = 0x00000100,
    LoadLibrarySearchSystem32 = 0x00000800,
    LoadLibrarySearchUserDirs = 0x00000400,
    LoadWithAlteredSearchPath = 0x00000008
}

在 Hans Passant 发表评论后编辑:

总体目标是能够在我的应用程序(Windows 服务)运行时替换/更新本机 DLL。我检测到文件更改,然后重新加载 DLL。我不太确定 DllImportAttribute 是否可以在不重新启动服务的情况下实现这一点。

我应该更具体地说明实际问题:我无法使用LoadLibraryEx 加载本机DLL,无论它是放在我的EXE 旁边,还是在另一个随机文件夹中,还是在SysWow64 中。 为什么它可以与DllImportAttribute 一起使用?我很确定我的系统上不存在丢失的FastMM subdependency DLL(既不在实际的 DLL 旁边,也不在任何 Windows 目录中)。 p>

【问题讨论】:

  • 如何在 C# 中定义 NativeMethods.LoadLibraryEx?
  • @SimonMourier 我已更新问题以回答您的评论。
  • 引用:“我不能指定在专用目录中搜索这种方式”。当您调用 LoadLibrary() 时,这也不起作用。找到 DLL 没有问题,但是找不到它的依赖项。考虑返回 [DllImport] 并使用 SetDllDirectory() 或修改 PATH 环境变量以帮助操作系统找到所有必需的 DLL。最后但并非最不重要的一点是,将所需的所有 DLL 部署在与 exe 相同的目录中,这是迄今为止避免 DLL Hell 的最佳方法。
  • @HansPassant 非常感谢您的反馈,我已经用一些关于您的建议的信息更新了问题。
  • 它可能不适用于 DllImport。您可能正在寻找不同版本的 dll。想想看,你不可能加载依赖于调试 FastMM4 的 dll,因为你没有那个 dll。这里有很多混乱。

标签: c# .net dll interop pinvoke


【解决方案1】:

这是因为DLL搜索顺序路径。在 windows 中,当应用程序尝试加载 DLL 时,底层系统会自动搜索 DLL 的路径,所以我们假设 Windows 的 DLL 搜索路径如下所示:

A) . <-- current working directory of the executable, highest priority, first check

B) \Windows

C) \Windows\system32

D) \Windows\syswow64 <-- lowest priority, last check

您可以在this Microsoft documentation 中阅读有关底层机制的更多信息。

搜索你的主DLL依赖的DLL并找到它在系统上的存储位置,使用AddDllDirectory或SetDllDirectory将它的目录添加到Windows的DLL搜索路径中。

  • 如果 dll 已经被任何正在运行的进程加载到内存中,Windows 会自动使用它而不是搜索,因此您可以使用 LoadLibrary 手动将 FastMM DLL 加载到内存中,然后尝试加载主 DLL,它也应该可以解决问题。

【讨论】:

  • 那么你的意思是DllImportAttribute机制找到了丢失的FastMM DLL,但我的代码没有?我没有这个 DLL,并且它不在我的 windows 或 SysWow64 文件夹中(它应该是 64 位 DLL,因为本机 DLL 也是 64 位)。
【解决方案2】:

@HansPassant 和@David Heffernan 是对的:我实际上尝试加载两个不同版本的 DLL(其中一个具有 FastMM 子依赖项,一个没有)。感谢您的帮助,对于给您带来的不便,我们深表歉意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    相关资源
    最近更新 更多