Размер
DynLib: библиотека для создания и работы с DLL
Play

DynLib: библиотека для создания и работы с DLL

Библиотека DynLib предоставляет удобные средства для разработчиков, использующих межмодульное взаимодействие (EXE<->DLL, DLL<->DLL) в своих проектах, и значительно сокращает время и количество кода. DynLib стала неотъемлемым инструментом разработки. Под катом делимся результатами.

Недостатки традиционного подхода к реализации DLL
  1. отсутствие возможности использовать пространства имен
  2. большое количество служебного кода, необходимого:
    • при реализации динамической загрузки библиотек;
    • при реализации межмодульного взаимодействия через классы, за счет использования декскрипторов (или иных неявных структур) и классов-оберток;
    • при реализации механизмов возвращения ошибки, в случае, когда экспортируемые функции могут генерировать исключения.
Примеры использования DynLib 1. Использование обычной 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_RECORD генерирует упакованную структуру данных для использования в межмодульном взаимодействии. Дополнительно создается конструктор со всеми перечисленными в макросе аргументами.

    DL_LIBRARY
    1. выступает в роли описания (документирования) интерфейса между EXE(DLL) и DLL;
    2. содержит необходимые структуры для автоматического экспорта функций библиотеки для разработчика;
    3. реализует класс, обеспечивающий загрузку DLL с заданным интерфейсом и предоставляющий доступ к экспортируемым функциям со стороны пользователя;
    4. обеспечивает корректное использование C++ исключений:
    DL_EXPORT
    • lib_class — полное имя класса, описывающего интерфейс взаимодействия (то имя класса, что использовалось в DL_LIBRARY);
    • lib_impl_class — полное имя класса класса, РЕАЛИЗУЮЩЕГО функции, указанные в интерфейсе взаимодействия.
    1. Создать класс (структуру);
    2. Определить каждую функцию из интерфейса как статическую. Функции должны находиться в области видимости public:;
    3. Произвести экспорт функций, написав конструкцию DL_EXPORT(lib, impl).
    DL_INTERFACE

    Макрос позволяет описать интерфейс класса и предоставить пользователю класс-обертку для работы с ним. Реализация класса-обертки обеспечивает корректное использование C++ исключений: Класс-обертка, генерируемая данным макросом, имеет разделяемое владение объектом, реализующего данный интерфейс. Разделяемое владение обеспечивается механизмом подсчета ссылок, т.е. когда происходит копирование объектов класса-обертки, вызывается внутренняя функция для увеличения счетчика ссылок, при уничтожении — внутренняя функция по уменьшению счетчика ссылок. При достижении счетчиком значения 0 происходит автоматическое удаление объекта. Доступ к методам интерфейса осуществляется через '.' или '->'. Библиотека DynLib гарантирует безопасное использование классов-интерфейсов на границе EXE(DLL)<->DLL

    • interface_class — имя класса, реализацию которого генерирует библиотека DynLib;
    • methods — перечисление функций, описывающих интерфейс класса,
    dl::shared
    1. динамическое создание объекта класса T с аргументами, переданными в конструкторе;
    2. добавление счетчика ссылок и обеспечение разделяемого владения (подобно boost(std)::shared_ptr);
    3. неявное приведение к объекту класса, генерируемого макросом DL_INTERFACE.

    Примеры использования dl::shared представлены ниже:

    dl::ref

    Функция библиотеки, позволяющая привести любой объект к объекту класса-интерфейса, объявленному через DL_INTERFACE, с идентичным набором методов. Обычно такое поведение необходимо, когда имеется функция, принимающая в качестве аргумента класс-интерфейс, а ему следует передать объект, размещенный в стеке. Использовать функцию dl::ref нужно с осторожностью, поскольку объекты классов-интерфейсов, в этом случае не будут владеть переданными объектами, а управление временем жизни объекта и его использованием через классы-интерфейсы ложится на пользователя. Копирование объектов классов-интерфейсов, ссылающих на объекты, переданные через dl::ref, разрешено и вполне корректно (поскольку счетчика ссылок нет, то и изменять нечего — объекты классы-интерфейсов знают как здесь корректно работать).