Форум программистов, компьютерный форум, киберфорум
Наши страницы

C++ Builder

Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 305, средняя оценка - 4.70
MikeSoft
Эксперт С++
3802 / 1778 / 85
Регистрация: 21.11.2009
Сообщений: 2,540
#1

Dynamic-Link Library: Теория + Практика - C++ Builder

20.07.2010, 23:49. Просмотров 42532. Ответов 7

1. Теоретическая часть. Знакомство с Dynamic-Link Library.
  • 1.1. Что такое DLL.
  • 1.2. Использование DLL.
  • 1.3. Необходимость внедрения DLL. Нужно ли это?


2. Практическая часть. Создание Dynamic-Link Library в RAD Studio.
  • 2.1. Создаём первую DLL своими руками.
  • 2.2. Неявная загрузка.
  • 2.3. Явная загрузка.
  • 2.4. Отложенная загрузка.


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


_________________________________________________



1. Теоретическая часть. Знакомство с Dynamic-Link Library.
  • 1.1. Что такое DLL.


Для начала нужно разобраться, что же такое 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'ами.



  • 1.2. Использование DLL.


DLL не может выполняться сама по себе, поэтому требует хост-процесс (главный процесс, в котором предполагается использование DLL).
Перед тем, как библиотеку можно будет использовать, её необходимо загрузить в область памяти хост-процесса. В exe-файле компилятором будет сгенерирована инструкция вызова заданной функции (call).
Т.к. DLL расположена в адресном пространстве процесса, то вызов функции из библиотеки будет иметь вид:
Код
call адрес_вызываемой_процедуры
Например:
Assembler
1
2
3
4
5
6
7
;Произвольный код
call 010199  ; Вызов процедуры
; Произвольный код
; Код с адреса 010199:
add eax, 500
mul eax, edx
ret
При выполнении команды call, процессор передает управление на код с адреса 010199, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой.

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

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

DLL может являться также обычным хранилищем ресурсов, о создании которого я рассказывал в теме http://www.cyberforum.ru/cpp-builder/thread127736.html.



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


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

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

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

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



2. Практическая часть. Создание Dynamic-Link Library в RAD Studio.
  • 2.1. Создаём первую DLL своими руками.


Пришло время постепенно перейти от теории к практике.
Открываем IDE (для написании данной статьи я использовал RAD Studio 2010).
Переходим к File -> New -> Other -> Dynamic-Link Library.
Перед нами возникает диалог:
Dynamic-Link Library: Теория + Практика
Source Type - язык, на котором ведётся разработка.
Use VCL - использование библиотеки визуальных компонентов.
Multi Threaded - опция, указывающая на то, будет ли использоваться многопоточность в данной DLL (VCL уже подразумевает в себе многопоточность).
VC++ Style DLL - опция, указывающая на то, будет ли DLL совместима с компиляторами Microsoft.

Если в совместимости нет нужды и опция не выбрана, то DLL будет иметь точку входу с таким прототипом:
C++
1
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved);
Для обеспечения совместимости точка входа изменяется на:
C++
1
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, LPVOID lpvReserved);
Оставим диалог без изменений и нажмём "ОК".
Перед нами появится шаблон минимальной DLL. Сохраним его.
Как вы помните, сама по себе DLL работать не может, ей нужен клиент. Поэтому, для удобства сразу создадим новый проект VCL Forms Application.
Для этого переходим в Project Manager, вызываем контекстное меню у нашей Project Group и переходим к Add New Project -> VCL Forms Application.
Для удобста я назвал проекты TestDLL и TestVCL соответственно (и сохранил их в одном каталоге - это избавит меня от копирования DLL или указания абсолютного пути):
Название: 2.png
Просмотров: 10359

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

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

К шаблону DLL добавляем функцию, которая будет вычислять сумму и выводить результат на экран:
C++
1
2
3
4
5
6
7
#include "TestDLL.h" // создание этого заголовочного файла будет описано ниже
//---------------------------------------------------------------------------
void ShowSum(const int A, const int B)
{
  ShowMessage(IntToStr(A) + " + " + IntToStr(B) + " = " + IntToStr(A + B));
}
//---------------------------------------------------------------------------
В проекте TestDLL добавим также заголовочный файл (TestDLL.h) с таким содержанием:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef __TESTDLL_H
#define __TESTDLL_H
 
/*
символ TESTDLL_EXPORTS по умолчанию определен в Вашем проекте (см. Project Options -> С/С++ -> General->Preprocessor Definitions).
При этом все экспортируемые идентификаторы предваряются символом DLL_SPEC.
В случае определения TESTDLL_EXPORTS в проекте DLL_SPEC определяется как экспортируемый объект; в случае же отсутствия такого определения мы получим импортируемый объект.
Таким образом, один и тот же заголовочный файл может быть использован и в DLL-проекте, и в проекте, который будет использовать данную DLL! Без каких-либо изменений.
*/
 
#ifdef TESTDLL_EXPORTS
#define DLL_SPEC extern "C" __declspec(dllexport)
 
#else
#define DLL_SPEC extern "C" __declspec(dllimport)
#endif // TESTDLL_EXPORTS
 
/*
Каждый экспортируемый идентификатор предваряем __declspec(dllexport).
Эта директива позволяет линкеру определить, что данный идентификатор следует экспортировать из DLL. При этом создается специальный lib-файл, который содержит все экспортируемые идентификаторы из модуля. Также экспортируемые объекты заносятся в раздел экспорта DLL.
*/
 
DLL_SPEC void ShowSum(const int A, const int B);
 
#endif // __TESTDLL_H
Сохраняем. Запускаем. DLL мы подготовили. Теперь необходимо узнать, как же подключить DLL к проекту. Сделать это можно тремя способами. Рассмотрим их подробнее.



  • 2.2. Неявная загрузка.


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

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

В проекте TestVCL подключим наш заголовочный файл:
C++
1
#include "TestDLL.h"
Также, не забываем сделать Project -> Add To Project и добавить в проект TestDLL.lib
Далее, объявим прототип:
C++
1
void DLL_SPEC ShowSum(const int A, const int B);
Теперь осталось только вызвать функцию там, где это необходимо.
Чтобы убедится в том, что всё работает, прописываем в конструкторе формы:
C++
1
ShowSum(3,2);
Запускаем и смотрим на результат. Думаю, "3 + 2 = 5" всех устраивает.



  • 2.3. Явная загрузка.


Для того, чтобы выполнить явную загрузку программист должен попыхтеть, управляя 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 к проекту (также, как и при неявной загрузке).
А дальше, для проверки снова пишем в конструкторе формы:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// определяем тип "указатель на функцию"
typedef void __cdecl (*dll_func)(const int A, const int B);
 
dll_func pShowSum = NULL;
 
HMODULE hDLL = LoadLibrary("TestDLL.dll");
if (!hDLL) {
  ShowMessage("Невозможно загрузить TestDLL.dll");
  return;
}
 
// пытаемся найти в таблице экспорта необходимую нам функцию
pShowSum = (dll_func)GetProcAddress(hDLL, "_ShowSum"); // обратите внимание на название функции (объяснение будет ниже)
 
if (!pShowSum) {
  ShowMessage("Невозможно найти функцию ShowSum");
  return;
}
 
pShowSum(3,2);
 
FreeLibrary(hDLL);
И на экране снова красуется победная надпись "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.



  • 2.4. Отложенная загрузка.


Для чего же нужна отложенная загрузка?
Представьте себе ситуацию: вы написали приложение, использующее стандартные системные библиотеки вашей новой операционной системы, скажем, для проверки орфографии.
Даёте это приложение пользователю, который использует ОС более старой версии, чем у вас и в этой ОС нет функций для проверки орфографии. А пользователю это не сильно и надо. Приложение будет работать, пока не обратится к необходимой функции. То есть, фактически, 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.
- необходимость усиленного контроля за многопоточными приложениями.


_________________________________________________
С уважением, Михаил (a.k.a MikeSoft)
56
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.07.2010, 23:49
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Dynamic-Link Library: Теория + Практика (C++ Builder):

Вопрос по теме Dynamic-Link Library: Теория + Практика - C++ Builder
Здорова всем! Посмотрел тему Dynamic-Link Library: Теория + Практика и решил попробовать сделать длл. До этого ни разу не пробовал....

Г.Шилдт. Теория и практика С++ - C++
Кто-нибудь дайте бесплатную ссылку на книгу: Г.Шилдт. Теория и практика С++. BHV-Санкт-Петербург. 416 стр Please, очень...

dynamic event - C++ Builder
Как привязать Event к динамически созданаму контролю и какие параметры подавать?? void __fastcall...

dynamic RTL не линкуется - C++ Builder
Пытаюсь отправить письмо. Письмо нормально отправляется. Но как только снимаю галку в опциях Build with runtime package, вываливается куча...

Сортировка Dynamic Array - C++ Builder
есть массив DynamicArray<DynamicArray<AnsiStrin g> > fi; p1 p2 p3 p4 p2 p3 p4 p2 p3 p4 p4 нужно отсортировать в порядке...

DYNAMIC - что это такое ? - C++ Builder
В программе, написанной в C++ Builder 6, в файле Unit12.h есть несколько строк, в которых упоминается DYNAMIC. (проект и его компоненты...

7
Avazart
Эксперт С++
7423 / 5513 / 316
Регистрация: 10.12.2010
Сообщений: 24,621
Записей в блоге: 17
25.12.2012, 22:38 #2
Хочу сделать поправку по тому как в проекте самой DLL по умолчанию макрос TESTDLL_EXPORTS не определен, и
ф-ция будет "импортироваться" а не идти на экспорт, т.е будет не доступна.

Если заглянуть в Архангельского ( 7 издание ) то там используется для этих целей макрос __DLL__

А значит следовало бы в место
C++
1
2
3
4
5
6
#ifdef TESTDLL_EXPORTS
#define DLL_SPEC extern "C" __declspec(dllexport)
 
#else
#define DLL_SPEC extern "C" __declspec(dllimport)
#endif // TESTDLL_EXPORTS
использовать :
C++
1
2
3
4
5
6
#ifdef __DLL__
#define DLL_SPEC extern "C" __declspec(dllexport)
 
#else
#define DLL_SPEC extern "C" __declspec(dllimport)
#endif // __DLL__
Макрос __DLL__ определяет среда если проект представляет собой dll и не определяет если это обычный проект

Да и непонятно зачем в самом проекте еще раз объявлять ф-цию еще раз если она уже включена в хедер подключаемой бибиотеки ?
Цитата Сообщение от MikeSoft Посмотреть сообщение
Далее, объявим прототип:
C++
1
void DLL_SPEC ShowSum(const int A, const int B);
Добавлено через 8 минут
Использование dll, Declaration terminated incorrectly
8
kzru_hunter
1102 / 771 / 58
Регистрация: 01.02.2011
Сообщений: 1,779
Записей в блоге: 1
29.04.2014, 11:37 #3
Явную загрузку можно ещё и так сделать (без использования typedef):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        // îáúÿâëÿåì "ГіГЄГ*Г§Г*òåëü Г*Г* ГґГіГ*êöèþ"
        void __cdecl (*pShowSum)(const int A, const int B); pShowSum = NULL;
 
        HMODULE hDLL = LoadLibrary("TestDLL.dll");
        if (!hDLL) {
          ShowMessage("ÍåâîçìîæГ*Г® Г§Г*ãðóçèòü TestDLL.dll");
          return;
        }
 
        // ïûòГ*åìñÿ Г*Г*éòè Гў ГІГ*áëèöå ýêñïîðòГ* Г*åîáõîäèìóþ Г*Г*Г¬ ГґГіГ*êöèþ
        (FARPROC)pShowSum = GetProcAddress(hDLL, "_ShowSum");
 
        if (!pShowSum) {
          ShowMessage("ÍåâîçìîæГ*Г® Г*Г*éòè ГґГіГ*êöèþ ShowSum");
          return;
        }
 
        pShowSum(3,2);
 
        FreeLibrary(hDLL);
P.S. Этот кусок кода был взят из шапки и затем переправлен.
1
Tlya
10 / 10 / 4
Регистрация: 20.11.2015
Сообщений: 252
Завершенные тесты: 3
17.12.2015, 04:29 #4
"ShowSum(3,2);" - нельзя ли конкретне... куда именно это записывать?
0
volvo
Супер-модератор
Эксперт Pascal/DelphiАвтор FAQ
24815 / 16489 / 5088
Регистрация: 22.10.2011
Сообщений: 29,153
Записей в блоге: 5
17.12.2015, 09:48 #5
Чтобы убедится в том, что всё работает, прописываем в конструкторе формы
C++
1
ShowSum(3,2);
Какое из выделенных слов непонятно?
0
Tlya
10 / 10 / 4
Регистрация: 20.11.2015
Сообщений: 252
Завершенные тесты: 3
19.12.2015, 01:41 #6
в какое из мест? (вопрос наверно не из умных, просто я новичок)
0
Миниатюры
Dynamic-Link Library: Теория + Практика  
SatanaXIII
Супер-модератор
Эксперт С++
5668 / 2713 / 255
Регистрация: 01.11.2011
Сообщений: 6,641
Завершенные тесты: 1
22.12.2015, 11:02 #7
Tlya, по правилам языка C++ конструктор класса носит имя этого класса. Форма это класс. Из всего этого следует, что конструктор формы будет иметь название формы - TForm1, номер два в вашем списке.

На всякий случай:
1) Глобальная область видимости (вне функций).
2) Конструктор формы.
3) Метод формы, вызывающийся сразу после завершения работы конструктора.
0
Tlya
10 / 10 / 4
Регистрация: 20.11.2015
Сообщений: 252
Завершенные тесты: 3
22.12.2015, 15:40 #8
Большущее спасибо!
0
22.12.2015, 15:40
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.12.2015, 15:40
Привет! Вот еще темы с ответами:

Dynamic Link Library (.dll) - Delphi
Необходимо создать программу с использованием динамической библиотеки. А именно: создать подпрограммы обработки множеств: объединение,...

The ordinal 459 could not be located in the dynamic link library urlmon.dll - Антивирусы
В общем,я не знал куда засунуть эту тему и написал сюда... Проблема такая... the ordinal 459 could not be located in the dynamic link...

Makefile dynamic library creation error - C++
Добрый день. Создал makefile со следующим содержанием: # Project PROJECT_NAME=StarXml EXECUTABLE=StarXml # Dirs TARGET_DIR=bin...

Проблемы с подключением библиотек: Unknown(): Unable to load dynamic library 'C:PHPextensionsphp_exif.dll' - Не найдена указанная процедура. - PHP
Операционная система: Windows XP Pro Web-сервер: IIS Хочу подключить библиотеку php_exif.dll Она лежит в каталоге C:PHPextensions ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru