Використання dll в якості plugin-ів


Використання DLL в якості PlugIn-ів

Використання DLL в якості PlugIn-ів

У темах для написання статей розділу "Hello World" присутній питання про динамічні бібліотеках і модулі ShareMem. Я хотів би дещо розширити постановку питання: Нехай нам треба побудувати систему безболісно расширяемую функціонально. Напрошується само собою рішення - бібліотеки динамічного компонування. І які ж грабельки розкидані на цій стежці?

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

Чи використовувати ShareMem - питання конкретної постановки задачі. Якщо можна обійтися без таких виділень пам'яті, то вперед, з піснею! Інакше доведеться разом з програмою тягати borlndmm.dll, яка і реалізує безболісний обмін покажчиками між бібліотеками.

Можна задатися питанням "А чому?". І отримати відповідь "Так треба!". По всій видимості, Delphi працює з Heap (купою, звідки виділяється пам'ять) по-своєму. Деякий час тому ми на роботі обговорювали це питання, повзали по ісходникам і до єдиної думки так і не прийшли. Але є припущення, що Delphi виділяє відразу великий шматок пам'яті в купі і вже потім за запитом відрізає від нього необхідні шматочки, тим самим не довіряючи системі виділяти пам'ять. Можливо, це не так і якщо хто підправить мене, буду вдячний. Так чи інакше - проблема існує, і рішення є.

Збіг властивостей Name різних вікон буде викликати виняткову ситуацію. Уникнути цього не складно, а виникає помилка мабуть через те, що різні типи класів мають одне ім'я в межах одного контейнера.

Досить важливим є знищення вікна перед вивантаженням бібліотеки і завершенням програми. Delphi розслабляє: за виділеними ресурсами стежити не треба, вікна самі створюються і знищуються і ще багато чого робиться за програміста. Накидав компонентіков, встановив зв'язки і все готово. Уявімо: бібліотека вивантажено, вікно з бібліотеки існує, система за бібліотекою вже почистила дескриптори, так інші ресурсікі і що виходить? Секунд п'ять Delphi при закритті програми висить, а потім "Access violation." Далі вирізано цензурою.

Більше граблів помічено не було. Та й згадані - серйозної проблеми не представляють, єдине, що потрібно, писАть акуратно, текст вилизувати, та й думати частіше.

Побудова програми з Plug In-ами

Можливо 2 підходи до побудови такої програми
  • плагіни отримують інформацію від ядра програми, але самі до ядра не звертаються. Назвемо такий підхід пасивним.
  • в активному підході плагіни ініціюють деякі події і змушують ядро ​​їх слухатися.

Другий підхід вимагає більш складного алгоритмічного побудови, тому я розгляну тільки перший.

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

В процесі роботи з'ясувалося, що для пасивної моделі досить 6 функцій:
  1. Отримання внутрішньої інформації про плагін (в програмі function GetModuleInfo: TModuleInfo). При наявності в бібліотеці такої функції і правильному її виклику, ми будемо знати що ця DLL - наш плагін. Сама функція може повертати що завгодно, наприклад назву і тип плагіна.
  2. Формування початкових значень (в програмі procedure Initialize). Плагін опоряджається після завантаження, тобто заповнює змінні значеннями за замовчуванням.
  3. Передача даних в плагін (в програмі procedure SetData (Kind: TDataKind; const Buffer; Size: Integer)). Дозволяє передавати дані в плагін.
  4. Отримання даних - в програмі не реалізована, але робиться за типом SetData.
  5. Запуск плагіна (в програмі Run). Запускається плагін. Дії можуть бути різними: показ вікна, модальний показ вікна, розрахунок якого-небудь параметра і т.д.
  6. І есесьно останов плагіна. Тут дії зворотні пункту 2.

Трохи зупинюся на передачу даних. Паскаль при всій своїй жорсткій типізації надає приємне засіб передачі в функцію нетипізований даних. Якщо програма знає про те, які саме дані прийшли, оттіпізіровать :) їх досить просто. Цей спосіб передачі використовується в SetData. У модулі SharedTypes.Pas, використовуваному усіма трьома проектами описані відповідні константи TDataKind для типів переданих даних.

Тепер про реалізацію

Нехай ядро, тобто exe-файл, шукає плагіни, запускає їх і по таймеру передає в них два цифрових значення, які один плагін буде зображувати в текстовому вигляді, а другий у вигляді діаграм. Реалізація плагінів відрізняється мінімально, тому розповім про одне - Digital.dll. Почнемо перерахування функцій:

Функція повертає інформацію про модуль. В даному випадку це цифрове відображення, шлях і тип модуля.

Процедура запам'ятовує змінну Application і робить нульовий посилання на форму плагіна.

Процедура запуску плагіна створює вікно. Вікно створюється видимим.

Процедура знищує вікно і повертає старий TApplication.

Процедура отримання даних. Залежно від отриманого типу даних з дані в змінної Buffer відповідно типізуються. Тут відбувається звернення до формі плагіна, розписувати я його не буду, там все просто, см. Исходники. Типи, які використовуються тут, описані в SharedTypes.pas

За плагинам це все.

Як видно з тексту, це проста надбудова над плагіном, що не додає функціональності, але що дозволяє зберігати все в одному об'єкті.

Тепер залишилося тільки зібрати плагіни і запустити. Збір інформації та запуск відбувається після натискання однойменної кнопки на головній формі. Як збирати плагіни - справа смаку. У цьому прикладі я сканують заданий каталог, можна зберігати в INI-файлі, реєстрі, можна придумати свій формат зберігання. Збір плагінів:

Бажано не забувати про звільнення модулів. Це вже в самому кінці (див. Вихідні тексти).

І ось як це все виглядає.

Використання dll в якості plugin-ів

У цьому тексті я привів кістяк можливого рішення побудови модульної системи. Питання, наїзди і побажання приймаються.

Максим Мазитов
Спеціально для Королівства Delphi