为什么 LoadLibrary 在 DllImportAttribute 工作时失败?
Why does LoadLibrary fail while DllImportAttribute works?
我正在为客户创建一个 .NET 应用程序,该应用程序使用他们的第三方系统之一执行 I/O。由于他们会定期更改此系统的密码,我应该通过调用他们在专用目录(不在我的 EXE 文件之外)中提供的本机 DLL 来动态检索它。
但是,我无法使用 LoadLibraryEx. The weird thing is that I can call the library using the DllImportAttribute 动态加载 DLL。
这是我到目前为止所做的:
根据此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 (which essentially is Dependency Walker for Win10), I can clearly see that one of the statically linked DLLs is missing
中打开本机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
,但结果相同。
西蒙斯评论后编辑:
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 目录)。
是因为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 or SetDllDirectory.[=14=将它的目录添加到Windows的DLL搜索路径中]
- 如果 dll 已经被任何 运行 进程加载到内存中 Windows 自动使用它而不是搜索,因此您可以使用 LoadLibrary 手动将 FastMM DLL 加载到内存中,然后尝试加载主DLL,它应该也能解决问题。
@HansPassant 和@David Heffernan 是对的:我实际上尝试加载两个不同版本的 DLL(其中一个具有 FastMM 子依赖性,一个没有)。感谢您的帮助,对于给您带来的不便,我们深表歉意。
我正在为客户创建一个 .NET 应用程序,该应用程序使用他们的第三方系统之一执行 I/O。由于他们会定期更改此系统的密码,我应该通过调用他们在专用目录(不在我的 EXE 文件之外)中提供的本机 DLL 来动态检索它。
但是,我无法使用 LoadLibraryEx. The weird thing is that I can call the library using the DllImportAttribute 动态加载 DLL。
这是我到目前为止所做的:
根据此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 (which essentially is Dependency Walker for Win10), I can clearly see that one of the statically linked DLLs is missing
- 但是,如果我复制 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
,但结果相同。
西蒙斯评论后编辑:
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 目录)。
是因为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 or SetDllDirectory.[=14=将它的目录添加到Windows的DLL搜索路径中]
- 如果 dll 已经被任何 运行 进程加载到内存中 Windows 自动使用它而不是搜索,因此您可以使用 LoadLibrary 手动将 FastMM DLL 加载到内存中,然后尝试加载主DLL,它应该也能解决问题。
@HansPassant 和@David Heffernan 是对的:我实际上尝试加载两个不同版本的 DLL(其中一个具有 FastMM 子依赖性,一个没有)。感谢您的帮助,对于给您带来的不便,我们深表歉意。