为什么DllImportAttribute工作时LoadLibrary失败?

时间:2018-09-10 12:30:41

标签: c# .net dll interop pinvoke

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

但是,我无法使用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时,结果句柄为空,错误代码为126,通常表示DLL or one of its dependencies could not be found
  • 当我使用LoadLibraryEx调用DoNotResolveDllReferences时,我得到了一个有效的句柄,但是之后,我无法调用GetProcAddress(错误代码127)-我怀疑必须完全加载DLL。
  • 当我在Dependencies(本质上是Win10的Dependency Walker)中打开本机DLL时,我可以清楚地看到其中一个静态链接的DLL丢失了Missing FastMM 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目录中) )。

2 个答案:

答案 0 :(得分:0)

这是因为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

您可以在custom event accessors中了解有关基础机制的更多信息。

搜索您的主DLL依赖于它的DLL,并找到它在系统上的存储位置,然后使用this Microsoft documentationAddDllDirectory将其目录添加到Windows的DLL搜索路径中。

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

答案 1 :(得分:0)

@HansPassant和@David Heffernan是正确的:实际上,我尝试加载DLL的两个不同版本(其中一个具有FastMM子依赖关系,一个没有)。多谢您的协助,不便之处,敬请谅解。