(no subject)

Oct 05, 2009 23:34

"Смерека" впевнено наближається до версії 1.0. Вкладаюся в список задач і навіть в графік їх виконання; фактично, сьогодні закінчив останню велику зміну і лишається буквально пара штрихів.

Основною метою версії 1.0, як я докладно писав раніше, є розробка автоматизованої системи тестування GUI та заміна інтерфейсу плагінів на такий, що здатен розширюватися в зворотньо-сумісний спосіб. Обидві задачі містили неабиякий технологічний челендж.

У першому випадку челендж полягав у тому, що досвід тестування графічного інтерфейсу у мене був приблизно нульовий, і мені треба було все починати спочатку. На щастя, пошук підходящого інструменту виявився вдалим, і пакет AutoIt виправдав себе на 95%. Бейсікоподібна мова, звісно, викликАла деякий зубовний скрежет і навіть патологічне звикання, та все ж програмувати під AutoIt можна. Такі просунуті можливості, як здатність працювати з окремими контролками чи викликати WinAPI напряму, виявилися в моєму випадку критичними.

На цю задачу я потратив добрячих півтори сотні годин і залишився задоволеним з того, що у мене вийшло. Скрипти запускаються одним кліком з файлового менеджера, працюють 13 хвилин і за той час тикаються в усі важливі можливості "Смереки", перевіряючи, чи нічого не поламалося. Це виглядає як мультик, у якому на екрані розгортається бурна діяльність, літає миша, вводяться тексти, вискакують вікна та лунають різні звуки. Покрити 100% функціональності не вдалося за браком часу - за бортом наразі опинилися косметичні речі, а також кілька фіч, не пов"язаних напряму з плагінами. Однак, всі важливі можливості тестуються всерйоз: скрипти емулюють дії юзера, після чого перевіряють результат своїх дій, отримуючи через операційну систему текст вікон чи навіть міряючи пікселі.

Важливою властивістю автоматизованих тестів є незалежність як від "Смереки" (я мінімізував "знання" програми про тести, хоч повністю його усунути не зміг), так і від параметрів операційної системи: тести працюють за будь-яких кольорових і текстових схем чи найстройок ефектів Віндовса; іншими словами, тупої прив"язки до якихось захардкоджених пікселів я уникнув. Скажімо, параметри дерева "Смереки" заміряються на початку тестування, отримується висота елементів, горизонтальний зсув відносно рівня, вигляд кнопок "згорнути"/"розгорнути" і таке інше - і лиш тоді, на основі цього, скрипти починають працювати. Це дасть мені можливість швидко і доволі надійно перевіряти функціональність програми на інших платформах - наприклад, на Windows 7, який гряде на зміну ХР. Але, що не менш важливо, автоматизоване тестування дозволило мені безстрашно затіяти широкі зміни - такі, як заміна інтерфейсу плагінів. Можна запустити мультик, зробити й випити каву, повернутися і переконатися, що все й надалі працює як належить.



Друга мета - це заміна інтерфейсів плагінів. Дотепер плагіни прив"язувалися до "Смереки" жорсткими С++-ними інтерфейсами: передавалися об"єкти класів, заголовки яких повинні були бути доступними (разом з приватними членами) обом сторонам. Будь-яка зміна імплементації, не кажучи вже про зміну самих інтерфейсів класів, призводила до перекомпіляції і автоматичного застарівання усіх існуючих плагінів: їх ставало неможливо загружати. Це неприйнятно, тому мені довелося шукати способу визначити інтерфейс плагінів так, щоб еволюція інтерфейсу не призводила до застарівання існуючих плагінів, котрі працюють зі старими інтерфейсами.

На жаль, С++ в цьому плані досить вредна і таких речей напряму не підтримує. У мене було 2 варіанти: або використовувати С-враппери для С++-інтерфейсів (в такому разі завантажувачі динамічних бібліотек можуть в рантаймі встановлювати наявність або відсутність функцій інтерфейсу), або використовувати абстрактні базові класи і функції-фабрики (тут на допомогу приходить RTTI). Спочатку я схилявся до першого варіанту: плагіни експонують досить прості інтерфейси, усіх методів там штук сім чи вісім, можна зробити й сішний інтерфейс. Однак потім до мене швидко дійшло, що інтерфейс експонують не тільки плагіни для "материнської" програми, але й програма для плагінів. Об"єктів та інтерфейсів, які предоставляє "Смерека" плагінам, до дідька: це методи для роботи з базами даних, методи для роботи з іншими плагінами, різноманітні структури для ініціалізації і т.п. Це вже добрячих півсотні функцій, для яких писати C-враппери в стилі "if-then-else" є витончинем мазохізмом.

Тому я пішов другим шляхом: абсолютно всі об"єкти між аплікацією та плагінами ходять у вигляді вказівників на абстрактні базові класи, причому право створювати об"єкти (знаючи їхні статичні типи) має тільки аплікація. Я тепер можу безболісно міняти імплементацію усіх цих об"єктів. Також я можу додавати нові методи в ці інтерфейси, - щоправда, в такому випадку мені доведеться заводити новий інтерфейс і наслідувати його від старого, а клієнту використовувати dynamic_cast і перевіряти, якої це свіжості інтерфейс. Воно не дуже елегантно, і більшість ОО-мов краще справляються з цією задачею, але в даному випадку це працюватиме, що є краще, ніж нічого.

Навіть тривіальні структури з двома-трьома полями тепер передаються у вигляді вказівників на абстрактні класи: це дає можливість при необхідності додавати нові поля, не вбиваючи сумісність зі старими. Можна було реалізувати поля у вигляді get- і set-функцій, але я зробив трохи інакше, емулювавши властивості (properties) за допомогою перевантажених по специфікатору const функцій, які повертають посилання. Таким чином, структура

struct DocumentInitStruct
{
ItemId mParentId;
};

стала інтерфейсом

class IDocumentInitStruct
{

public:

virtual ItemId& ParentId_() = 0;
virtual const ItemId& ParentId_() const = 0;

virtual ~IDocumentInitStruct() {}

};

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

class IDocumentInitStruct
{

public:

DECLARE_PROPERTY(ItemId, ParentId_);

virtual ~IDocumentInitStruct() {}

};

З цього інтерфейсу вже наслідується DocumentInitStruct, сокрита від взгляда плагінів. Плагін отримує вказівник на IDocumentInitStruct і за допомогою псевдовластивості отримує з неї дані:

ItemId parentId = documentInitStruct->ParentId_();

Тепер, коли мені прийде в голову передавати ще одне поле "MoonPhase", мені доведеться зробити новий інтерфейс

class IDocumentInitStruct2 : public IDocumentInitStruct
{

public:

DECLARE_PROPERTY(unsigned short, MoonPhase_);

virtual ~IDocumentInitStruct2() {}

};

а плагінам, яких цікавить ця важлива інформація, робити додатковий танець з бубном:

IDocumentInitStruct2* dis2 = dynamic_cast(documentInitStruct);
if(NULL != dis2)
{
unsigned short moonPhase = dis2->MoonPhase_();
}

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

Я не згадав ще одне важливе обмеження, без якого ця вся радість не працюватиме: плагіни повинні компілюватися виключно компілятором Microsoft Visual Studio Express версії 8 або вище і виключно з тими настройками, які я виставлю в SDK. Інакше бінарної сумісності між плагінами і основною аплікацією не буде взагалі ніякої. Адже бінарний інтерфейс С++ не стандартизує, і кожен компілятор винаходить свій власний ровер. Це сумний і прикрий факт, за С++ який копають всі, кому не лінь, але я тут не бачу великих проблем, зважаючи на безкшотовність цього компілятора. Крім того, компілятор - це просто компілятор, нелюбителі "студії" можуть прикрутити його до будь-якого середовища. Незалежно від архітектури моїх плагінів це обмеження лишалося б в силі, адже до GUI-бібліотеки wxWidgets плагіни так чи інакше повинні лінкуватися "напряму" через __declspec(dllimport), - з цим я уже нічого не пороблю.

Лишається наступне:

(1) Зробити plugin SDK, в якому була би документація, порожній проект плагіна і плагін-семпл, які сходу білдяться і запускаються;
(2) Прикинути, чи варто переходити на STLPort (адже нові версії Microsoft Visual Studio можуть внести зміни в STL, що зробить бінарники несумісними з бінарниками, скомпільованими старішою версією);
(3) Спробувати збілдити все з нуля на віртуальній машині;
(4) Зарелізитися і почати маркетинг нової версії програми.

teh_codez

Previous post Next post
Up