Динамически подключаемые библиотеки dll описание способы загрузки
Передо мной возникла задача написать загрузчик библиотек, имеющий возможность предоставить какие-то интерфейсные функции внешней динамической библиотеке. Решение должно быть максимально кроссплатформенно (как минимум, работать на Linux и Windows). Загружаться должны библиотеки, написанные на различных языках программирования, поддерживающих создание динамических библиотек. В качестве примера были выбраны языки C и Pascal.
Решение
Основной загрузчик библиотек написан на языке C. Для того, чтобы загружаемые библиотеки имели возможность использовать функции основной программы, основная программа разделена на 2 части: на основной и подгружаемый модули. Основной модуль нужен просто для запуска программы, подгружаемый модуль — это также динамическая библиотека, связываемая с основным модулем во время его запуска. В качестве компиляторов были выбраны gcc (MinGW для Windows) и fpc.
Здесь будет приведён упрощённый пример программы, позволяющий разобраться в данном вопросе и учить первокурсников писать модули к своей программе (в школе часто преподают именно Pascal).
Загрузчик библиотек
main.c
А это модуль, отвечающий за загрузку динамических библиотек, который сам вынесен в динамическую библиотеку для того, чтобы подгружаемые библиотеки имели возможность использовать предоставляемые им функции:
loader.c
Заголовочные файлы
Это всё была реализация, а теперь заголовочные файлы.
Вот интерфейс модуля, загружающего динамические библиотеки, для основного модуля:
loader.h
Вот интерфейс загрузчика для загружаемых им динамических библиотек (перечень функций, которые динамические библиотеки могут использовать в основной программе):
functions.h
Как видно, здесь всего одна функция printString для примера.
Компиляция загрузчика
Пример недистрибутивной компиляции (в случае Windows в опции компилятора просто нужно добавить -DWIN32):
Дистрибутивная компиляция от недистрибутивной отличается тем, что в дистрибутивном случае динамические библиотеки ищутся в /usr/lib и имеют вид lib$(NAME).so.$(VERSION), в случае недистрибутивной компиляции они называются lib$(NAME).so, а ищутся в каталоге запуска программы.
Теперь посмотрим, что у нас получилось после компиляции:
Тут видим, что функции, отмечаемые как U ищутся во внешних динамических библиотеках, а функции, отмечаемые как T предоставляются модулем. Это бинарный интерфейс программы (ABI).
Динамические библиотеки
Библиотека на языке C
Здесь везде окружение extern «C» <> нужно для того, чтобы нашу программу можно было компилировать при помощи C++-компилятора, такого, как g++. Просто в C++ можно объявлять функции с одним и тем же именем, но с разной сигнатурой, соответственно в этом случае используется так называемая декорация имён функций, то есть сигнатура функций записывается в ABI. Окружение extern «C» <> нужно для того, чтобы не использовалась эта декорация (тем более, что эта декорация зависит от используемого компилятора).
Компиляция
Если мы уберём в нашем модуле окружение extern «C» <> и скомпилируем при помощи g++ вместо gcc, то увидим следующее:
То есть, как и ожидалось, ABI библиотеки изменился, теперь наш загрузчик не сможет увидеть функцию run в этой библиотеке:
Библиотека на языке Pascal
Как мы увидели выше, для того, чтобы наш загрузчик видел функции в динамических библиотеках, созданных компилятором C++, пришлось дополнять наш код вставками extern «C» <>, это притом, что C/C++-компиляторы и языки родственные. Что уж говорить про компилятор FreePascal совершенно другого языка — Pascal? Естественно, что и тут без дополнительных телодвижений не обойтись.
Для начала нам нужно научиться использовать экспортированные в C функции для динамических библиотек. Вот пример аналогичного C/C++ заголовочного файла на языке Pascal:
func.pas
Вот пример самого модуля на языке Pascal:
modul.pas
Компиляция
Смотрим ABI получившейся библиотеки:
Как видим, ничего лишнего, однако настораживает предупреждение ld во время компиляции. Находим в гугле возможную причину предупреждения, это связано с компиляцией без PIC (Position Independent Code — код не привязан к физическому адресу), однако в man fpc находим, что наша опция -Cg должна генерировать PIC-код, что само по себе странно, видимо fpc не выполняет своих обещаний, либо я делаю что-то не так.
Теперь попробуем убрать в нашем заголовочном файле кусок name 'printString' и посмотрим, что выдаст компилятор теперь:
Как видно, декорация в FreePascal совсем другого рода, чем в g++ и тоже присутствует.
При запуске с этим модулем получаем:
А с правильным модулем получаем:
Вот и всё, своей задачи — использование динамических библиотек, написанных на различных языках — мы достигли, и наш код работает как под Linux, так и под Windows (у самого Mac'а нет, поэтому не смотрел, как там с этим). Кроме того, загруженные библиотеки имеют возможность использовать предоставляемые в основной программе функции для взаимодействия с ней (то есть являются плагинами основной программы).
Сам загрузчик может выполняться в отдельном процессе, чтобы эти плагины не смогли сделать ничего лишнего с основной программой. Соответственно, основная программа может быть написана на любом другом удобном языке программирования (например, на Java или на том же C++).
В этой статье описывается, что такое библиотека динамических ссылок (DLL) и различные проблемы, которые могут возникнуть при использовании библиотек DLL. Здесь также описаны некоторые сложные проблемы, которые следует учитывать при разработке собственных библиотек DLL.
Применяется к: Windows 10 — все выпуски
Исходный номер базы знаний: 815065
Точка входа DLL
Каждая динамическая библиотека имеет точку входа. Точка входа это функция с определённым именем, которая вызывается операционной системой в нескольких специальных случаях. По умолчанию имя этой функции DllMain.
Система при вызове функции передаёт следующие параметры:
- hinstDLL - ссылка на DLL. В действительности для 32-х разрядных библиотек этот параметр содержит базовый (начальный) адрес по которому загружена библиотека в виртуальном адресном пространстве процесса.
- fdwReason - содержит код события которое было причиной вызова функции.
- LpvReserved - зарезервирован и не используется.
Используя эту функцию DLL может перехватить четыре различных события коды которых указываются в параметре fdwReason.
DLL_PROCESS_ATTACH новый процесс загружает динамическую библиотеку автоматически или функцией LoadLibrary.
DLL_PROCESS_DETACH процесс который использовал динамическую библиотеку завершается или выгружает её функцией FreeLibrary.
DLL_THREAD_ATTACH в одном из процессов использующих DLL создан новый поток.
DLL_THREAD_DETACH в одном из процессов использующих DLL завершился поток.
Аргументы функций и возвращаемые значения
По умолчанию предполагается, что любая экспортируемая функция из динамической библиотеки возвращает тип int . Другие возвращаемые типы можно указать при помощи атрибута restype . При этом, чтобы указать типы аргументов функции можно воспользоваться атрибутом argtypes .
Например, стандартная функция strcat принимает два указателя на строки и возвращает один указатель на новую строку. Давайте попробуем ей воспользоваться.
Код Python:
Результат:
На этом закончим с примерами использования готовых DLL. Давайте попробуем применить знания о структуре DLL и модуле ctypes для того, чтобы собрать и начать использовать собственную библиотеку.
Типы динамической компоновки
Существует два метода вызова функции в библиотеке DLL:
- При динамической компоновке во время загрузки модуль выполняет явные вызовы экспортированных функций DLL, как если бы они были локальными функциями. Для этого необходимо связать модуль с библиотекой импорта для библиотеки DLL, содержащей функции. Библиотека импорта предоставляет системе сведения, необходимые для загрузки библиотеки DLL, и нахождение экспортированных функций DLL при загрузке приложения.
- При динамической компоновке во время выполнения модуль использует функцию LoadLibrary или LoadLibraryEx для загрузки библиотеки DLL во время выполнения. После загрузки библиотеки DLL модуль вызывает функцию GetProcAddress для получения адресов экспортированных функций DLL. Модуль вызывает экспортированные функции DLL с помощью указателей функций, возвращаемых GetProcAddress. Это устраняет необходимость в библиотеке импорта.
Универсальный решатель проблем DLL
Средство DLL Universal Problem Solver (DUPS) используется для аудита, сравнения, документирование и отображения сведений о библиотеке DLL. В следующем списке описаны служебные программы, из которых создается средство DUPS.
Эта служебная программа перечисляет все библиотеки DLL на компьютере и регистрирует сведения в текстовом файле или файле базы данных.
Эта программа сравнивает библиотеки DLL, перечисленные в двух текстовых файлах, и создает третий текстовый файл, содержащий различия.
Эта служебная программа загружает текстовые файлы, созданные с помощью Dlister.exe и Dcomp.exe в базу данных DLLHell.
Эта служебная программа предоставляет версию графического пользовательского интерфейса (GUI) Dtxt2DB.exe.
Создание DLL в CodeBlocks
Для создания DLL-проекта в среде разработки CodeBlocks при создании нового проекта необходимо выбрать пункт «Dynamic Link Library». Созданный проект будет содержать два файла main.cpp и main.h.
Для экспортирования функции из DLL - перед ее описанием следует указать ключевое слово __declspec(dllexport).
Зависимости DLL
Если программа или библиотека DLL использует функцию DLL в другой библиотеке DLL, создается зависимость. Программа больше не является автономной, и если зависимость нарушена, у нее могут возникнуть проблемы. Например, программа может не выполняться, если выполняется одно из следующих действий:
- Зависимая библиотека DLL обновляется до новой версии.
- Зависимые библиотеки DLL исправлены.
- Зависимая библиотека DLL перезаписывается более ранней версией.
- Зависимая библиотека DLL удаляется с компьютера.
Эти действия называются конфликтами DLL. Если обратная совместимость не применяется, возможно, программа не будет успешно запущена.
В следующем списке описываются изменения, которые были внесены в Windows 2000 и более поздних Windows операционных системах, чтобы свести к минимуму проблемы с зависимостями.
Windows защиты файлов
В Windows защиты файлов операционная система предотвращает обновление или удаление библиотек DLL системы неавторизованными агентами. При попытке установки программы удалить или обновить библиотеку DLL, определенную как системную библиотеку DLL, Windows служба защиты файлов будет искать допустимую цифровую подпись.
Частные библиотеки DLL
Частные библиотеки DLL позволяют изолировать программу от изменений, внесенных в общие библиотеки DLL. Частные библиотеки DLL используют сведения .local о версии или пустой файл для принудительного применения версии библиотеки DLL, используемой программой. Чтобы использовать частные библиотеки DLL, найдите библиотеки DLL в корневой папке программы. Затем для новых программ добавьте сведения о версии в библиотеку DLL. Для старых программ используйте пустой .local файл. Каждый метод указывает операционной системе использовать частные библиотеки DLL, расположенные в корневой папке программы.
Аннотация
В этой статье описаны методы динамической компоновки, зависимости DLL, точки входа DLL, экспорт функций DLL и средства устранения неполадок DLL.
Для Windows операционных систем большая часть функциональных возможностей операционной системы предоставляется библиотекой DLL. Кроме того, при запуске программы в одной из Windows операционных систем большая часть функций программы может быть предоставлена библиотеками DLL. Например, некоторые программы могут содержать множество различных модулей, а каждый модуль программы содержится и распространяется в библиотеках DLL.
Использование библиотек DLL помогает повысить модульность кода, повторное использование кода, эффективное использование памяти и сокращение места на диске. Таким образом, операционная система и программы загружаются быстрее, выполняются быстрее и занимать меньше места на диске на компьютере.
Если программа использует библиотеку DLL, проблема, которая называется зависимостью, может привести к тому, что программа не будет выполняться. Когда программа использует библиотеку DLL, создается зависимость. Если другая программа перезаписывает и прерывает эту зависимость, исходная программа может не запуститься успешно.
Вызов функций из DLL
Существует два способа загрузки DLL: с явной и неявной компоновкой.
При неявной компоновке функции загружаемой DLL добавляются в секцию импорта вызывающего файла. При запуске такого файла загрузчик операционной системы анализирует секцию импорта и подключает все указанные библиотеки. Ввиду своей простоты этот способ пользуется большой популярностью; но неявной компоновке присущи определенные недостатки и ограничения:
- Все подключенные DLL загружаются всегда, даже если в течение всего сеанса работы программа ни разу не обратится ни к одной из них;
- Если хотя бы одна из требуемых DLL отсутствует (или DLL не экспортирует хотя бы одной требуемой функции) - загрузка исполняемого файла прерывается, даже если отсутствие этой DLL не критично для исполнения программы.
- Поиск DLL происходит в следующем порядке: в каталоге, содержащем вызывающий файл; в текущем каталоге процесса; в системном каталоге %Windows%System%; в основном каталоге %Windows%; в каталогах, указанных в переменной PATH. Задать другой путь поиска невозможно (вернее - возможно, но для этого потребуется вносить изменения в системный реестр, и эти изменения окажут влияние на все процессы, исполняющиеся в системе - что не есть хорошо).
Явная компоновка устраняет все эти недостатки - ценой некоторого усложнения кода. Программисту самому придется позаботиться о загрузке DLL и подключении экспортируемых функций (не забывая при этом о контроле над ошибками, иначе в один прекрасный момент дело кончится зависанием системы). Зато явная компоновка позволяет подгружать DLL по мере необходимости и дает программисту возможность самостоятельно обрабатывать ситуации с отсутствием DLL. Можно пойти и дальше - не задавать имя DLL в программе явно, а сканировать такой-то каталог на предмет наличия динамических библиотек и подключать все найденные к приложению. Именно так работает механизм поддержки plug-in’ов в популярном файл-менеджере FAR (да и не только в нем).
Таким образом, неявной компоновкой целесообразно пользоваться лишь для подключения загружаемых в каждом сеансе, жизненно необходимых для работы приложения динамических библиотек; во всех остальных случаях - предпочтительнее явная компоновка.
Загрузка DLL с неявной компоновкой
Чтобы вызвать функцию из DLL, ее необходимо объявить в вызывающем коде - либо как external (т.е. как обычную внешнюю функцию), либо предварить ключевым словом __declspec(dllimport). Первый способ более популярен, но второй все же предпочтительнее - в этом случае компилятор, поняв, что функция вызывается именно из DLL, сможет соответствующим образом оптимизировать код. Например:
Пример библиотеки DLL и приложения
В Visual C++ 6.0 можно создать библиотеку DLL, выбрав тип проекта библиотеки win32 Dynamic-Link или тип проекта MFC AppWizard (dll ).
Ниже приведен пример библиотеки DLL, созданной в Visual C++ с помощью типа проекта Dynamic-Link Win32 .
Ниже приведен пример проекта приложения Win32 , который вызывает экспортируемую функцию DLL в библиотеке DLL SampleDLL.
При динамической компоновке во время загрузки необходимо связать библиотеку импорта SampleDLL.lib, которая создается при сборке проекта SampleDLL.
При динамической компоновки во время выполнения для вызова экспортированной функции DLL используется код SampleDLL.dll, аналогичный следующему коду.
При компиляции и связывании приложения SampleDLL операционная система Windows ищет библиотеку DLL SampleDLL в следующих расположениях в следующем порядке:
Системная Windows папка
Функция GetSystemDirectory возвращает путь к Windows системной папке.
Функция GetWindowsDirectory возвращает путь к Windows папке.
Файл сборки содержит манифест сборки, метаданные типа, код MSIL и другие ресурсы. Манифест сборки содержит метаданные сборки, которые содержат все сведения, необходимые для самостоятельного описания сборки. В манифест сборки включены следующие сведения:
- Имя сборки
- Сведения о версии
- Сведения о языке и региональных параметрах
- Сведения о строгом имени
- Список сборок файлов
- Справочные сведения о типе
- Сведения о ссылочных и зависимых сборках
Код MSIL, содержащийся в сборке, не может выполняться напрямую. Вместо этого выполнением кода MSIL управляет среда CLR. По умолчанию при создании сборки сборка является закрытой для приложения. Чтобы создать общую сборку, необходимо назначить сборке строгое имя, а затем опубликовать сборку в глобальном кэше сборок.
В следующем списке описаны некоторые функции сборок по сравнению с функциями библиотек DLL Win32.
При создании сборки все сведения, необходимые среде CLR для запуска сборки, содержатся в манифесте сборки. Манифест сборки содержит список зависимых сборок. Таким образом, среда CLR может поддерживать согласованный набор сборок, используемых в приложении. В библиотеках DLL Win32 нельзя поддерживать согласованность между набором библиотек DLL, используемых в приложении при использовании общих библиотек DLL.
В манифесте сборки сведения о версии записываются и применяются средой CLR. Кроме того, политики версий позволяют принудительно применять использование конкретной версии. В библиотеках DLL Win32 управление версиями не может применяться операционной системой. Необходимо убедиться, что библиотеки DLL поддерживают обратную совместимость.
Сборки поддерживают параллельное развертывание. Одно приложение может использовать одну версию сборки, а другое — другую. Начиная с Windows 2000, параллельное развертывание поддерживается путем поиска библиотек DLL в папке приложения. Кроме того, Windows защиты файлов предотвращает перезапись или замену системных библиотек DLL неавторизованными агентами.
Автономная и изоляция
Приложение, разработанное с помощью сборки, может быть автономным и изолированным от других приложений, работающих на компьютере. Эта функция помогает создавать установки без влияния.
Сборка выполняется с разрешениями безопасности, предоставленными в манифесте сборки и управляемыми средой CLR.
Независимый от языка
Динамическая компоновка позволяет модулю включать только сведения, необходимые для локализации экспортируемой функции DLL во время загрузки или во время выполнения. Динамическая компоновка отличается от более привычной статической компоновки, при которой компоновщик копирует код библиотечной функции в каждый модуль, который ее вызывает.
Содержание
Выгрузка динамических библиотек из памяти
Когда загруженная динамическая библиотека больше не нужна - ее можно освободить, вызвав функцию
и передав ей дескриптор библиотеки, ранее возвращенный функцией LoadLibrary. Обратите внимание - DLL можно именно освободить, но не выгрузить! Выгрузка DLL из памяти не гарантируется, даже если работу с ней завершили все ранее загрузившие ее процессы.
Задержка выгрузки предусмотрена специально - на тот случай, если эта же DLL через некоторое время вновь понадобится какому-то процессу. Такой трюк оптимизирует работу часто используемых динамических библиотек, но плохо подходит для редко используемых DLL, загружаемых лишь однажды на короткое время. Никаких документированных способов насильно выгрузить динамическую библиотеку из памяти нет; а те, что есть - работают с ядром на низком уровне и не могут похвастаться переносимостью. Поэтому здесь мы их рассматривать не будем. К тому же - тактика освобождения и выгрузки DLL по-разному реализована в каждой версии Windows: Microsoft, стремясь подобрать наилучшую стратегию, непрерывно изменяет этот алгоритм; а потому и отказывается его документировать. Нельзя не обратить внимания на одно очень важное обстоятельство: динамическая библиотека не владеет никакими ресурсами - ими владеет, независимо от способа компоновки, загрузивший ее процесс. Динамическая библиотека может открывать файлы, выделять память и т. д., но память не будет автоматически освобождена после вызова FreeLibrary, а файлы не окажутся сами собой закрыты - все это произойдет лишь после завершения процесса, но не раньше! Естественно, если программист сам не освободит все ненужные ресурсы вручную, с помощью функций CloseHandle, FreeMemory и подобных им.
Если функция FreeLibrary пропущена, DLL освобождается (но не факт, что выгружается!) только после завершения вызвавшего процесса. Могут возникнуть сомнения: раз FreeLibrary немедленно не выгружает динамическую библиотеку из памяти, так зачем она вообще нужна? Не лучше ли тогда все пустить на самотек - все равно ведь загруженные DLL будут гарантированно освобождены после завершения процесса? Что ж, доля правды тут есть, и автор сам порой так и поступает; но при недостатке памяти операционная система может беспрепятственно использовать место, занятое освобожденными динамическими библиотеками под что-то полезное - а если DLL еще не освобождены, их придется "скидывать" в файл подкачки, теряя драгоценное время. Поэтому лучше освобождайте DLL сразу же после их использования!
Недавно меня заинтересовала тема использования DLL из Python. Кроме того было интересно разобраться в их структуре, на тот случай, если придется менять исходники библиотек. После изучения различных ресурсов и примеров на эту тему, стало понятно, что применение динамических библиотек может сильно расширить возможности Python. Собственные цели были достигнуты, а чтобы опыт не был забыт, я решил подвести итог в виде статьи — структурировать свой знания и полезные источники, а заодно ещё лучше разобраться в данной теме.
Под катом вас ожидает статья с различными примерами, исходниками и пояснениями к ним.
Содержание
- Структура DLL
- DLL & Python
- Подключение DLL
- Типы данных в С и Python
- Аргументы функция и возвращаемые значения
- Своя DLL и ее использование
Надеюсь из содержания немного станет понятнее какую часть нужно открыть, чтобы найти ответы на свои вопросы.
Обходчик зависимостей
Средство "Обходчик зависимостей" может рекурсивно проверять все зависимые библиотеки DLL, используемые программой. При открытии программы в Dependency Вебере он выполняет следующие проверки:
- Обходчик зависимостей проверяет наличие отсутствующих библиотек DLL.
- Монитор зависимостей проверяет наличие недопустимых программных файлов или библиотек DLL.
- Монитор зависимостей проверяет, совпадают ли функции импорта и экспорта.
- Обходчик зависимостей проверяет наличие циклических ошибок зависимостей.
- Монитор зависимостей проверяет наличие недопустимых модулей, так как модули предназначены для другой операционной системы.
С помощью Dependency Walker можно документировать все библиотеки DLL, используемые программой. Это может помочь предотвратить и устранить проблемы DLL, которые могут возникнуть в будущем. При установке Visual Studio версии 6.0 dependency Викер находится в следующем каталоге:
drive\Program Files\Microsoft Visual Studio\Common\Tools
Типы библиотек DLL
При загрузке библиотеки DLL в приложение два метода связывания позволяют вызывать экспортированные функции DLL. Два метода связывания — динамическое связывание во время загрузки и динамическое связывание во время выполнения.
Динамическое связывание во время загрузки
При динамической компоновки во время загрузки приложение выполняет явные вызовы экспортированных функций DLL, таких как локальные функции. Чтобы использовать динамическую компоновку во время загрузки, предоставьте файл заголовка (H) и файл библиотеки импорта (LIB) при компиляции и связывание приложения. При этом компоновщик предоставит системе сведения, необходимые для загрузки библиотеки DLL и разрешения экспортированных расположений функций DLL во время загрузки.
Динамическое связывание во время выполнения
При динамической компоновки LoadLibrary LoadLibraryEx во время выполнения приложение вызывает функцию или функцию для загрузки библиотеки DLL во время выполнения. После успешной загрузки GetProcAddress библиотеки DLL используйте функцию для получения адреса экспортированной функции DLL, которую требуется вызвать. При использовании динамической компоновки во время выполнения файл библиотеки импорта не требуется.
В следующем списке описаны критерии приложения для использования динамической компоновки во время загрузки и использования динамической компоновки во время выполнения:
Если важна начальная производительность запуска приложения, следует использовать динамическое связывание во время выполнения.
При динамической компоновки во время загрузки экспортированные функции DLL являются локальными функциями. Это упрощает вызов этих функций.
При динамической компоновки во время выполнения приложение может выполнять ветвь для загрузки различных модулей по мере необходимости. Это важно при разработке версий на нескольких языках.
Загрузка DLL с явной компоновкой
Явную загрузку динамических библиотек осуществляет функция
или ее расширенный аналог
Обе они экспортируются из KERNEL32.DLL, следовательно, каждое приложение требует неявной компоновки по крайней мере этой библиотеки. В случае успешной загрузки DLL возвращается линейный адрес библиотеки в памяти. Передав его функции
мы получим указатель на функцию lpProcName, экспортируемую данной DLL. При возникновении ошибки обе функции возвращают NULL. После завершения работы с динамической библиотекой ее следует освободить вызовом функции
Для пояснения приведем код примера с подробными комментариями:
Справочная база данных библиотеки DLL
База данных справки DLL помогает найти определенные версии библиотек DLL, установленных программным продуктом Майкрософт.
Средства устранения неполадок библиотеки DLL
Для устранения неполадок с библиотекой DLL доступно несколько средств. Ниже приведены некоторые из этих средств.
Разработка DLL
В этом разделе описываются проблемы и требования, которые следует учитывать при разработке собственных библиотек DLL.
Преимущества DLL
В следующем списке описаны некоторые преимущества, которые предоставляются, когда программа использует библиотеку DLL:
Использует меньше ресурсов
Если несколько программ используют ту же библиотеку функций, библиотека DLL может уменьшить дублирование кода, загруженного на диск и в физической памяти. Это может значительно повлиять на производительность не только программы, выполняемой на переднем плане, но и других программ, работающих в Windows операционной системе.
Повышение уровня модульной архитектуры
Библиотека DLL помогает повысить уровень разработки модульных программ. Она помогает разрабатывать крупные программы, для которых требуется несколько языковых версий, или программы, для которых требуется модульная архитектура. Примером модульной программы является программа учета с множеством модулей, которые можно динамически загрузить во время выполнения.
Упрощает развертывание и установку
Если функции в библиотеке DLL требуется обновление или исправление, для развертывания и установки библиотеки DLL не требуется повторное связывание программы с библиотекой DLL. Кроме того, если несколько программ используют ту же библиотеку DLL, все эти программы будут пользоваться преимуществами обновления или исправления. Эта проблема может возникать чаще при использовании библиотеки DLL сторонних разработчиков, которая регулярно обновляется или исправлена.
Дополнительная информация
Библиотека DLL — это библиотека, содержащая код и данные, которые могут использоваться несколькими программами одновременно. Например, в Windows операционных системах библиотека DLL Comdlg32 выполняет общие функции, связанные с диалогом. Каждая программа может использовать функции, содержащиеся в этой библиотеке DLL, для реализации диалогового окна " Открыть". Это помогает повысить эффективность повторного использования кода и эффективного использования памяти.
С помощью библиотеки DLL программу можно разделить на отдельные компоненты. Например, программа учета может быть продана модулем. Каждый модуль можно загрузить в основную программу во время выполнения, если он установлен. Так как модули являются отдельными, время загрузки программы ускоряется. Модуль загружается только при запросе этой функции.
Кроме того, обновления проще применять к каждому модуле, не затрагивая другие части программы. Например, у вас может быть программа заработной платы, и налоговые ставки меняются каждый год. Если эти изменения изолированы в библиотеке DLL, можно применить обновление, не требуя сборки или установки всей программы еще раз.
В следующем списке описаны некоторые файлы, реализованные в качестве библиотек DLL в Windows операционных системах.
ActiveX элементов управления (OCX)
Примером элемента ActiveX является элемент управления календаря, который позволяет выбрать дату из календаря.
панель управления (.cpl)
Примером файла .cpl является элемент, расположенный в панель управления. Каждый элемент является специализированной библиотекой DLL.
Файлы драйвера устройства (DRV)
Примером драйвера устройства является драйвер принтера, который управляет печатью на принтере.
Своя DLL и ее использование
Пример 1
Шаблон DLL уже был рассмотрен выше, а сейчас, когда дело дошло до написания своей DLL и работы с ней, выскочили первые и очевидные грабли — несовместимость разрядности DLL и Python. У меня на ПК установлен Python x64, оказалось, что как бы не были DLL универсальны, разрядность DLL должна соответствовать разрядности Python. То есть, либо ставить компилятор x64 и Python x64, либо и то и то x32. Хорошо, что это не сложно сделать.
Ниже привожу код шаблона DLL, в который добавил вывод строки при подключении библиотеки, а также небольшой разбор и вывод аргументов, с которыми вызвалась DllMain . В примере можно понаблюдать, какие участки кода библиотеки вызываются и когда это происходит.
Код DLL на С:
Код Python:
Функция SomeFunction получает указатель на строку и выводит её в окно. На рисунке ниже показана работа программы.
Рисунок 2 — Демонстрация работы шаблона библиотеки из Code Blocks.
Все действия происходящие в кейсе DLL_PROCESS_ATTACH , код которого приведен ниже, вызываются лишь одной строкой в Python коде:
Рисунок 3 — Действия происходящие при подключении DLL.
Пример 2
Чтобы подвести итог по использованию DLL библиотек из Python, приведу пример, в котором есть начальная инициализация параметров и передача новых через указатели на строки и структуры данных. Этот код дает понять, как написать аналог структуры С в Python. Ниже привожу код main.c , man.h и main.py .
Код DLL на С:
В коде main.h определена структура Passport с тремя полями: два указателя и целочисленная переменная. Кроме того, четыре функции объявлены, как экспортируемые.
Код DLL на С:
Внутри кейса DLL_PROCESS_ATTACH происходит выделение памяти под строки и начальная инициализация полей структуры. Выше DllMain определены функции:
GetPassport — вывод полей структуры passport в консоль.
*SetName(char new_name)** — установка поля name структуры passport .
*SetSurname(char new_surname)** — установка поля surname структуры passport .
*SetPassport(Passport new_passport)** — установка всех полей структуры passport . Принимает в качестве аргумента указатель на структуру с новыми полями.
Теперь можно подключить библиотеку в Python.
Код на Python
В коде выше многое уже знакомо, кроме создания структуры аналогичной той, которая объявлена в DLL и передачи указателя на эту структуру из Python в DLL.
Результат:
Экспорт функций DLL
Чтобы экспортировать функции DLL, можно добавить ключевое слово функции в экспортированные функции DLL или создать файл определения модуля (DEF), в котором перечислены экспортированные функции DLL.
Чтобы использовать ключевое слово функции, необходимо объявить каждую функцию, которую требуется экспортировать, с помощью следующего ключевого слова:
__declspec(dllexport)Чтобы использовать экспортированные функции DLL в приложении, необходимо объявить каждую функцию, которую требуется импортировать, с помощью следующего ключевого слова: __declspec(dllimport)
Как правило, для разделения инструкции экспорта и инструкции используется один файл заголовка с оператором define ifdef и оператором import .
Вы также можете использовать файл определения модуля для объявления экспортированных функций DLL. При использовании файла определения модуля не нужно добавлять ключевое слово функции в экспортированные функции DLL. В файле определения модуля объявляется инструкция LIBRARY и EXPORTS инструкция для библиотеки DLL. Следующий код является примером файла определения.
Структура DLL
DLL — Dynamic Link Library — динамическая подключаемая библиотека в операционной системе (ОС) Windows. Динамические библиотеки позволяют сделать архитектуру более модульной, уменьшить количество используемых ресурсов и упрощают модификацию системы. Основное отличие от .EXE файлов — функции, содержащиеся в DLL можно использовать по одной.
Учитывая, что статья не о самих библиотеках, лучше просто оставить здесь ссылку на довольно информативную статью от Microsoft: Что такое DLL?.
Для того, чтобы понять, как использовать динамические библиотеки, нужно вникнуть в их структуру.
DLL содержит набор различных функций, которые потом можно использовать по-отдельности. Но также есть возможность дополнительно указать функцию точки входа в библиотеку. Такая функция обычно имеет имя DllMain и вызывается, когда процессы или потоки прикрепляются к DLL или отделяются от неё. Это можно использовать для инициализации различных структур данных или их уничтожения.
Рисунок 1 — Пустой template, предлагаемый Code Blocks для проекта DLL.
На рисунке 1 приведен шаблон, который предлагает Code Blocks, при выборе проекта типа DLL. В представленном шаблоне есть две функции:
Для начала стоит подробнее рассмотреть функцию DllMain . Через нее ОС может уведомлять библиотеку о нескольких событиях (fdwReason):
DLL_PROCESS_ATTACH – подключение DLL. Процесс проецирования DLL на адресное пространство процесса. С этим значением DllMain вызывается всякий раз, когда какой-то процесс загружает библиотеку с явной или неявной компоновкой.
DLL_PROCESS_DETACH – отключение DLL от адресного пространства процесса. С этим значением DllMain вызывается при отключении библиотеки.
DLL_THREAD_ATTACH – создание процессом, подключившим DLL, нового потока. Зачем DLL знать о каких-то там потоках? А вот зачем, далеко не каждая динамическая библиотека может работать в многопоточной среде.
DLL_THREAD_DETACH – завершение потока, созданного процессом, подключившим DLL. Если динамическая библиотека создает для каждого потока свои "персональные" ресурсы (локальные переменные и буфера), то это уведомление позволяет их своевременно освобождать.
Опять же, в тему структуры DLL можно углубляться до бесконечности, там есть много различных нюансов, о которых немного изложено в этой статье.
У DllMain не так много аргументов, самый важный fdwReason уже рассмотрен выше, теперь о двух других:
- Аргумент lpvReserved указывает на способ подключения DLL:
- 0 — библиотека загружена с явной компоновкой.
- 1 — библиотека загружена с неявной компоновкой.
О явной и неявной компоновке можно прочесть подробно в статье: Связывание исполняемого файла с библиотекой DLL.
В предложенном на рисунке 1 шаблоне есть функция SomeFunction , которая может быть экспортирована из динамической библиотеки. Для того, чтобы это показать, при объявлении функции указывается __declspec(dllexport) . Например, так:
Функции, не объявленные таким образом, нельзя будет вызывать снаружи.
Библиотеки DLL и управление памятью
Каждый процесс, загружающий библиотеку DLL, сопоставляет его с виртуальным адресным пространством. После того как процесс загрузит библиотеку DLL в свой виртуальный адрес, он может вызвать экспортированные функции DLL.
Система поддерживает счетчик ссылок по процессам для каждой библиотеки DLL. Когда поток загружает библиотеку DLL, счетчик ссылок увеличивается на единицу. Когда процесс завершается или значение счетчика ссылок становится нулевым (только динамическая компоновка во время выполнения), Библиотека DLL выгружается из виртуального адресного пространства процесса.
Как и любая другая функция, экспортированная функция DLL выполняется в контексте потока, который ее вызывает. Поэтому применяются следующие условия.
С точки зрения программиста - DLL представляет собой библиотеку функций (ресурсов), которыми может пользоваться любой процесс, загрузивший эту библиотеку. Сама загрузка отнимает время и увеличивает расход потребляемой приложением памяти; поэтому дробление одного приложения на множество DLL ничего хорошего не принесет. Другое дело - если какие-то функции используются несколькими приложениями. Тогда, поместив их в одну DLL, мы избавимся от дублирования кода и сократим общий объем приложений - и на диске, и в оперативной памяти. Можно выносить в DLL и редко используемые функции отдельного приложения; например, немногие пользователи текстового редактора используют в документах формулы и диаграммы - так зачем же соответствующим функциям впустую "отъедать" память?
Загрузившему DLL процессу доступны не все ее функции, а лишь явно предоставляемые самой DLL для "внешнего мира" - т.н. экспортируемые. Функции, предназначенные сугубо для "внутреннего" пользования, экспортировать бессмысленно (хотя и не запрещено). Чем больше функций экспортирует DLL - тем медленнее она загружается; поэтому к проектированию интерфейса (способа взаимодействия DLL с вызывающим кодом) следует отнестись повнимательнее. Хороший интерфейс интуитивно понятен программисту, немногословен и элегантен: как говорится, ни добавить, ни отнять. Строгих рекомендаций на этот счет дать невозможно - умение приходит с опытом.
Типы данных в С и Python
Модуль ctypes предоставляет возможность использовать типы данных совместимые с типами в языке С. Ниже приведена таблица соответствия типов данных.
Сtypes type C type Python type c_bool _Bool bool (1) c_char char 1-character string c_wchar wchar_t 1-character unicode string c_byte char int/long c_ubyte unsigned char int/long c_short short int/long c_ushort unsigned short int/long c_int int int/long c_uint unsigned int int/long c_long long int/long c_ulong unsigned long int/long c_longlong __int64 or long long int/long c_ulonglong unsigned __int64 or unsigned long long int/long c_float float float c_double double float c_longdouble long double float c_char_p char * (NUL terminated) string or None c_wchar_p wchar_t * (NUL terminated) unicode or None c_void_p void * int/long or None Таблица 1 — Соответствие типов данных языка Python и языка C, которое предоставляет модуль ctypes .
Первое, что стоит попробовать — это использовать указатели, куда без них? Давайте напишем программу, где создадим строку и указатель на неё, а потом вызовем printf() для них:
Результат:
Если вы создали указатель, то разыменовать (получить доступ к значению, на которое он указывает) можно с использованием атрибута value , пример выше.
DLL & Python
Первым делом, расскажу, как подключать уже собранные DLL, затем, как вызывать из них функции и передавать аргументы, а уже после этого, постепенно доделаю шаблон из Code Blocks и приведу примеры работы с собственной DLL.
Точка входа DLL
При создании библиотеки DLL можно дополнительно указать функцию точки входа. Функция точки входа вызывается, когда процессы или потоки присоединяются к библиотеке DLL или отсоединяются от библиотеки DLL. Функцию точки входа можно использовать для инициализации структур данных или для уничтожения структур данных по мере необходимости библиотеки DLL. Кроме того, если приложение является многопоточным, можно использовать локальное хранилище потоков (TLS) для выделения памяти, которая является частной для каждого потока в функции точки входа. Ниже приведен пример функции точки входа библиотеки DLL.
Когда функция точки входа возвращает значение FALSE, приложение не запускается при использовании динамической компоновки во время загрузки. При использовании динамической компоновки во время выполнения загрузка только отдельной библиотеки DLL не выполняется.
Функция точки входа должна выполнять только простые задачи инициализации и не должна вызывать другие функции загрузки или завершения библиотеки DLL. Например, в функции точки входа не следует прямо или косвенно вызывать LoadLibrary функцию или функцию LoadLibraryEx . Кроме того, не следует вызывать функцию FreeLibrary при завершении процесса.
В многопоточных приложениях убедитесь, что доступ к глобальным данным DLL синхронизирован (потокобезопасный), чтобы избежать возможного повреждения данных. Для этого используйте TLS для предоставления уникальных данных для каждого потока.
Подключение DLL
Основной библиотекой в Python для работы с типами данных, совместимыми с типами языка С является ctypes . В документации на ctypes представлено много примеров, которым стоит уделить внимание.
Чтобы начать работать с DLL, необходимо подключить библиотеку к программе на Python. Сделать это можно тремя способами:
- cdll — загружает динамическую библиотеку и возвращает объект, а для использования функций DLL нужно будет просто обращаться к атрибутам этого объекта. Использует соглашение вызовов cdecl.
- windll — использует соглашение вызовов stdcall. В остальном идентична cdll.
- oledll — использует соглашение вызовов stdcall и предполагается, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического вызова исключения WindowsError.
Для первого примера будем использовать стандартную Windows DLL библиотеку, которая содержит всем известную функцию языка С — printf() . Библиотека msvcrt.dll находится в папке C:\WINDOWS\System32 .
Код Python:
Результат:
Можно использовать подключение библиотеки с помощью метода windll либо oledll , для данного кода разницы не будет, вывод не изменится.
Если речь не идет о стандартной библиотеке, то конечно следует использовать вызов с указанием пути на dll. В ctypes для загрузки библиотек предусмотрен метод LoadLibrary . Но есть еще более эффективный конструктор CDLL , он заменяет конструкцию cdll.LoadLibrary . В общем, ниже показано два примера вызова одной и той же библиотеки msvcrt.dll.
Код Python:
Иногда случается, что необходимо получить доступ к функции или атрибуту DLL, имя которого Python не "примет"… ну бывает. На этот случай имеется функции getattr(lib, attr_name) . Данная функция принимает два аргумента: объект библиотеки и имя атрибута, а возвращает объект атрибута.
Код Python:
Результат:
Теперь становится понятно, как подключить библиотеку и использовать функции. Однако, не всегда в DLL нужно передавать простые строки или цифры. Бывают случаи, когда требуется передавать указатели на строки, переменные или структуры. Кроме того, функции могут и возвращать структуры, указатели и много другое.
Читайте также: