Почему LoadLibrary терпит неудачу, в то время как DllImportAttribute работает? - PullRequest
0 голосов
/ 10 сентября 2018

Я создаю .NET-приложение для клиента, которое выполняет ввод-вывод с одной из сторонних систем. Поскольку они регулярно меняют пароль этой системы, я должен извлекать его динамически, вызывая собственную DLL, которую они предоставляют в выделенном каталоге (не считая моего EXE-файла).

Однако у меня проблемы с динамической загрузкой DLL с использованием LoadLibraryEx . Странно то, что я могу вызвать библиотеку, используя DllImportAttribute .

Это то, что я сделал до сих пор:

Согласно этому ответу SO , я использую следующий код (в конструкторе), чтобы попытаться динамически загрузить 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 или одна из ее зависимостей не может быть найдена .
  • Когда я вызываю LoadLibraryEx с DoNotResolveDllReferences, тогда я получаю рабочий дескриптор, но после этого я не могу вызвать GetProcAddress (код ошибки 127) - я подозреваю, что для этого мне нужно полностью загрузить DLL.
  • Когда я открываю собственную DLL в Dependencies (что по сути является Dependency Walker для Win10), я ясно вижу, что отсутствует одна из статически связанных DLL Missing FastMM DLL
  • Однако, если я скопирую DLL помимо моего EXE-файла и использую атрибут 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
}

РЕДАКТИРОВАТЬ после комментария Ганса Пассанта:

Общая цель - возможность заменить / обновить собственную DLL, когда мое приложение (служба Windows) работает. Я обнаружил изменение файла и затем перезагрузил DLL. Я не совсем уверен, возможно ли это с DllImportAttribute без перезапуска службы.

И я должен быть более конкретным в данной проблеме: я не смог загрузить нативную DLL-библиотеку, используя LoadLibraryEx, независимо от того, была ли она размещена рядом с моим EXE-файлом, или в другой случайной папке, или в SysWow64. Почему это работает с DllImportAttribute? Я почти уверен, что отсутствующая DLL-библиотека суб-зависимостей FastMM не присутствует в моей системе (ни рядом с реальной DLL, ни в любой Windows каталог).

Ответы [ 2 ]

0 голосов
/ 11 сентября 2018

@ HansPassant и @David Heffernan правы: на самом деле я пытался загрузить две разные версии DLL (у одной из них была зависимость FastMM, у другой - нет). Спасибо за вашу помощь и извините за неудобства.

0 голосов
/ 10 сентября 2018

Это потому, что путь порядка поиска DLL. В Windows, когда приложение пытается загрузить DLL, базовая система автоматически ищет какой-либо путь к DLL, поэтому давайте представим, что путь поиска DLL в Windows выглядит примерно так:

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

B) \Windows

C) \Windows\system32

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

Подробнее о базовом механизме вы можете прочитать в этой документации Microsoft .

Найдите DLL, от которой зависит ваша основная DLL, и найдите, где она хранится в системе, добавьте ее каталог в путь поиска DLL Windows, используя AddDllDirectory или SetDllDirectory .

  • Если dll уже загружена в память любым из запущенных процессов, Windows автоматически использует ее вместо поиска, так что вы можете загрузить FastMM DLL в память вручную, используя LoadLibrary, а затем попытаться загрузить основную DLL, и это тоже должно решить проблему.
...