Знайомство з PDL (Portable Dynamic Loader) (исходники), Різне, Програмування, статті

Що таке PDL

PDL (portable dynamic loader) – це легка, проста і портабельная бібліотека, призначена для створення і використання динамічно завантажуваних об’єктів класів.

Для чого ж потрібна динамічне завантаження класів

Основне застосування цієї технології – це розробка динамічно підключаються плагінів, що розширюють функціональність основної програми. Головна проблема полягає в тому, що динамічно завантажувані бібліотеки на багатьох платформах якісно підтримують тільки процедурний стиль програмування, а із завантаженням і використанням екземплярів класів виникають різноманітні проблеми. Більшість цих проблем (але, на жаль, поки не все) і вирішує бібліотека pdl.

На платформі win32 pdl є сильно спрощеною альтернативою використанню технології com, без лічильника посилань, без глобальної реєстрації класів і безлічі інших можливостей. Для unix / linux платформ існують аналогічні бібліотеки, наприклад, С + + dynamic class loader. Підтримка динамічного завантаження класів присутня і у великій крос-платформної бібліотеці wxwidgets.

Метою розробки pdl було створення саме кроссплатформенной бібліотеки, яка дозволила б використовувати один і той же код завантаження класів для win32 і unix / linux платформ (на відміну від com і c + + dynamic class loader). У той же час, бібліотека повинна була бути максимально незалежною і легковагої (тому була забракована wxwidgets).

Створення динамічно завантажуваного класу

Отже, розглянемо детально процес створення динамічно завантажуваного класу з використанням бібліотеки pdl. Перш за все, слід визначити інтерфейс класу, через який ми будемо працювати з примірником завантаженого класу. Обов’язкова умова: цей інтерфейс повинен успадковуватися від класу pdl :: dynamicclass. Давайте уважніше придивимося до визначення dynamicclass:

Код
сlass dynamicclass

public:
    /**
     * @brief get class name
     * return class name
     */
    virtual const char * getclassname() const throw() = 0;
    
    /**
     * @brief destroy class instance
     */
    void destroy() throw() { delete this; }

protected:
    /**
     * @brief destructor
     */
    virtual ~dynamicclass() throw() {;; }
};

Чисто віртуальна функція getclassname () повертає ім’я класу. Вам не потрібно турбуватися про її визначення, трохи нижче я поясню чому.

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

Отже, наш інтерфейс ми повинні успадкувати від pdl :: dynamicclass і визначити в ньому віртуальний деструктор, бажано в секції protected (щоб не порушувати ідеологію pdl).

Крім того, в оголошенні інтерфейсу обов’язково потрібно написати макрос declare_dynamic_class, передавши йому як параметр ім’я класу. Якщо подивитися на опис цього макросу, стає очевидно його призначення: він просто визначає віртуальну функцію getclassname ():

Код
#define declare_dynamic_class( classname )
public:
    virtual const char * getclassname() const throw() { return #classname; }


Ну і наостанок, додамо в опис інтерфейсу оголошення чисто віртуальних методів, що реалізують корисну функціональність майбутніх динамічно завантажуваних класів. Нехай у нашому випадку це буде метод void dosomething().


В результаті ми маємо наступне оголошення інтерфейсу:

Код
#include

сlass mytestinterface: public pdl :: dynamicclass
{
public:
    /**
     * @brief test method
     */
    virtual void dosomething() throw() = 0;

    /**
     * @brief declare this class dynamically loadable
     */
    declare_dynamic_class( mytestinterface )
};

Це оголошення слід помістити в окремий заголовний файл, в нашому випадку – mytestinterface.hpp, тому як для забезпечення сумісності включатися він буде і при складанні динамічно завантажуваного класу, і при його безпосередній завантаженні і використанні.

Далі слід визначити сам клас, успадкувавши його від абстрактного інтерфейсу mytestinterface і визначивши методи, що реалізують корисну функціональність. Крім того, клас потрібно експортувати за допомогою макросу export_dynamic_class. Зверніть увагу, що цей макрос повинен знаходитися поза визначення класу:

Код
#include
#include

class mytestclass1 : public mytestinterface
{
public:
    /**
     * @brief test method
     */
    void dosomething() throw()
    {
        fprintf( stderr, “mytestclass1::dosomething()
” );
    }
};

export_dynamic_class( mytestclass1 )

Погляньмо на визначення макросу export_dynamic_class (файл dynamicclass.hpp):

Код
#define export_dynamic_class( classname )
extern “c” pdl_decl_export pdl::dynamicclass * create##classname()
{
    try { return new classname(); }
    catch( … ) {;; }
    return null;
}

Цей макрос оголошує і визначає експортовану функцію create <ім'я_класу>, яка створює і повертає екземпляр зазначеного в параметрі класу. Модифікатор extern “c” потрібен для того, щоб компілятор НЕ декорував ім’я функції в бібліотеці. Макрос pdl_decl_export оголошує функцію експортується. Він визначений у файлі platform. H і його реалізація залежить від конкретної платформи.

Слід звернути увагу на дві речі. По-перше, в експортованої функції ловляться всі виключення конструктора. У випадку, якщо виклик конструктора аварійно завершився викидом виключення, функція просто поверне null. Це пов’язано зі складнощами, що виникають при спробі обробити в основному коді виключення, що викидається кодом плагина. По-друге, експортована функція повертає покажчик на pdl :: dynamicclass, до якого неявно перетворюється створений об’єкт класу. Таким чином, якщо ми забудемо успадкувати інтерфейс створюваного класу від pdl :: dynamicclass, компілятор нагадає нам про це.

Після того, як клас створений, ми можемо скомпілювати плагін. Слід зазначити, що один плагін може містити кілька різних динамічно завантажуваних класів, головна умова: вони повинні мати унікальні в рамках цього плагіна імена. Не слід забувати, що експортувати за допомогою макросу export_dynamic_class потрібно кожен з класів.

Використання динамічно завантажуваних класів

Отже, у нас є бібліотека-плагин, що містить динамічно завантажуваний клас. Тепер спробуємо використовувати цей клас.

Для початку нам потрібно інстанція динамічного завантажувача класів – pdl :: dynamicloader. Цей клас є Сінглтон, тобто існує завжди в єдиному екземплярі. Для отримання посилання на цей екземпляр використовуємо статичний метод dynamicloader :: instance ():

Код
pdl::dynamicloader & dynamicloader = pdl::dynamicloader::instance();

Далі, нам потрібно завантажити інстанцію класу і отримати покажчик на неї:

Код
mytestinterface * instance =
    dynamicloader.getclassinstance< mytestinterface >( mylibname, “mytestclass1” );

Тут mylibname – це ім’я бібліотеки-плагіна, наприклад “mytestclass1.dll” або “mytestclass.so”. Не забуваємо підключити заголовний файл з визначенням інтерфейсу класу – в нашому випадку це mytestinterface.hpp, згаданий вище.

Нарешті, викликаємо метод завантаженого класу:

Код
instance -> dosomething();

Так як динамічний завантажувач у разі помилки викидає виключення типу pdl :: loaderexception, буде правильно обернути його виклики в блок try / catch. Повний код прикладу буде виглядати так:

Код
#include
#include

try
{
    pdl::dynamicloader & dynamicloader = pdl::dynamicloader::instance();
    mytestinterface * instance =
        dynamicloader.getclassinstance< mytestinterface >( mylibname, “mytestclass1” );
    instance -> dosomething();
}
catch( pdl::loaderexception & ex )
{
    fprintf( stderr, “loader exception: %s
“, ex.what() );
}

Слід відзначити ще один цікавий момент: всі динамічно завантажені класи поводяться як Сінглтон. Іншими словами, при повторному виклику dynamicloader :: getinstance () з тим же ім’ям бібліотеки і з тим же ім’ям класу буде повернений покажчик на вже завантажений екземпляр. Це зроблено для полегшення контролю видаленням інстанцій динамічно завантажуваних класів. Якщо буде потрібно створювати безліч інстанцій класів, рекомендується реалізувати фабрику класів і зробити її динамічно завантажується.

А як же віддаляються інстанції завантажених класів? Для цього і існує метод dynamicclass :: destroy (). Викликати безпосередньо його не потрібно – це робить сам завантажувач в своєму деструкції, або при виклику методу dynamicloader :: reset (). Чому ж не викликається звичайний деструктор? Справа в тому, що деякі тонкощі механізму виділення динамічної пам’яті міняються від компілятора до компілятору. Уявімо собі, що плагін зібраний компілятором a, а основна програма, яка використовує його – компілятором b. Якщо ми завантажуємо клас з плагина, його інстанція створюється кодом, згенерованим компілятором a. І якщо ми спробуємо видалити цю інстанцію з коду основної програми просто викликавши деструктор, то видалити її спробує код, що згенерував компіляторів b. І тут можливі казуси.

Для того, щоб уникнути подібних проблем, деструктор ~ dynamicclass () зроблений захищеним, а замість нього слід викликати метод dynamicclass :: destroy (). Це метод буде гарантує, що код деструктора класу скомпільований тим же компілятором, що і код його конструктора.

Ложка дьогтю

Є один тонкий момент, пов’язаний з іменами бібліотек. Якщо ім’я бібліотеки міняється, то вважається, що це вже зовсім інша бібліотека, хоча по-різному записані імена можуть вказувати на одну і ту ж бібліотеку. Наприклад: c: myproglibsmylib.dll і mylib.dll.

Слід зазначити також, що бібліотека pdl не вирішує проблеми з різним декоруванням імен методів різними компіляторами – таке завдання зараз не ставиться.

На даний момент бібліотека відтестували на платформах:



Я буду вдячний за будь-яку інформацію щодо роботи PDL на інших платформах.


Подяки
Велике спасибі Володимиру Сторожових (Влад) і Олександру Леденеву (shunix), завдяки чиїй критиці ця стаття стала (я сподіваюся) краще.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*