DynLib: библиотека для создания и работы с DLL
Библиотека DynLib предоставляет удобные средства для разработчиков, использующих межмодульное взаимодействие (EXE<->DLL, DLL<->DLL) в своих проектах, и значительно сокращает время и количество кода. DynLib стала неотъемлемым инструментом разработки. Под катом делимся результатами.
Недостатки традиционного подхода к реализации DLL- отсутствие возможности использовать пространства имен
- большое количество служебного кода, необходимого:
- при реализации динамической загрузки библиотек;
- при реализации межмодульного взаимодействия через классы, за счет использования декскрипторов (или иных неявных структур) и классов-оберток;
- при реализации механизмов возвращения ошибки, в случае, когда экспортируемые функции могут генерировать исключения.
Задача. Динамически подключить и использовать библиотеку test_lib.dll, реализующую простые математические операции, с интерфейсом, представленным в заголовочном файле: Решение. Необходимо написать следующий заголовочный файл и подключить его к проекту. Препроцессор сгенерирует класс test::lib, выполняющий динамическую загрузку DLL и содержащий перечисленные функции sum, mul и epsilon. Для подключения DLL к приложению необходимо включить представленный заголовочный файл test_lib.hpp в исходный код. Далее следует создать объект класса test::lib. Доступ к экспортируемых функциям DLL возможен через '.' или '->'.
2. Создание библиотеки calculator.dllЗадача. Написать библиотеку calculator.dll, которая должна вычислять сумму, произведение двух чисел и значение квадратного корня. Динамически загрузить библиотеку и вызвать каждую функцию. Решение
4. Реализация библиотеки shapes.dll. Использование интерфейсов.Задача. Создать библиотеку shapes.dll по работе с геометрическими фигурами (квадрат, прямоугольник, круг). Все фигуры должны поддерживать общий интерфейс, через который можно узнать координаты центра фигуры. Решение
Как подключить библиотекуБиблиотека поставляется в виде заголовочных файлов. Никаких .lib и .dll не требуется. Для подключения требуется добавить следующую директиву:
Элементы библиотекиМногие классы и макросы библиотеки DynLib могут использоваться самостоятельно и отдельно друг от друга.
DL_BLOCKСлужит контейнером для всех остальных макросов.
DL_NS_BLOCKСлужит контейнером для всех остальных макросов. Создает пространства имен для класса. Макросы, которые описаны ниже кроме DL_EXPORT, должны быть помещены в DL_BLOCK или DL_NS_BLOCK
DL_C_LIBRARYНазначение макроса — предоставить пользователю готовый класс, реализующий динамическую загрузку DLL и автоматический импорт функций. Макрос представлен как:
- lib_class — имя класса, реализацию которого генерирует библиотека DynLib;
- functions — перечисление функций, экспортируемых DLL. задается через список следующего формата (ret_type, call, (name, import_name), arguments)
- ret_type — тип возвращаемого функцией значения;
- call — формат вызова, например: __sdtcall, __cdecl и т.п.;
- name — имя функции (для пользователя);
- import_name — имя функции, заданной в таблице экспорта DLL, включая декорацию (если она есть). Если name и import_name совпадают, то import_name можно не указывать.
- arguments — список (тип аргумента, имя аргумента, = значение по умолчанию), задающий входные аргументы. Имя аргумента и значение по умолчанию можно не указывать.;
Макрос DL_RECORD генерирует упакованную структуру данных для использования в межмодульном взаимодействии. Дополнительно создается конструктор со всеми перечисленными в макросе аргументами.
DL_LIBRARY- выступает в роли описания (документирования) интерфейса между EXE(DLL) и DLL;
- содержит необходимые структуры для автоматического экспорта функций библиотеки для разработчика;
- реализует класс, обеспечивающий загрузку DLL с заданным интерфейсом и предоставляющий доступ к экспортируемым функциям со стороны пользователя;
- обеспечивает корректное использование C++ исключений:
- lib_class — полное имя класса, описывающего интерфейс взаимодействия (то имя класса, что использовалось в DL_LIBRARY);
- lib_impl_class — полное имя класса класса, РЕАЛИЗУЮЩЕГО функции, указанные в интерфейсе взаимодействия.
- Создать класс (структуру);
- Определить каждую функцию из интерфейса как статическую. Функции должны находиться в области видимости public:;
- Произвести экспорт функций, написав конструкцию DL_EXPORT(lib, impl).
Макрос позволяет описать интерфейс класса и предоставить пользователю класс-обертку для работы с ним. Реализация класса-обертки обеспечивает корректное использование C++ исключений: Класс-обертка, генерируемая данным макросом, имеет разделяемое владение объектом, реализующего данный интерфейс. Разделяемое владение обеспечивается механизмом подсчета ссылок, т.е. когда происходит копирование объектов класса-обертки, вызывается внутренняя функция для увеличения счетчика ссылок, при уничтожении — внутренняя функция по уменьшению счетчика ссылок. При достижении счетчиком значения 0 происходит автоматическое удаление объекта. Доступ к методам интерфейса осуществляется через '.' или '->'. Библиотека DynLib гарантирует безопасное использование классов-интерфейсов на границе EXE(DLL)<->DLL
- interface_class — имя класса, реализацию которого генерирует библиотека DynLib;
- methods — перечисление функций, описывающих интерфейс класса,
- динамическое создание объекта класса T с аргументами, переданными в конструкторе;
- добавление счетчика ссылок и обеспечение разделяемого владения (подобно boost(std)::shared_ptr);
- неявное приведение к объекту класса, генерируемого макросом DL_INTERFACE.
Примеры использования dl::shared представлены ниже:
dl::refФункция библиотеки, позволяющая привести любой объект к объекту класса-интерфейса, объявленному через DL_INTERFACE, с идентичным набором методов. Обычно такое поведение необходимо, когда имеется функция, принимающая в качестве аргумента класс-интерфейс, а ему следует передать объект, размещенный в стеке. Использовать функцию dl::ref нужно с осторожностью, поскольку объекты классов-интерфейсов, в этом случае не будут владеть переданными объектами, а управление временем жизни объекта и его использованием через классы-интерфейсы ложится на пользователя. Копирование объектов классов-интерфейсов, ссылающих на объекты, переданные через dl::ref, разрешено и вполне корректно (поскольку счетчика ссылок нет, то и изменять нечего — объекты классы-интерфейсов знают как здесь корректно работать).