LoadLibrary, по-видимому, загружает неправильную DLL

У меня странная проблема с LoadLibrary в Windows. Сначала немного предыстории. Это приложение зависит от Qt, а Qt разбит на несколько библиотек. Пытаюсь обновить версии Qt, но никого не ломая. Новая библиотека Qt обратно совместима со старой. Это означает, что приложение, созданное с использованием более старой версии, может работать, если оно загружает более новую версию. Обратное неверно — приложение, созданное с использованием более новой версии, будет иметь отсутствующие символы, если загружена более старая версия.

Библиотеки Qt находятся в каталогах конкретной версии (например, c:\qt\qt-4.5.2\lib и c:\qt\qt-4.8.1\lib). Существует также общий каталог, который большинство разработчиков имеет в своем PATH, который содержит «текущие» версии всех сторонних библиотек, которые мы используем (назовем его c:\common\lib). Именно здесь библиотеки Qt обычно находятся при запуске приложения.

Я поместил библиотеки новой версии Qt в общее расположение, и все вроде бы работало нормально, за исключением одного случая. Рассматриваемое приложение разделено на несколько библиотек, некоторые из которых загружаются с помощью вызова LoadLibrary(). Некоторые из этих DLL, загружаемых во время выполнения, зависят от библиотек Qt. В одном случае загруженная DLL зависит от QtXml, которая сама зависит от QtCore.

Вот где это становится странным. Приложение зависит от QtCore, а также загружает библиотеку, зависящую от QtXml. Приложение и библиотека были собраны со старой версией Qt. Если это приложение запускается только с общим каталогом в PATH, все работает, потому что библиотеки DLL новой версии Qt загружаются из общего каталога. Однако, если PATH содержит каталог, в котором старые библиотеки DLL версии Qt хранятся перед общим каталогом, то загрузка DLL времени выполнения завершится ошибкой с отсутствующими символами.

(Эта ситуация возникает при выполнении автоматизированного модульного тестирования, когда сценарии явно задают PATH для использования конкретной версии библиотеки.)

Насколько я могу судить, приложение загружает старую версию QtCore.dll, а загружаемая во время выполнения DLL (каким-то образом) загружает новую версию QtXml.dll, что не удается, потому что уже загруженный QtCore не имеет нужных символов.

Но это кажется невозможным, поскольку PATH — это что-то вроде c:\qt\qt-4.5.2\lib;c:\common\lib (плюс другие несвязанные пути). Если я удалю более новую версию QtXml из общего каталога lib (не заменю ее старой версией, а просто удалю), то LoadLibrary() преуспеет, потому что она загружает версию 4.5.2 всех библиотек Qt. Но это не очень хорошее долгосрочное решение, так как запуск без каталога конкретной версии Qt в PATH (общий) не сможет найти QtXml.

Как это могло произойти? Как может LoadLibrary() (или что-то, что он рекурсивно вызывает для разрешения зависимостей библиотеки) загрузить библиотеку из позже в PATH? Я не могу найти ничего, что указывало бы на то, что каталогу общей библиотеки уделяется особое внимание (это не установленный каталог DLL). Это не упоминается в процессе сборки, это просто то, что разработчики имеют в своем PATH для удобства.

Кстати, аналогичная ситуация существует в Linux с LD_LIBRARY_PATH и dlopen(), и там все работает отлично. Это то, что Windows делает по-другому, чего я не понимаю. Кто-нибудь знает, что может пойти не так?


person Scott Minster    schedule 19.06.2012    source источник
comment
Каждая проблема DLL Hell возникает, когда вы храните DLL в общем каталоге, растет, когда вы начинаете полагаться на переменную окружения PATH, и поджигает ваш дом, когда вы начинаете смешивать версии. Не делитесь DLL.   -  person Hans Passant    schedule 19.06.2012
comment
В установленной системе это не будет проблемой, так как версия сборки будет совмещена с приложением. На самом деле это только проблема разработки, когда одновременно можно использовать несколько версий сторонних библиотек.   -  person Scott Minster    schedule 19.06.2012


Ответы (1)


LoadLibrary имеет много удивительных поступков. Убедитесь, что вы полностью просмотрели все Примечания к нему в MSDN.

Если уже загружена библиотека (любой версии) с таким же именем, LoadLibrary просто возвращает дескриптор уже загруженной DLL. Это может сыграть роль в вашем сценарии.

Затем, если вы указали относительный путь или просто имя файла, LoadLibrary применяет тайные правила поиска. Ваша переменная PATH обычно является последним местом для поиска. Вполне вероятно, что какое-то другое правило находит «неправильную» DLL еще до того, как она дойдет до проверки PATH. Хорошим советом является всегда использовать абсолютный путь к файлу, который вы хотите загрузить, чтобы быть уверенным, что его правила поиска не захватывают неправильный файл. Обычный недостаток безопасности заключается в том, что вы не контролируете, где ищет LoadLibrary, и злоумышленник убеждает ваше приложение загрузить поддельную DLL.

И, наконец, установщик может применить перенаправление DLL, которое может переопределить то, что вы запрашиваете, и где это можно найти. Я не уверен, что это характерно для DLL Qt или нет, но вы можете проверить свой реестр.

Иногда я использовал ProcMon от SysInternals для наблюдения за программой во время ее загрузки. библиотеки DLL. Вы можете увидеть каждое место, которое он проверяет, что может дать вам представление о том, почему он находит неправильную версию.

person Adrian McCarthy    schedule 19.06.2012