Начало тут:
1. динамически подключаемые библиотеки;
2. о динамически подключаемых библиотеках подробнее;
3. преимущества динамического связывания;
4. создание динамически подключаемой библиотеки;
5. функция точки входа DLL;
6. динамическое связывание во время запуска;
7. динамическое связывание во время выполнения.
Перевод с английского статьи от 31.05.2018 г. «Dynamic-Link Library Search Order»:
https://docs.microsoft.com/ru-ru/windows/win32/dlls/dynamic-link-library-search-order
(На данный момент на этом сайте нет перевода этой статьи на русский, есть только версия на английском.)
[Операционная] система может содержать множество версий одной и той же динамически подключаемой библиотеки (DLL). Приложения могут контролировать местоположение, из которого DLL загружается, путем определения полного пути [к файлу DLL] или с помощью другого механизма, такого как манифест [файл на языке XML с информацией о приложении, в том числе может содержать информацию о необходимых приложению DLL]. Если эти методы не используются, система ищет DLL во время запуска программы так, как описано в этой статье.
Так как статья довольно длинная [и в ней много повторов, поэтому она довольно нудная], то вот ее содержание со ссылками на ее разделы (все они находятся на этой же странице):
- факторы, влияющие на процесс поиска;
- порядок поиска DLL для приложений «Windows Store»:
- стандартный порядок поиска DLL для приложений «Windows Store»;
- альтернативный порядок поиска DLL для приложений «Windows Store»;
- порядок поиска DLL для приложений настольного компьютера:
- стандартный порядок поиска DLL для приложений настольного компьютера;
- альтернативный порядок поиска DLL для приложений настольного компьютера;
- порядок поиска DLL с использованием флагов LOAD_LIBRARY_SEARCH.
Факторы, влияющие на процесс поиска
Следующие факторы влияют на то, будет ли система искать DLL:
- если DLL с тем же самым именем модуля уже загружена в память, система проверит только [следует ли выполнить] перенаправление [перенаправление включается наличием в папке с исполняемым файлом приложения пустого файла с таким же именем, как у приложения, и расширением
.local
и указывает системе начать поиск DLL с папки, в которой находится этот файл.local
] и манифест [на наличие указаний о местонахождении нужной DLL] и [при отсутствии перенаправления и манифеста] будет считать результатом поиска загруженную DLL, при этом не будет иметь значения, в каком каталоге эта загруженная DLL находится. Система не станет выполнять других действий по поиску DLL; - если DLL есть в списке библиотек DLL, известных той версии операционной системы Windows, в которой запущено приложение, система использует свою копию известной DLL (и свои копии библиотек DLL, от которых эта известная DLL зависит, если таковые имеются) вместо того, чтобы искать эту DLL. Увидеть список известных текущей системе DLL можно, обратившись к следующему ключу реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControl
Set\Control\Session Manager\KnownDLLs ; - если DLL имеет зависимости [от других DLL], система будет искать библиотеки DLL, от которых зависит исходная, так, как если бы эти библиотеки загружались с указанием только имен их модулей. Это правило остается верным, даже если исходная DLL загружалась с указанием полного пути к ней.
Порядок поиска DLL для приложений «Windows Store»
Когда приложение «Windows Store» загружает упакованный модуль [DLL] посредством вызова функции LoadPackagedLibrary, DLL должна находиться в графе зависимостей пакета для процесса. Подробнее об этом читайте в описании функции LoadPackagedLibrary. Когда приложение «Windows Store» загружает модуль [DLL] другими средствами и не указывает полный путь к нему, система ищет DLL и ее зависимости [модули, от которых эта DLL зависит] во время запуска программы так, как описано в этом разделе.
Замечание для пользователей предыдущих по отношению к свежим на дату написания статьи версиям операционных систем Windows (Windows 7 (вики: с 22.10.2009 г.), Windows Server 2008 R2 (вики: с 22.10.2009 г.), Windows Vista (вики: с 30.11.2006 г.), Windows Server 2008 (вики: с 12.12.2008 г.), Windows Server 2003 (вики: с 24.04.2003 г.) и Windows XP (вики: с 24.08.2001 г.)): приложения «Windows Store» поддерживаются, начиная с операционных систем «Windows 8» (вики: с 26.10.2012 г.) и «Windows Server 2012» (вики: с 04.09.2012 г.).
Перед тем, как система запустит поиск DLL, она проверит следующее:
- если DLL с таким же именем модуля уже загружена в память, система будет использовать загруженную DLL, при этом не будет иметь значения, в каком каталоге эта загруженная DLL находится. Система не станет выполнять других действий по поиску DLL;
- если DLL есть в списке библиотек DLL, известных той версии операционной системы Windows, в которой запущено приложение, система использует свою копию известной DLL (и свои копии библиотек DLL, от которых эта известная DLL зависит, если таковые имеются) вместо того, чтобы искать эту DLL. Увидеть список известных текущей системе DLL можно, обратившись к следующему ключу реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControl
Set\Control\Session Manager\KnownDLLs .
Если система должна выполнить поиск модуля или его зависимостей [других модулей, от которых зависит работа исходного модуля], система всегда будет использовать порядок поиска, предназначенный для приложений «Windows Store», даже если искомая зависимость представляет из себя не код приложения «Windows Store».
Стандартный порядок поиска DLL для приложений «Windows Store»
Если модуль DLL еще не загружен или не находится в списке известных системе DLL, то система начинает поиск в следующих местах в указанном ниже порядке:
- Граф зависимостей пакета для процесса. Местом поиска является пакет приложения плюс любые зависимости, отмеченные как
<PackageDependency>
в разделе<Dependencies>
манифеста пакета приложения. Зависимости ищутся системой в том порядке, в котором они появляются в манифесте; - Каталог, из которого был загружен вызывающий DLL процесс;
- Системный каталог (
%SystemRoot%\system32
).
Если DLL имеет зависимости [от других DLL], система будет искать библиотеки DLL, от которых зависит исходная, так, как если бы эти библиотеки загружались с указанием только имен их модулей. Это правило остается верным, даже если исходная DLL загружалась с указанием полного пути к ней.
Альтернативный порядок поиска DLL для приложений «Windows Store»
Если модуль меняет стандартный порядок поиска DLL посредством вызова функции LoadLibraryEx с флагом LOAD_WITH_ALTERED_SEARCH_PATH, система ищет в каталоге, из которого указанный [в параметре функции LoadLibraryEx] модуль был загружен, вместо каталога вызывающего процесса. Система выполняет поиск в следующих местах в указанном ниже порядке:
- Граф зависимостей пакета для процесса. Местом поиска является пакет приложения плюс любые зависимости, отмеченные как
<PackageDependency>
в разделе<Dependencies>
манифеста пакета приложения. Зависимости ищутся системой в том порядке, в котором они появляются в манифесте; - Каталог, из которого указанный [в параметре функции LoadLibraryEx] модуль был загружен;
- Системный каталог (
%SystemRoot%\system32
).
Порядок поиска DLL для приложений настольного компьютера
Приложения настольного компьютера [desktop applications] могут указывать место, из которого будет загружена DLL, посредством указания полного пути к DLL, могут использовать перенаправление поиска DLL, либо могут использовать манифест. Если ни один из этих методов не используется, система ищет DLL во время загрузки программы так, как описано в этом разделе.
Перед тем, как система запустит поиск DLL, она проверит следующее:
- если DLL с таким же именем модуля уже загружена в память, система будет использовать загруженную DLL, при этом не будет иметь значения, в каком каталоге эта загруженная DLL находится. Система не станет выполнять других действий по поиску DLL;
- если DLL есть в списке библиотек DLL, известных той версии операционной системы Windows, в которой запущено приложение, система использует свою копию известной DLL (и свои копии библиотек DLL, от которых эта известная DLL зависит, если таковые имеются) вместо того, чтобы искать эту DLL. Увидеть список известных текущей системе DLL можно, обратившись к следующему ключу реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControl
Set\Control\Session Manager\KnownDLLs .
Если DLL имеет зависимости [от других DLL], система будет искать библиотеки DLL, от которых зависит исходная, так, как если бы эти библиотеки загружались с указанием только имен их модулей. Это правило остается верным, даже если исходная DLL загружалась с указанием полного пути к ней.
Важное замечание. Если злоумышленник получит доступ к одному из каталогов, в которых выполняется поиск DLL, он может поместить в этот каталог вредоносную копию искомой DLL. Чтобы узнать способы предотвращения таких атак, читайте статью «Безопасность DLL».
Стандартный порядок поиска DLL для приложений настольного компьютера
Стандартный порядок поиска DLL, используемый системой, зависит от того, включен или выключен безопасный режим поиска DLL. Безопасный режим поиска DLL помещает текущий каталог пользователя позже в очередности хода поиска.
Безопасный режим поиска DLL по умолчанию включен. Чтобы его выключить, следует создать в реестре параметр HKEY_LOCAL_MACHINE\System\CurrentControl
Замечание для пользователей операционной системы Windows XP (вики: с 24.08.2001 г.; поддержка этой системы прекращена корпорацией «Microsoft» c 08.04.2014 г.): безопасный режим поиска DLL по умолчанию выключен. Чтобы включить его, следует создать параметр реестра SafeDllSearchMode и установить его значение в 1. Безопасный режим поиска DLL включен по умолчанию, начиная с «Windows XP» с установленным пакетом обновления SP2.
Если параметр реестра SafeDllSearchMode включен, порядок поиска DLL будет следующим:
- Каталог, из которого загружено приложение;
- Системный каталог [обычно это каталог
..\Windows\System32\
, но не всегда]. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу; - Системный каталог для 16-разрядных библиотек DLL [обычно это каталог
..\Windows\System\
, но не всегда]. Не существует функции [из набора Windows API], которая получала бы путь к этому каталогу, но поиск в этом каталоге всё равно выполняется; - Каталог операционной системы Windows [обычно это каталог
..\Windows\
, но не всегда]. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу; - Текущий каталог;
- Каталоги, перечисленные в переменной среды PATH. Заметим, что эта переменная не включает путь, определенный в разрезе каждого приложения ключом реестра App Paths [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\W
indows\CurrentVersion\App Paths] . Ключ реестра App Paths не используется при поиске DLL.
Если параметр реестра SafeDllSearchMode выключен, порядок поиска DLL будет следующим:
- Каталог, из которого загружено приложение;
- Текущий каталог;
- Системный каталог [обычно это каталог
..\Windows\System32\
, но не всегда]. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу; - Системный каталог для 16-разрядных библиотек DLL [обычно это каталог
..\Windows\System\
, но не всегда]. Не существует функции [из набора Windows API], которая получала бы путь к этому каталогу, но поиск в этом каталоге всё равно выполняется; - Каталог операционной системы Windows [обычно это каталог
..\Windows\
, но не всегда]. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу; - Каталоги, перечисленные в переменной среды PATH. Заметим, что эта переменная не включает путь, определенный в разрезе каждого приложения ключом реестра App Paths [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\W
indows\CurrentVersion\App Paths] . Ключ реестра App Paths не используется при поиске DLL.
Альтернативный порядок поиска DLL для приложений настольного компьютера
Стандартный порядок поиска DLL, используемый системой, может быть изменен с помощью вызова функции LoadLibraryEx с флагом LOAD_WITH_ALTERED_SEARCH_PATH. Стандартный порядок поиска DLL также может быть изменен с помощью вызова функции SetDllDirectory.
Замечание: стандартный порядок поиска DLL для процесса попадет под влияние вызова функции SetDllDirectory в родительском процессе, если этот вызов был выполнен до запуска текущего процесса.
Замечание для пользователей операционной системы Windows XP (вики: с 24.08.2001 г.; поддержка этой системы прекращена корпорацией «Microsoft» c 08.04.2014 г.): изменение стандартного порядка поиска DLL с помощью вызова функции SetDllDirectory не поддерживалось до «Windows XP» с установленным пакетом обновления SP1.
Если вы включили стратегию альтернативного порядка поиска DLL, она будет продолжать своё действие до того момента, пока все соответствующие исполняемые модули не будут найдены. После того, как система запустит в работу подпрограммы инициализации DLL, она вернется к стратегии стандартного порядка поиска DLL.
Функция LoadLibraryEx поддерживает альтернативный порядок поиска DLL, если она вызывается с флагом LOAD_WITH_ALTERED_SEARCH_PATH и в ее параметре lpFileName указан абсолютный путь.
Заметим, что стратегия стандартного порядка поиска DLL и стратегия альтернативного порядка поиска DLL, установленная функцией LoadLibraryEx с флагом LOAD_WITH_ALTERED_SEARCH_PATH, различаются только в одном: стандартный порядок поиска DLL начинается с каталога вызывающего приложения, а альтернативный порядок поиска DLL начинается с каталога исполняемого модуля, загружаемого функцией LoadLibraryEx.
Если параметр реестра SafeDllSearchMode включен, альтернативный порядок поиска DLL будет следующим:
- Каталог, определенный параметром lpFileName функции LoadLibraryEx;
- Системный каталог [обычно это каталог
..\Windows\System32\
, но не всегда]. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу; - Системный каталог для 16-разрядных библиотек DLL [обычно это каталог
..\Windows\System\
, но не всегда]. Не существует функции [из набора Windows API], которая получала бы путь к этому каталогу, но поиск в этом каталоге всё равно выполняется; - Каталог операционной системы Windows [обычно это каталог
..\Windows\
, но не всегда]. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу; - Текущий каталог;
- Каталоги, перечисленные в переменной среды PATH. Заметим, что эта переменная не включает путь, определенный в разрезе каждого приложения ключом реестра App Paths [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\W
indows\CurrentVersion\App Paths] . Ключ реестра App Paths не используется при поиске DLL.
Если параметр реестра SafeDllSearchMode выключен, альтернативный порядок поиска DLL будет следующим:
- Каталог, определенный параметром lpFileName функции LoadLibraryEx;
- Текущий каталог;
- Системный каталог [обычно это каталог
..\Windows\System32\
, но не всегда]. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу; - Системный каталог для 16-разрядных библиотек DLL [обычно это каталог
..\Windows\System\
, но не всегда]. Не существует функции [из набора Windows API], которая получала бы путь к этому каталогу, но поиск в этом каталоге всё равно выполняется; - Каталог операционной системы Windows [обычно это каталог
..\Windows\
, но не всегда]. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу; - Каталоги, перечисленные в переменной среды PATH. Заметим, что эта переменная не включает путь, определенный в разрезе каждого приложения ключом реестра App Paths [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\W
indows\CurrentVersion\App Paths] . Ключ реестра App Paths не используется при поиске DLL.
Функция SetDllDirectory поддерживает альтернативный порядок поиска DLL, если в ее параметре lpPathName указан путь. В этом случае альтернативный порядок поиска DLL будет следующим:
- Каталог, из которого загружено приложение;
- Каталог, указанный в параметре lpPathName функции SetDllDirectory;
- Системный каталог [обычно это каталог
..\Windows\System32\
, но не всегда]. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу; - Системный каталог для 16-разрядных библиотек DLL [обычно это каталог
..\Windows\System\
, но не всегда]. Не существует функции [из набора Windows API], которая получала бы путь к этому каталогу, но поиск в этом каталоге всё равно выполняется; - Каталог операционной системы Windows [обычно это каталог
..\Windows\
, но не всегда]. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу; - Каталоги, перечисленные в переменной среды PATH. Заметим, что эта переменная не включает путь, определенный в разрезе каждого приложения ключом реестра App Paths [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\W
indows\CurrentVersion\App Paths] . Ключ реестра App Paths не используется при поиске DLL.
Если в параметр lpPathName функции SetDllDirectory передана пустая строка, эта функция удаляет текущий каталог из поискового списка каталогов.
Функция SetDllDirectory в случае своей успешной отработки выключает безопасный режим поиска DLL, а указанный в ее параметре каталог включается в поисковый список каталогов. Чтобы восстановить безопасный режим поиска DLL, основанный на параметре реестра SafeDllSearchMode, и вернуть текущий каталог в поисковый список каталогов, следует вызвать функцию SetDllDirectory с ее параметром lpPathName, установленным в значение NULL.
Порядок поиска DLL с использованием флагов LOAD_LIBRARY_SEARCH
Приложение может определить порядок поиска DLL, используя один или более флагов LOAD_LIBRARY_SEARCH функции LoadLibraryEx. Приложение также может использовать флаги LOAD_LIBRARY_SEARCH в качестве параметра функции SetDefaultDllDirectories, чтобы настроить порядок поиска DLL для процесса. Приложение может задать дополнительные каталоги для настройки порядка поиска DLL, нужных процессу, с помощью функций AddDllDirectory или SetDllDirectory.
Замечание для пользователей предыдущих по отношению к свежим на дату написания статьи версиям операционных систем Windows (Windows 7 (вики: с 22.10.2009 г.), Windows Server 2008 R2 (вики: с 22.10.2009 г.), Windows Vista (вики: с 30.11.2006 г.), Windows Server 2008 (вики: с 12.12.2008 г.)): флаги LOAD_LIBRARY_SEARCH доступны на этих системах, если установлено обновление KB2533623.
Замечание для пользователей операционных систем Windows Server 2003 (вики: с 24.04.2003 г.) и Windows XP (вики: с 24.08.2001 г.): флаги LOAD_LIBRARY_SEARCH в этих системах не поддерживаются.
В каких именно каталогах происходит поиск DLL, зависит от флагов, указанных при вызове функций SetDefaultDllDirectories или LoadLibraryEx. Если используется более, чем один флаг, соответствующие каталоги обыскиваются в следующем порядке:
- Каталог, содержащий DLL (флаг LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR). В этом каталоге ищутся только DLL, от которых зависит загружаемая DLL;
- Каталог приложения (флаг LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
- Пути, явно добавленные с помощью функции AddDllDirectory (флаг LOAD_LIBRARY_SEARCH_USER_DIRS) или функции SetDllDirectory. Если был добавлен больше, чем один путь, порядок, в котором эти пути обыскиваются, является неопределенным;
- Системный каталог (флаг LOAD_LIBRARY_SEARCH_SYSTEM32).
Если приложение не производит вызова функции LoadLibraryEx с любым из флагов LOAD_LIBRARY_SEARCH, чтобы настроить порядок поиска DLL для процесса, система ищет библиотеки DLL либо с помощью стандартного порядка поиска, либо с помощью альтернативного порядка поиска.
On windows, we want applications to be able to start without the need for a path specification, yet we need our DLLs to be found. In the past, we relied on PATH for this, but this is going away, for the following reasons:
- The PATH variable on windows is extremely limited (260 chars) and it runs out fast. on User’s machines, it can lead to problems.
- If two or more versions of an application are installed, one of the two will end up using the libraries of the other, with horrible consequences, depending on which come first in the PATH specification.
- Using the path doesn’t solve the problem that some users encounter, that is, if they have a library in the System32 directory that happens to have the same name, it will override ours and generate strange errors.
Windows has a weird lookup strategy for dlls, and it depends on flags and other circumstances. What [http://msdn.microsoft.com/en-us/library/7d83bc18.aspx MSDN] says is the following order, in the most simplified approach:
- The directory where the executable module for the current process is located.
- The current directory.
- The Windows system directory. The GetSystemDirectory function retrieves the path of this directory.
- The Windows directory. The GetWindowsDirectory function retrieves the path of this directory.
- The directories listed in the PATH environment variable.
An important thing to note is that “executable module” is not necessarily the running exe. If exe depends on a library dll1 which in turn depends on another library dll2, the “executable module” will be dll1. To add to the confusion, we need to deal with both regular dependencies (e.g. linkage) and with dynamic opening (“dlopen” like) which is extensively used by python to load “pyd” files (basically, compiled python modules).
We want to make the last entry irrelevant, that is, we don’t want to depend on the path for library lookup. Excluding the system directories and magic registry tricks, we are left only with the first option. Which means that we need to put all stuff in the right place. We also don’t want system libraries to take over or influence our installation.
A temporary solution has been devised, but requires care. Also, the solution may involve two different strategies for developers and users (deployed version)
Developers
For developers, keep relying on %PATH% for lookup, and point this to the external-libs/build/lib. You may still have weird behaviors if there’s another application installed, another python, or another set of libraries somewhere on the system. This is an acceptable compromise.
The alternative would be littering external-libs/build/ with copies of dll files in the right places, near the relevant modules.
Users
As said above, the right place depends on what entity we are talking about. There are many possible situations that may occur
- direct dependencies of the executable
- direct dependencies of the python27.dll
- direct dependencies of a dll
- dlopen() dependencies of python (that is, pyd files)
- direct dependencies of the pyd files.
Four possible places where dlls may need to go
- in bin
- in lib
- and somewhere under lib/site-packages/
- in lib/qt-plugins
I present here all cases, together with the proper placement.
- Direct dependency (e.g. link time library) of the executable: in bin. Examples: python27.dll.
- Dynamically loaded (“dlopen”) library, typically pyd: in lib or in appropriate lib/site-packages dir. This stuff gets opened dynamically by python using LoadLibrary. The lookup is performed thanks to sys.path.
- Dynamic dependency for qt plugins. under lib/qt-plugins. This is resolved thanks to a QCoreApplication.addLibraryPath() invocation. Qt takes care of the rest.
- Link dependencies (dlls) of pyd files. These may go in two places: either on the side with the pyd (that is, under the lib/site-packages/whatever/ path), or in lib/. The second case is for special libraries that happens to be dependencies. The dll gets loaded at startup and when the python module is loaded, apparently the windows loader finds out that the dependency is already mapped to VM and doesn’t complain nor explores alternatives.
NOTE: If you have both a direct dependency for an executable, and a direct or dynamic dependency on the same dll somewhere else, you might have to have two copies of the dll in two different places.
Open Problems
- can’t use mkl scalapack dynamically, because the dynamic dll is missing the “pzherk” symbol. We are forced to link it statically.
Other attempts
Here is a list of alternative strategies we examined to tackle the problem, unsuccessful for some reason or another.
- Using AddDllDirectory can’t be done. Windows 8 only
- Using SetDllDirectory doesn’t appear to work. It allows only one directory. An experiment on h5py led to it being unable to find another pyd file that was needed and was inside the site-packages/h5py. Putting all .pyd in /lib is not a good idea. It’s a game that ends as soon as we have two modules with the same filename.
- Using App Path in the registry, not an option. The lookup is done on the executable name, and the executable name will collide if the user installs two versions of the app.
- On windows, we can’t use the same trick we use on linux and OSX (having a pre-startup script that exports the proper paths), for both point one above, and because it would open a terminal. Workarounds may exist, but in the end the problem is much more complex. If we do this export within the C code, we would have to locate the absolute path of the executable, something that may not be easy. Also, we are still constrained to 260 chars, and we would have troubles anyway for subsequent executables spawned by the app.
- manifests. It’s unclear if they would help, but they are complex and require further analysis.
Published on Dec 16, 2018.
Edit: I added a section about delayed loading thanks to comments which pointed out that I had overlooked this
There are two ways of loading a shared library (.DLL on Windows, .so on most Unix platforms, and .dylib on macs). One is to use a function like LoadLibary
or dlopen
. Then you must manually extract the functions from the library with GetProcAddress
or dlsym
. Most linkers (all that I know of) also allow you to link with a shared library so that it’s loaded on startup and its functions are automatically resolved to symbols in your code. That’s particularly useful as it’s the only sensible way to load a C++ interface from a shared library.
The first way (the manual one) works almost the same way on all platforms. You can supply a path to the shared library file, either a relative or an absolute one, and it will work. I’m going to discuss the second way of loading shared libraries: the automatic one. There is a critical difference between how it works on Windows and other operating systems, namely, the way in which the executable1 searches for the shared library file to load.
If you’re a Windows-only programmer, you probably developed your own way to deal with DLL Hell, whether it’s by redirection or careful output directory management. Or, you know, alternatively, you try to use DLLs as little as possible. Anyway, you should know that on other operating systems when linking with a shared library (.so or .dylib file), you can supply a search path for it to the linker itself. In fact, to some people’s occasional frustration, the default search path when linking with no specific arguments is the absolute path to the shared library. This search path is called an rpath.
If you do multi-platform programming, you’ve probably experienced frustration with the lack of rpaths on Windows.
If you’re not a Windows programmer, I don’t know why you’re reading this article, but to put it simply: In Windows, shared libraries, or DLLs, don’t have rpaths. Instead the executable searches for a shared library by filename in a variety of places which you cannot control when building the executable. It searches the files in the same directory as the executable, then files in your PATH
environment variable, and some other insignificant places. And if the DLL isn’t there?…
Why do we want that on Windows?
Some may be wondering why one would want such a thing. I’m sure some are perfectly content with the DLL search order, but here are two very popular use-cases2:
Using third party libraries
This is probably the most common case. You want to use a third party library, but you don’t really want to build it yourself, so you download a handy binary release and extract it… well, somewhere. Or you do want to build it yourself, but you do it with its default settings and it spits out its binaries again somewhere.
Most Windows programmers are likely familiar with this scenario.
Using CMake (or anything other than .vcxproj, really)
CMake is the de facto standard for building C and C++ software these days. Most open source C++ projects use it. Of course there are many alternatives and most of them work the same way: the default output for a target is likely not the same for all targets3. So, when you build a project which outputs DLL files, by default it’s very likely that not all DLL files and executables will end up in the same directory.
I suspect that most Windows programmers who use a build system (or a build system generator) other than Visual Studio’s .sln
and .vcxproj
files are familiar with this scenario as well.
What do people do?
Given that this is a pretty popular issue in Windows development, there are already popular solutions to both use cases, each with its own downsides compared to rpath.
Change the output directory
This is probably the most popular solution for the case where you have a project which outputs executables and DLLs. You just manually change the output directory for all targets to be the same. In CMake you will often see people changing CMAKE_RUNTIME_OUTPUT_DIRECTORY
to something4. This sets the output directory for all executables and DLLs to be that something.
Many projects use this. I use this. As a whole it’s a relatively safe and often adequate solution5. There are some cases, however, where a common output directory is inapplicable:
- Having multiple targets which produce binaries with the same name. It’s impossible to have them in the same directory.
- Creating some configuration files with the same name. With CMake this can happen when you have files which depend on generator expressions which may be placed in
$<TARGET_FILE_DIR>
. In any case configuration files whose name doesn’t depend on the executable name will clash. - Same goes for output files with generic names, like
output.log
.
These are indeed very rare and some people might even say that they are bad practice and should be avoided. However, if something like this does happen, it can very well be missed which can lead to subtle and hard to detect bugs.
Copying DLLs
When dealing with third party libraries, you typically don’t have such fine control over where their DLL files end up, so copying them alongside your executables is often the choice. This is probably the most popular solution for third party libraries. It’s employed by many existing package managers. This requires a common output directory6 and carries with it the drawbacks of having one, but it’s also wasteful, having multiple copies of the same files over and over again.
To avoid this waste there’s another popular approach here and that is copying or installing third party DLLs to some directory in your PATH
. This fixes the problem of filename clashes, but introduces a new one: the so called DLL Hell. DLL Hell is when you have multiple DLLs with the same name in your PATH
. Executables will automatically load the first one which matches, but that may not be what you want. There might be differences in version or Debug/Release differences. Such problems are hard to detect and often lead to crazy behavior or, worse, subtle and hard to detect bugs. There is a way to deal with that on Windows (I mentioned redirection manifests above), but it requires you to have control over the DLLs as opposed to the executables. If a third-party vendor doesn’t support redirection manifests, it may be really hard to add them on your own.
What else can people do?
Having a common output directory and copying third party dependencies to it mostly works. It’s what almost everyone does. But I don’t like it. And neither should you!
Manually taking care of this is iffy and error prone. Knowing how easy it is to just specify a search path for a shared library on other platforms makes me cringe every time I have to deal with this Windows-specific problem.
I decided to do some research and try to find a solution. Here’s what I found:
(Useless) application configuration files
One of the first things I found in my research were application configuration files. At first glance it seems like the ideal solution: You add a manifest to the executable, you add a configuration file, and in it you specify search paths for DLLs. Voilà! Rpaths on Windows.
I was so impressed by this that I began envisioning a CMake library which would take care of this automatically. I thought of the interface, how it would work… and then it dawned on me to ask myself why there isn’t anyone already doing it. Sure, it seems like a somewhat obscure feature, but someone had to have thought of this.
My second glance explained why this isn’t a solution. See, in the linked docs they talked about assemblies. Windows documentation used to use the term “assembly” quite loosely before, but it has been mostly improved to mean exactly one thing: .NET binaries: .NET executables and .NET DLLs.
If you program managed binaries, this might be useful info to you (and most likely you already know this), but I work mainly with native binaries. I suspect most readers do, too. So, yeah… this really got my hopes up, but sadly it does nothing to native binaries.
Registry “App Paths”
There’s this thing called App Paths. In your registry under HKLM
or HKCU
you can find Software\Microsoft\Windows\CurrentVersion\App Paths\
which contains a list of applications that can be started from the Run (Win-R) window without specifying a full path. If you open this, you will probably find your browser, your video player, and similar default programs inside.
You can add your own, though. Just add a new key with the name of your executable (say “myexe.exe”), then in the default value supply the actual path to that executable. In doing so you will inform the operating system that an executable is globally available to run. Now you can run without the full path to the executable via the Run window, by calling ShellExecute
.
The important thing here is that Windows will also load another value from the registry key of your executable. It’s called “Path” and in it you can supply a string which will be appended to the PATH
environment variable when the executable is started. So in Path
you can add directories which contain the DLLs you need.
Like this
This will work.
…when you run it from the Run menu.
…or when you start it from another program, by calling ShellExecute
.
…even when you double-click it in Windows Explorer.
…but crucially and to my great annoyance NOT when you run it from the command line and NOT when started from Visual Studio with F5 or ctrl-F5.
Well, technically you can start it from the command line by typing > explorer myexe.exe
, or if you create you own “starter” program which forwards all arguments to ShellExecute
, but both will open a new window. This means the standard output of your executable will not be in the console window you started it from. Also, if you want to debug it from Visual Studio, you can manually attach to the process. It’s not impossible to work like this.
Not to mention that this is pretty hard to automate. You have to play a lot with the registry as part of the build process. Not so nice. Oh, and if you have multiple executables with the same name or you want to use this for DLLs, you’re also out of luck.
This info is useful for Windows application development, of course, but I’m sure those who do that are already familiar with this. As a solution to our rpath problem, this is not very practical.
Visual Studio specific: Custom .user file
So App Paths doesn’t help us when we run executables from the console or from the IDE. This reminded me that there’s a much better development alternative if you’re like me: you use Visual Studio and 99% of the time you start your executables from within it with F5 or ctrl-F5 instead of from the console or otherwise.
Visual Studio allows you to set a custom environment for a project when you start it from the IDE. It’s in project Settings>Debugging. The important thing about the Debugging settings is that they’re not stored in the .vcxproj file, but instead in an associated .vcxproj.user file. This file is not touched by the popular .vcxproj generators like CMake, so you can actually generate it yourself.
Here’s an example:
<?xml version="1.0" encoding="utf-8"?>
<!-- Template configured by CMake -->
<Project ToolsVersion="@USEFILE_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LocalDebuggerWorkingDirectory>@USERFILE_WORKING_DIR@</LocalDebuggerWorkingDirectory>
<LocalDebuggerEnvironment>PATH=@USERFILE_PATH@;%PATH%$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>
Then from CMake you can configure it with something like:
if(MSVC)
set(USEFILE_VERSION 15.0) # Visual Studio 2017, use MSVC_VERSION
# to properly set this variable
# (it's not in the scope of the article)
set(USERFILE_WORKING_DIR your\\working\\dir) # windows paths
set(USERFILE_PATH path\\to\\lib1;path\\to\\lib2)
configure_file(
"UserFile.template.user"
"${CMAKE_CURRENT_BINARY_DIR}/${YOUR_TARGET_NAME}.vcxproj.user"
@ONLY
)
endif()
I’ve used this on a couple of occasions. It can actually make your life a bit easier if you always start the program from Visual Studio in development.
But I digress…
Patching the executable
At this point I had to admit that the operating system wasn’t going to help me. Then I thought something along the lines of the following:
What does automatically loading a DLL actually mean? To load a DLL you have to call
LoadLibrary
in some way or another. So the linker must produce some code which callsLoadLibrary("mydll.dll");
and then updates the symbols I use from this DLL to the correct addresses.
This of course means that the executable contains the raw string "mydll.dll"
somewhere inside it. What if instead of this it contains "C:\mydll.dll"
?
This seemed like an easy-to-have revelation, so I searched for something on this subject and quickly found this blog post by Max Bolingbroke.
In short, here’s what he does:
- Build executable which links to some DLL.
- Open the executable with a binary editor, find the name of the DLL and change it to a full path.
- In case there is not enough room for the full path, you can either create
a_dll_with_a_really_long_name_which_will_be_replaced_by_a_full_path_to_a_dll_with_a_shorter_name.dll
- Or patch the linker .lib file with dlltool.exe:
dlltool --output-lib library.lib --dllname veryverylongdllname.dll library.o
only to replace the longer name with your full path with the binary editor afterwards. - Or if you’re using a .def file, you can specify the long DLL name in it.
- In case there is not enough room for the full path, you can either create
- That’s it. You hacked yourself an rpath for the DLL on Windows.
Do check out the blog post. It contains the whole process. It’s made with MinGW, but I was able to replicate his results with executables created by msvc as well. In fact, I created a small (and naïve) command line utility which can be used to patch an existing executable or DLL according to the strategy from above. It works for binaries produced by MinGW or msvc.
Yes. This really works. The problem is that automating it is indeed a bit of a pain. You have to patch executables after they’re built and this really confuses your build system. I suspect that using my tool or a similar one, an automation is possible (though likely build-system-specific), but still, it’s patching executables as a post build step. It’s definitely something which is frowned upon in C and C++ circles.
I therefore wouldn’t recommend this strategy over single-output-directory-and-third-party-dlls-copies, but if you’re pressed to have the closest possible thing to an rpath on Windows, this really seems like the only way. I for one will definitely keep it in mind in case I ever have to use it.
Visual C/C++ specific: delayed DLL loading
Microsoft’s linker LINK.exe supports delayed loading. This feature allows a linked DLL to be loaded when the first call to a function from it is made instead of at the startup of the executable. To make use of delayed loading you should add the /DELAYLOAD:dllname
switch to the linker command (for example /DELAYLOAD:foo.dll
).
At first I didn’t realize how this can help. After all, I don’t care when the DLL is loaded but where it’s loaded from. But after some comments to this I found out that I had neglected to see ways to make this actually quite useful.
First there’s the naïve (and quite dangerous) approach which you should probably not use: SetDllDirectory
. This function allows you to set the search path for DLLs which LoadLibrary
will use. This will probably work for the simplest of cases, but there are some problems which would make it from unusable to dangerous:
- If you call functions from a DLL globally, this will not work. You have no way to guarantee that
SetDllDirectory
will be called before your global calls7. SetDllDirectory
sets the search paths for the entire process. This means that these search paths will be used for all calls toLoadLibrary
. Depending on what kind of DLLs you have on your system, this may actually lead to stuff being loaded which you don’t want and bring you to a whole new level of DLL Hell.
There’s another approach, though. Using the delayed loading helper function.
In a typical delayed loading scenario (where you only care about when or if some DLLs are loaded) you would link with Delayimp.lib
which provides a function to do the actual delayed loading. You must define a hook function which the helper function will use. The hook function is quite easy to use and actually allows you to substitute the call to LoadLibrary
with your own. You can see how to do so in the docs. You would also see that setting the delayed loading hook depends on initializing a global variable, which brings us back to the problem of using functionality from the DLL globally. You will need this global to be initialized first, but you will have no way of guaranteeing it7.
The thing is that you could choose to not link with Delayimpl.lib
and instead provide a delayed loading function yourself. Now this is not trivial but it seems to be possible. I will probably play around with this in the following days and update this article.
Using delayed loading with a custom helper function seems to be the closest thing you can get, without patching the executable. Still, there are several obvious problems with it which make it unpleasant:
- It is LINK.exe specific, so Visual Studio specific. No MinGW here.
- You will have to include a significant platform-specific piece of code in every binary
- Your build system will have to supply the paths to libraries to the compiler. This is not really that trivial, because you will somehow have to transfer the knowledge of what DLLs you are using to the compiler. The easiest solution here would probably be for the build system to configure some .c file which will be added to the binary.
Nevertheless this does seem quite workable, and as I said I will play with it more.
What would be really nice?
As you can see, sadly none of the solutions above are ideal. There is simply no great alternative to rpath on Windows, even though thanks to delayed loading we could manage it somehow, and thanks to Max Bolingbroke we learned how to hack it in. But from learning how to hack it in, it seems as though it’s not that hard to actually have a solution. The linker could easily do it for us. In fact you saw that dlltool.exe gets us halfway there already.
So here’s my appeal to people who write linkers for Windows:
Can we have a way to specify paths for DLLs?
Micosoft, can we have this for LINK.exe?
/RENAMEDLL:"foo.dll","c:\foo.dll"
Mingw-w64, can we have this for ld.exe?
--renamedll "foo.dll" "c:/foo.dll"
Please?
On Linux, programs use LIBRARY_PATH and LD_LIBRARY_PATH to find libraries. Is there something similar on Windows, especially Windows 7?
Also, what are the best practices for using DLL files on Windows? For example, where should I put them, and should I use environment variables? I want to follow the standard way of working on Windows without using complicated solutions.
On Windows, the equivalent to
LIBRARY_PATH
andLD_LIBRARY_PATH
environment variables used on Linux for searching libraries is somewhat different. Windows uses a combination of well-defined search order and environment variables to locate Dynamic Link Libraries (DLLs). Here's a more detailed explanation:Searching for DLLs in Windows
When a program loads a DLL, Windows uses the following search order:
- The directory from which the application is loaded.
- The system directory (
system32
on 32-bit systems orSysWOW64
on 64-bit systems).- The 16-bit system directory.
- The Windows directory.
- The current working directory.
- Directories listed in the
PATH
environment variable.This means that on Windows, you can influence the search path by modifying the
PATH
environment variable. There isn't a direct equivalent environment variable specifically for libraries likeLIBRARY_PATH
orLD_LIBRARY_PATH
.Setting the
PATH
Environment Variable
- System Properties:
- Right-click on "Computer" or "This PC" on the Desktop or in Explorer.
- Click "Properties."
- Click "Advanced system settings."
- In the System Properties dialog, click the "Environment Variables" button.
- Editing the PATH:
- In the "Environment Variables" dialog, scroll down in the "System variables" section, select the
PATH
variable, and click "Edit."- Add your directory paths to the variable, separated by semicolons (
;
).Best Practices for DLLs on Windows
Application Directory: Place DLLs in the same directory as the executable. This is simple and avoids issues with DLL search order and version conflicts.
System Directory: Place shared system-wide DLLs in
C:\Windows\System32
for 64-bit DLLs orC:\Windows\SysWOW64
for 32-bit DLLs on a 64-bit system. However, this should be done sparingly to avoid conflicts and maintain system stability.Custom Directory with PATH: You can create a custom directory (e.g.,
C:\MyApp\Libraries
) and add this directory to thePATH
environment variable. This allows you to keep your application directory clean and still share DLLs across applications.Application-Specific Configuration: Use application-specific mechanisms for defining the library load path. Many applications and development environments offer ways to set the DLL search paths programmatically.
Proper Versioning: Use proper versioning of your DLLs to avoid conflicts. Use side-by-side (SxS) assemblies if you need to handle multiple versions of the same DLL.
Avoiding Conflicts: Be cautious of "DLL Hell," a situation where multiple applications with different version dependencies lead to conflicts. Use unique names or versioned directories to avoid overlapping DLL names.
Conclusion
While Windows does not use an environment variable strictly equivalent to
LIBRARY_PATH
orLD_LIBRARY_PATH
for locating libraries, it leverages thePATH
environment variable and a specific search order. By using the guidelines explained above, you should be able to manage DLLs effectively for your applications on Windows.
- Заметки
- Системное администрирование
- Windows
- Порядок поиска Dll-ек
На заметочку, в каком порядке Windows ищет нужные dll-файлы:
- Каталог, в котором находится исполняемый модуль текущего процесса.
- Текущий каталог
- Системный каталог Windows. Функция GetSystemDirectory извлекает путь к этому каталогу.
- Каталог Windows. Функция GetWindowsDirectory извлекает путь к этому каталогу.
- Каталоги, перечисленные в переменной окружения PATH
#Processing
#Windows
Рейтинг: 0/5 — 0
голосов