Динамически подключаемые библиотеки dll описание способы загрузки

Обновлено: 06.07.2024

Для начала нужно разобраться, что же такое DLL.
DLL — это сокращение фразы Dynamic-Link Library — динамически подключаемая библиотека.
Первоначальная идея введения библиотек заключалась в более рациональном использовании ресурсов ПК, таких как ОЗУ и НЖМД. Предполагалось, что различные приложения будут использовать одну библиотеку, тем самым ликвидируя проблему сохранения одинаковых участков кода в различных местах и многократную загрузку одинаковых участков в ОЗУ.

В дальнейшем, было решено увеличить эффективность разработки приложений за счёт модульности. Обновление самих DLL должно было позволить расширять и усовершенствовать систему, не затрагивая при этом всех приложений. Однако, данная идея столкнулась с проблемой одновременного требования приложениями разных, не полностью совместимых версий библиотек, что приводило к сбоям. Данная проблема была названа "DLL Hell".

По сути, DLL - это относительно независимый участок кода, имеющий свою оболочку.
Оболочка DLL имеет секции импорта/экспорта.

Секция экспорта перечисляет те идентификаторы объектов (классы, функции, переменные), доступ к которым предоставляет данная DLL. В большинстве случаев именно эта секция вызывает особый интерес у разработчиков. Тем не менее, DLL может вовсе не содержать секцию экспорта. Кроме функций, к которым можно получить доступ извне (exported), существуют также внутренние функции (internal), которые спрятаны от "посторонних глаз" и могут использоваться лишь кодом самой DLL.

Секция импорта предназначена для связи данной DLL с другими.
Большинство библиотек импортируют функции из системных DLL (kernel32.dll, user32.dll и др.). Иногда в стандартный список нужно добавить другие библиотеки, например ws2_32.dll для работы с Socket'ами.

DLL не может выполняться сама по себе, поэтому требует хост-процесс (главный процесс, в котором предполагается использование DLL).
Перед тем, как библиотеку можно будет использовать, её необходимо загрузить в область памяти хост-процесса. В exe-файле компилятором будет сгенерирована инструкция вызова заданной функции (call).
Т.к. DLL расположена в адресном пространстве процесса, то вызов функции из библиотеки будет иметь вид:

При выполнении команды call, процессор передает управление на код с адреса 010199, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой.

Файл, являющийся динамически загружаемой библиотекой не всегда носит расширение *.dll.
Есть также: *.cpl (библиотеки, используемые апплетом панели управления) и *.ocx.

Библиотеки удобно использовать для разделения приложения на части, выполняющие разные роли.
Например, приложение, обеспечивающее работу с базами данных может быть разделено на часть, отвечающую за выборку данных из базы, находящейся на сервере, и, часть, которая отвечает за визуальное управление приложением. Это явный пример той модульности, о которой я упоминал выше. Вы в праве изменять принципы обмена данными с сервером БД, не затрагивая при этом работу с визуальной частью.

  • 1.3. Необходимость внедрения DLL. Нужно ли это?

Настало время ответить на вопрос: Когда же нужно использовать DLL?
Если вы разрабатываете небольшой проект или тестовое приложение, то внедрение DLL будет для вас пустой тратой времени и сил. Тратой времени: не только времени на создание библиотеки, но ещё и времени, необходимого для загрузки DLL в память хост-процесса. До того, как вы будете использовать объекты из DLL, вам рано или поздно прийдётся выгрузить содержимое в память. А это требует некоторого времени.
Однако, если вашу DLL будет использовать более, чем одна копия приложения (или несколько различных приложений) - наступит явный выигрыш.

Для эксперимента запустите Microsoft Office. Первый запуск долгий. Это обусловлено тем, что все необходимые модули загрузаются в оперативную память. Теперь полностью закройте Office и снова откройте его! Окно появится почти мгновенно. Это обусловлено тем, что модули хранились в ОЗУ (система не тронет их, пока ей не понадобится память для других целей).

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

Но не старайтесь придать вашему проекту чрезвычайной "уникальности" за счёт помещения каждой функции в отдельную библиотеку! Это можно делать, только зачем тратить время для загрузки каждого модуля?

  • 2.1. Создаём первую DLL своими руками.

Пришло время постепенно перейти от теории к практике.
Открываем IDE (для написании данной статьи я использовал RAD Studio 2010).
Переходим к File -> New -> Other -> Dynamic-Link Library.
Перед нами возникает диалог:

Source Type - язык, на котором ведётся разработка.
Use VCL - использование библиотеки визуальных компонентов.
Multi Threaded - опция, указывающая на то, будет ли использоваться многопоточность в данной DLL (VCL уже подразумевает в себе многопоточность).
VC++ Style DLL - опция, указывающая на то, будет ли DLL совместима с компиляторами Microsoft.

Если в совместимости нет нужды и опция не выбрана, то DLL будет иметь точку входу с таким прототипом:

Для обеспечения совместимости точка входа изменяется на:

Оставим диалог без изменений и нажмём "ОК".
Перед нами появится шаблон минимальной DLL. Сохраним его.
Как вы помните, сама по себе DLL работать не может, ей нужен клиент. Поэтому, для удобства сразу создадим новый проект VCL Forms Application.
Для этого переходим в Project Manager, вызываем контекстное меню у нашей Project Group и переходим к Add New Project -> VCL Forms Application.
Для удобста я назвал проекты TestDLL и TestVCL соответственно (и сохранил их в одном каталоге - это избавит меня от копирования DLL или указания абсолютного пути):

Без изменений запускаем TestVCL, сохраняем и переключаемся к проекту TestDLL (дабл-клик на проекте в Project Manager).

Переходим к Run -> Parameters и в поле Host Application указываем путь к нашему проекту TestVCL.

К шаблону DLL добавляем функцию, которая будет вычислять сумму и выводить результат на экран:

В проекте TestDLL добавим также заголовочный файл (TestDLL.h) с таким содержанием:

Сохраняем. Запускаем. DLL мы подготовили. Теперь необходимо узнать, как же подключить DLL к проекту. Сделать это можно тремя способами. Рассмотрим их подробнее.

При неявной загрузке DLL загружается (проецируется на адресное пространство вызывающего процесса) при его создании. Если при загрузке возникает ошибка - процесс останавливается и разрушается.

Для выполнения неявной загрузки приложению требуются:
- Заголовочный файл (*.h) с прототипами функций, описаниями классов и типов, которые используются в приложении.
- Библиотечный файл (*.lib), в котором описывается список экспортируемых из DLL функций (переменных), и их смещения, необходимые для правильной настройки вызовов функций.

В проекте TestVCL подключим наш заголовочный файл:

Также, не забываем сделать Project -> Add To Project и добавить в проект TestDLL.lib
Далее, объявим прототип:
Теперь осталось только вызвать функцию там, где это необходимо.
Чтобы убедится в том, что всё работает, прописываем в конструкторе формы:

Запускаем и смотрим на результат. Думаю, "3 + 2 = 5" всех устраивает.

Для того, чтобы выполнить явную загрузку программист должен попыхтеть, управляя DLL через функции WinAPI.
Наиболее часто рассматриваемые WinAPI функции:
DisableThreadLibraryCalls, FreeLibrary, FreeLibraryAndExitThread, GetModuleFileName, GetModuleHandle, GetProcAddress, LoadLibrary .

При этом, основными функциями являются:
LoadLibrary[Ex] - позволяют загрузить DLL в адресное пространство хост-процесса.
FreeLibrary - функция, используемая для явной выгрузки DLL.
GetProcAddress - функция, позволяющая получить виртуальный адрес экспортируемой из DLL функции(или переменной) для ее последующего вызова.

Общая методика выглядит так:
1. Загрузить DLL с помощью LoadLibrary.
2. Получить указатели на необходимые объекты с помощью GetProcAddress.
3. Выгрузить DLL после завершения всех действий.

Теперь возникает вопрос, как же проверить теорию на практике?
Всё, что нужно, это добавить TestDLL.lib к проекту (также, как и при неявной загрузке).
А дальше, для проверки снова пишем в конструкторе формы:

И на экране снова красуется победная надпись "3 + 2 = 5"

Остался один неосвещенный вопрос. Почему же название функции "ShowSum" мы ищем в библиотеке с нижним подчёркиванием?

Виновато во всём декорирование имён.

Декорирование (или искажение, mangling) имен - это специфическое явление, присущее компиляторам языка C++, которое необходимо учитывать при разработке DLL на этом языке. Оно заключается в том, что компилятор С++ к имени функции всегда добавляет сокращенный список формальных параметров и тип возвращаемого значения.

Прототип функции Test(int); мог быть преобразован компилятором, например в ?Test@@YAKH@Z.
Естественно, такое декорирование нам вообще не по душе. Избавиться от него можно объявляя все экспортируемые функции с модификатором extern "C" - тогда компилятор не будет искажать имя функции.

Однако, как мы видим, нижние подчёркивание всё же добавилось.
Это один из нюансов среды C++ Builder. Однако, можно отучить его добавлять нижнее подчёркивание таким образом:
Project -> Options -> C++ Compiler -> Output -> Generate underscores on symbol names - перевести в состояние false.

Для чего же нужна отложенная загрузка?
Представьте себе ситуацию: вы написали приложение, использующее стандартные системные библиотеки вашей новой операционной системы, скажем, для проверки орфографии.
Даёте это приложение пользователю, который использует ОС более старой версии, чем у вас и в этой ОС нет функций для проверки орфографии. А пользователю это не сильно и надо. Приложение будет работать, пока не обратится к необходимой функции. То есть, фактически, DLL не нужна до обращения к определённой функции. Исключение отсутствия можно обработать и выдать пользователю предупреждение с просьбой обновить библиотеки своей ОС (и т.п.).

Использование отложенной загрузки DLL в C++ Builder мало отличается от неявной загрузки.
В проект добавляется заголовочный (*.h) файл с описаниями и библиотечный файл (*.lib).
Далее, переходим в Project -> Options -> C++ Linker -> Advanced -> Delay Load DLLs и вписываем название нашей библиотеки (TestDLL.dll).

Когда библиотека теряет свою необходимость её нужно явно выгрузить с помощью __FUnloadDelayLoadedDLL. В качестве параметра передаём имя DLL (с расширением + параметр регистрозависим).
Если вы используете многопоточные приложения - убедитесь, что все потоки завершили работу с DLL.

Примечание: Нельзя использовать отложенную загрузку для библиотек, имеющих секцию импорта (т.е. использующих другие библиотеки), а также Kernel32.dll и RTLDLL.dll (т.к. функции поддержки отложенной загрузки как раз и находятся в последней).

3. Заключение.

Данная статья даёт вам представление о возможных вариантах использования DLL в ваших проектах.
При внимательном ознакомлении с методами загрузки можно выбрать наиболее оптимальный вариант.

Подведя итоги можно выявить плюсы и минусы описанных методов:

Явная загрузка:
+ контроль и управление процессом жизни DLL.
- управлением DLL занимается программист посредством WinAPI.

Неявная загрузка:
+ все заботы берет на себя компилятор и сборщик.
- ресурсы заняты всё время жизни приложения.

Отложенная загрузка:
+ все заботы берет на себя компилятор и сборщик.
+ возможность использования приложения с не полностью совместимыми DLL.
- необходимость усиленного контроля за многопоточными приложениями.

Библиотека DLL представляет собой коллекцию подпрограмм, которые могут быть вызваны на выполнение приложениями или подпрограммами из других библиотек. Подобно модулям, библиотеки DLL содержат разделяемый (sharable) код или ресурсы. Однако, в отличие от модулей, библиотеки содержат отдельно откомпилированный исполняемый код, который подключается к приложению динамически на этапе его выполнения, а не компиляции.

Для того чтобы библиотеки можно было отличить от самостоятельно выполняемых приложений, они имеют расширение .dll. Программы, написанные на Object Pascal, могут использовать библиотеки, написанные на других языках. С другой стороны, приложения Windows могут использовать библиотеки, написанные на Object Pascal.

Библиотеки могут содержать два вида подпрограмм: экспортируемые и внутренние. Экспортируемые подпрограммы могут вызываться процессом, подключающим библиотеку, а внутренние могут быть вызваны только внутри библиотеки.

Библиотека загружается в адресное пространство процесса, который ее вызвал. Подпрограммы библиотеки также используют стековое пространство процесса и его динамическую память.

Преимущества использования динамических библиотек (по сравнению со статическим подключением подпрограмм на этапе сборки приложения) следующие:

1 Процессы, которые загрузили библиотеку по одному и тому же базовому адресу, пользуются одной копией их кода, но имеют свои собственные копии данных. Благодаря этому снижаются требования к объему памяти и снижается своппинг.

2 Модификация подпрограмм библиотек не требует повторной компиляции приложений до тех пор, пока остается неизменным формат их вызова.

3 Библиотеки обеспечивают также послепродажную поддержку (after-market support). Например, дисплейный драйвер, предоставляемый библиотекой, может быть обновлен для того, чтобы поддерживать дисплей, который не существовал в момент продажи приложения.

4 Программы, написанные на разных языках программирования, могут использовать одну и ту же библиотечную функцию, если они следуют соглашению по вызову, предусмотренному функцией.

Интерфейс прикладных программ (Microsoft Win32 application programming interface – API) реализован как набор DLL–библиотек.

Вызов подпрограмм из библиотек DLL.

Прежде чем программа сможет вызвать из библиотеки подпрограмму, ее необходимо импортировать. Сделать это можно двумя путями: объявить подпрограмму с директивой external или использовать прямой вызов с помощью Windows API. При этом в любом случае подпрограмма подключается к приложению только на этапе выполнения приложения. Из этого также следует, что на этапе компиляции нет проверки корректности вызова подпрограммы.

Необходимо отметить, что Object Pascal не поддерживает импорт переменных из DLL.

Статическая загрузка подпрограмм.

Простейший способ импорта подпрограммы из библиотеки состоит в объявлении ее с помощью директивы external, например

procedure DoSomething; external 'MyLib.dll';

function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer):
Integer; stdcall; external 'user32.dll' name 'MessageBoxA';

// MessageBoxA – 'настоящее' имя функции в библиотеке

Если включить эти объявления в программу, библиотеки MyLib.dll и user32.dll будет загружена в память при запуске программы на выполнение. В течение всего времени выполнения программы идентификатор DoSomething или MessageBox будет ссылаться на одну и ту же точку входа в той же библиотеке.

Объявления импортируемых подпрограмм можно поместить непосредственно в программу или модуль, которые их используют. Вместе с тем использование библиотечных подпрограмм можно упростить, если собрать их объявления в отдельном модуле, который бы содержал также описания, необходимые для использования подпрограмм из библиотек. Примером может служить модуль Windows, предоставляющий доступ к функциям Windows API. Другие модули теперь будут просто подключать модуль импорта и вызывать необходимые подпрограммы обычным путем.

Пример использования статической библиотеки (полный текст см. в проекте UseDll):

Function Max(a,b:integer):integer; external 'MyDll.dll';

Function Min(a,b:integer):integer; external 'MyDll.dll';

Динамическая загрузка библиотек.

Приложение может самостоятельно загружать и выгружать библиотеки и получать доступ к их подпрограммам с использованием функции GetProcAddress. Для целей загрузки и выгрузки библиотек предназначены подпрограммы LoadLibrary и FreeLibrary. Пример их использования (полный пример см. в проекте UseDll_2):

const DllName = 'MyDll.dll'; //можно указать 'MyDll'

Procedure FormCreate ( Sender : TObject);

Procedure FormClose ( Sender: TObject; var Action: TCloseAction);

MyHandle : integer; для получения результата выполнения функции

Тип TFunction необходим для того, чтобы воспользоваться функцией

GetProcAddress для получения адреса необходимой функции из библиотеки>

TFunction = Function (a,b:integer) : integer;

Procedure TForm1.FormCreate ( Sender : TObject );

MyHandle := LoadLibrary ( DllName );

if MyHandle = 0 then

ShowMessage('Ошибка при загрузке библиотеки ' + DllName);

if (@FMax=nil) or (@FMin=nil) then

ShowMessage('Не найдены необходимые функции в библиотеке ' + DllName);

теперь подпрограммы можно вызывать используя имена FMax и FMin>

Procedure TForm1.FormClose( Sender : TObject; var Action: TCloseAction);

Выгружаем библиотеку из памяти>

В этом способе использования библиотеки ее загрузка в память не выполняется до вызова функции LoadLibrary, что позволяет экономить память. Кроме того, в этом случае можно выполнять программу даже в том случае, если какие-либо библиотеки не найдены (если, разумеется, в этом есть какой-либо смысл). Чтобы получить более подпробную информацию об ошибке, надо использовать функцию GetLastError.

Поиск загружаемой библиотеки, если ее имя не содержит пути, выполняется по следующим маршрутам (см. Help для функции LoadLibrary):

1 В каталоге, из которого было запущено приложение.

2 В текущем каталоге.

3 В каталоге System (Windows 95) или в каталоге System32 (Windows NT). Для получения пути к этому каталогу можно использовать функцию GetSystemDirectory.

4 Для Windows NT: в каталоге SYSTEM (16-битная версия).

5 В каталоге Windows. Для получения пути к этому каталогу можно использовать функцию GetWindowsDirectory.

6 В каталогах, перечисленных в переменной окружения PATH.

При попытке повторной загрузки библиотеки ошибки не происходит и библиотека не загружается.

В любой момент временибиблиотеку можно выгрузить с помощью процедуры FreeLibrary.

Решение проблем совместимости приложений.

Введение

Одним из самых важных новшеств в Microsoft® Windows® XP стало добавление целого ряда технологий совместимости приложений, доступных даже конечным пользователям через оболочку Windows XP. Распространение исправлений совместимости приложений на большом количестве компьютеров может быть трудным или невыполнимым, если оно предоставлено каждому пользователю компьютера. К счастью, есть более простой способ собирать группы исправлений совместимости и распределять их путем автоматической установки на компьютеры, работающие под управлением Windows XP.

Администратор совместимости

После установки группы необходимых исправлений совместимости, Вы можете воспользоваться Администратором совместимости, чтобы скомпоновать исправления совместимости для распространения на других компьютерах, работающих под управлением Windows XP.

Создание собственных оболочек совместимости с помощью Администратора совместимости

В этом разделе обсуждается как можно создавать и подготавливать файлы собственной базы данных с помощью Администратора совместимости, для поддержания множества приложений на одном или нескольких компьютерах, работающих под управлением Windows XP.

Администратор совместимости может компоновать исправления и оболочки совместимости для множества приложений в один файл базы данных совместимости (*.sdb), который потом может быть перенесен на другие компьютеры, работающие под управлением Windows XP. Это особенно полезно в большом сетевом окружении, где несколько человек должны обеспечивать поддержку программного обеспечения огромному числу пользователей.

Установка Администратора совместимости

Администратор совместимости, поставляемый с операционной системой Windows XP, может быть найден в папке Support Tools на установочном компакт-диске. Администратор совместимости распространяется как часть Пакета средств обеспечения совместимости приложений (Application Compatibility Toolkit) версии 2.0 и выше.

Для установки Пакета средств обеспечения совместимости приложений (Application Compatibility Toolkit) в Вашей ОС Windows XP:

  1. Вставьте установочный компакт-диск Windows XP в привод компакт-дисков
  2. Используя Мой компьютер ( My Computer ) или Проводник ( Windows Explorer), перейдите на привод, в который Вы вставили диск с ОС Windows XP, и откройте папку Support Tools.
  3. Щелкните дважды файл ACT. EXE для начала установки программы. Примите настройки, предложенные по умолчанию программой установки.

После установки Пакета средств обеспечения совместимости приложений (Application Compatibility Toolkit) его можно будет найти в меню Пуск. Администратор совместимости находится в группе Пакета средств обеспечения совместимости приложений (Application Compatibility Toolkit) в меню Пуск.

Использование Администратора совместимости

Windows XP содержит информацию о распространенных проблемах совместимости, которые могут происходить с некоторыми приложениями. Исправления совместимости приложений, предоставляемые Microsoft в Windows XP, созданы, чтобы помочь Windows XP должным образом поддерживать нормальную работу этих приложений, не ставя при этом под угрозу стабильность системы.

  • Антивирусные программы
  • Программы, которые требуют доступа на уровне ядра операционной системы
  • Программы, которые устанавливают специфические драйверы файловой системы

Причины неправильной работы приложений
Приложения, которые были созданы для работы с предыдущими версиями Windows, могут неправильно работать в ОС Windows XP Professional. Причины, по которым это может происходить:

  • Приложение не запускается, когда Windows сообщает ему о новой версии операционной системы. Зачастую приложение работает нормально, если пользователь сможет обойти этот момент.
  • Приложение обращается к старым версиям функций Win32 API, которые возвращают непредсказуемые значения на компьютерах с большим количеством ресурсов, таких как дисковое пространство
  • Приложение ожидает старых форматов данных Windows.
  • Приложение ожидает, что информация пользователя, такая как личные и временные папки, будет в определенном месте или в определенном формате.

Для устранения этих проблем с помощью Администратора совместимости Вам необходимо создать собственную базу данных, содержащую информацию об исправлениях совместимости, необходимую Вашим приложениям, а также информацию о соответствии файлов, которая позволяет Windows XP однозначно распознать приложение, требующее поддержки.

Создание собственной базы данных совместимости

Администратор совместимости позволяет Вам просматривать исправления совместимости приложений, хранящиеся в защищенных системой базах данных, чтобы применять нужные исправления для сотен приложений. Основной интерфейс Администратора позволяет контролировать приложения с исправлениями совместимости путем просмотра их в базе данных ОС Windows XP Professional. Эта информация отображается в верхней левой части (части системной базы данных) окна Администратора совместимости.

Системная база данных совместимости является составляющей операционной системы Windows XP Professional, обеспечивающей идеальную совместимость для сотен Windows-приложений. Эта база данных и соответствующие компоненты защищены операционной системой.

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

Чтобы создать новую собственную базу данных с помощью Администратора совместимости:

  1. Откройте Администратор совместимости выбрав в меню Пуск ( Start), Программы( All Programs), Пакет средств обеспечения совместимости приложений (Application Compatibility Toolkit), Администратор совместимости
  2. Если у Вас открыта собственная база данных, в меню Файл ( File) выберите Новый ( New).
  3. Зайдите в меню База данных ( Database) и нажмите Изменить название базы данных ( Change Database Name ). Как только Вы измените название базы данных, оно будет отображаться в заголовке собственной базы данных. Если пункт менюИзменить имя базы данных ( Change Database Name ) не активен, щелкните по области базы данных окна.
  4. В меню Файл ( File) нажмите Сохранить ( Save) и дайте название своему .sdb файлу. Теперь можно добавить исправления в Вашу собственную базу данных.

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

Для добавления оболочки совместимости

Тот же процесс может быть использован для добавления индивидуальных исправлений совместимости в собственную базу данных, за исключением того, что в окне Создать исправление приложения ( Create an Application Fix ) Вы должны выбрать вариантПрименить определенное исправление совместимости ( Apply Specific Compatibility Fix ). Как только все исправления и оболочки будут добавлены в базу данных, сохраните базу данных и проверьте приложение.
Совпадение имен файлов

Применение собственной базы данных к системе

Как только Вы создали Вашу собственную базу данных исправлений совместимости приложений, она должна быть применена к системе компьютера, на котором это приложение будет работать. Общий процесс развертывания исправлений совместимости на нескольких компьютерах под управлением Windows XP включает следующие действия:

  • Определите и проверьте исправления для необходимых приложений.
  • Создайте файл выборочной базы данных с нужными исправлениями.
  • Перенесите .sdb файл на нужные компьютеры под управлением Windows XP.
  • Используйте команду SDBINST.EXE, чтобы зарегистрировать базу данных. Она автоматически установит и добавит информацию об исправлениях в реестр на выбранных компьютерах.

Перенос файла собственной базы данных на другие компьютеры под управлением Windows XP
Перенос файла собственной базы данных на другие компьютеры под управлением Windows XP может быть проведена разными способами:

  • Можно поместить файл базы данных в программу установки и распространить его с помощью Групповой политики в сети с Active Directory, но это требует дополнительной работы.
  • Файл может быть скопирован вручную на каждый удаленный компьютер, или это можно сделать с помощью сценария входа в систему.
  • Еще одной возможностью является размещение файла .sdb на общем сетевом ресурсе, к которому имеют доступ все пользователи Windows XP.

Передо мной возникла задача написать загрузчик библиотек, имеющий возможность предоставить какие-то интерфейсные функции внешней динамической библиотеке. Решение должно быть максимально кроссплатформенно (как минимум, работать на 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

Современное программирование стало достаточно сложным, уже написаны километры строк кода, и некоторые его части время от времени приходится дублировать в разных программах. Если бы каждый программист в каждой новой программе заново писал весь необходимый код, языки программирования никогда бы не получили такого стремительного развития. Однако мы в своих проектах можем использовать инструменты, созданные другими программистами - типы, процедуры, функции… Мы уже неоднократно подключали к нашим проектам различные модули и пользовались описанными в них средствами. И всё было бы хорошо, если бы не одно НО…

Дело в том, что при использовании модульного подхода, мы в момент компиляции проекта внедряем в него весь тот код, который был описан в модуле. И наша программа от такого внедрения "толстеет", добавляет иной раз по несколько мегабайт . Казалось бы, пустяки, современное аппаратное обеспечение достаточно хорошо развито, и мы можем себе это позволить. Но это все же тысячи строк чужого кода, причем далеко не весь этот код задействован в наших проектах! Теперь представьте, что вы пишете одну программу за другой, внедряете в нее одни и те же модули, в результате ваши проекты содержат мегабайты совершенно одинакового кода. И ладно бы только это, но представьте также, что вы одновременно загружаете и используете несколько программ (да здравствует многозадачность !). А эти программы тоже могут иметь в своем составе множество строк дублированного кода. Вот теперь у нас получается настоящее расточительство - мы впустую переводим не только память жесткого диска, где хранятся программы, но и оперативную память , куда мы их для использования загрузили!

Есть еще один недостаток модульного подхода - мы можем использовать чужой код, только если он написан на таком же языке программирования, каким пользуемся мы. Ну, в Lazarus мы можем использовать модули Delphi, а также код на Ассемблере. Но если код был написан на С++ или Visual Basic , к примеру? Короче говоря, требовался новый подход, позволяющий использовать чужой код, не внедряя его непосредственно в программу, и, кроме того, "языконезависимый". И таким подходом стала DLL.

DLL (англ., Dynamic Link Library - Динамически Подключаемые Библиотеки) - универсальный механизм внедрения в программу функций и процедур, написанных другими программистами, и возможно, на другом языке программирования. Внедрение происходит не в момент компиляции проекта, как в случае модулей, а в момент загрузки готовой программы в оперативную память.

Использование DLL дает нам еще одно очень важное преимущество. Представьте, что у вас десятки программ импортируют какой-то код. И вот, этот код нужно изменить, или дополнить, потому что вы перешли на другую ОС, на другие стандарты или просто потому, что начальство потребовало от вас расширить функциональность ваших программ. Если бы вы использовали модули, вам пришлось бы туго - нужно было бы переделывать не только этот модуль , но и перекомпилировать ваши программы. Но вы ведь их создавали не неделю и не месяц, правда? Что, если часть из них вы писали когда-то на Delphi 7, другую часть на Delphi XE3, а третью - уже на Lazarus? Работа была бы та еще. Но с использованием DLL эта задача сильно упрощается. Вам нужно переделать всего лишь эту самую DLL -библиотеку, а программы, использующие её, переделывать уже не нужно!

Каким же образом используются эти DLL ? Фактически, DLL - это библиотека тех инструментов, которые могут быть использованы вами в различных проектах. Причем с учетом того, что вы, может быть, будете использовать различные языки программирования. Итак, первым делом, вы создаете саму библиотеку - файл *.dll. Создается он, как отдельное приложение , но после компиляции сам по себе работать все равно не сможет, поскольку не имеет собственной точки входа, как выполняемый exe- файл .

Далее, вы создаете приложение . Файл используемой динамической библиотеки вы помещаете в ту же папку, что и проект. В случае, если несколько ваших проектов будут использовать эту библиотеку, то её лучше поместить в какую-либо системную папку, например,

C:\Windows\system32

Тогда все эти программы смогут подгружать данную библиотеку, не указывая её адреса - Windows просматривает все свои системные папки и знает, где находится эта DLL . Функции и процедуры, описанные в DLL , наша программа будет воспринимать, как свои собственные. И когда во время выполнения программы происходит вызов таких функций и процедур, то будет подгружена соответствующая DLL .

Во время компиляции проекта код библиотеки не будет внедрен в программу. Это означает две вещи:

  1. ваша программа не будет иметь лишнего кода;
  2. при распространении программы между пользователями вам нужно будет также позаботиться, чтобы у них была установлена и эта dll-библиотека.

Когда пользователь загружает вашу программу, то также загружается и используемый dll - файл , на это тратится дополнительное время. Зато библиотека остается в оперативной памяти даже после завершения работы загрузившей её программы. И если затем будет загружена другая программа , использующая ту же DLL , она уже не будет тратить время на повторную загрузку этой библиотеки, а воспользуется уже загруженной в память версией. Более того, если будут одновременно работать несколько программ, использующих одну и ту же DLL , то они не будут загружать для себя по копии этой библиотеки, поскольку Windows гарантирует, что одновременно может быть загружена только одна копия динамической библиотеки. Так что первая из этих программ загрузит DLL в оперативную память , остальные будут использовать её же, не тратя лишнего времени на загрузку. Вот почему такие программы, как MS Word или MS Excel медленно загружаются в первый раз - они обращаются ко множеству библиотек, которые также подгружаются в память . Зато когда вы загружаете MS Word вторично, он грузится гораздо быстрее, так как нужные библиотеки уже в оперативной памяти! Почувствовали разницу между DLL и модулями?

Кстати, динамическим библиотекам принято давать расширение *.dll, однако это не всегда так. Вы можете дать такой библиотеке любое расширение, или даже вообще не указывать его! Например, драйверы устройств имеют расширение *.drv, но это такие же динамические библиотеки.

У DLL есть один недостаток: поскольку они языконезависимы, а также по некоторым другим причинам, в этих библиотеках нельзя использовать глобальные переменные , которые будут доступны в программе. Поэтому в DLL нельзя использовать объекты (свойства объектов - это глобальные переменные ). Так что воспринимайте DLL , как набор процедур и функций, который удобно подгружать к различным программам.

В нашем курсе подразумевается, что мы используем операционную систему семейства Windows , однако механизм динамически подключаемых библиотек существует во всех современных операционных системах. В Windows такая библиотека представляет собой файл *.dll, в Linux и Unix - *.so (Shared Object Library ), а в MacOS X - *.dylib ( Dynamic Shared Library ). И все эти библиотеки можно создавать с помощью Lazarus. Учитывая, что мы используем ОС Windows , мы будем рассматривать создание dll-файлов.

Читайте также: