Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Для чего и как я писал BOSS’а и что из этого получилось. Кроссплатформенная система плагинов на C 11

Anna | 24.06.2014 | нет комментариев

Несколько раз я теснее ссылался на свой пост о кроссплатформенной системе плагинов на C 11 [1]. Данный пост появился из мечты испробовать некоторые новшества эталона C 11. На чем пробовать — длинно не думал. У меня теснее был пост о кроссплатформенной компонентной модели [2] на C 03 и он возник из небольшого ранее разрабатываемого плана.


С возникновением gcc 4.7.2 мне предоставилась вероятность поэкспериментировать вдоволь с C 11. Немножко позже решил довести материал до логичного заключения: сделать код больше завершенным, немножко написать документации и сделать маленький источник [3], где все это дозволено было бы поместить.

Что и как было реализовано и отчего принято то либо иное решение было расписано в предыдущих постах. Этим же постом мне хотелось рассказать не о всевозможных специализациях образцов и прочих прелестях C , а о итогах: с чем пришлось столкнуться при желании поддержать несколько платформ (Windows, Linux, FreeBSD) и при желании сделать сборку различными компиляторами, а так же о планах становления.

Содержание:

Появление идеи


Где-то в 2001-м году мне в руки попали две книги: «Язык программирования С » (Бьерн Страуструп, 3-е издание) и «Модель COM и использование ATL 3.0» (Трельсен, 2001 г.). В то время я начинал программировать на C . Книга Трельсена мне дюже понравилась. В ней было отлично описана работа с MS COM, а так же описаны основные тезисы программирования на основе интерфейсов. Поработав некоторое время с MS COM, я со временем перешел к программированию под Linux/Unix и к кроссплатформенной разработке. Если же говорить об навыке, то дозволено сказать, что огромную его часть составляет именно кроссплатформенная разработка с написанием собственных всевозможных оберток, скажем, с использованием pImpl, а так же применение различных кроссплатформенных библиотек.

При разработке кроссплатформенного ПО мне стало не хватать некоторой компонентной модели. MS COM — это Windows ориентированная разработка. А как же кроссплатформенная? Захотелось сделать что-то свое, кроссплатформенное, основанное на тех же конструкциях с чисто виртуальными функциями, как-то декомпозировать систему с разбиением на модули, а в последствии и организовать взаимодействие процессов, прокинув через их границы те же интерфейсы, и в то же время иметь вероятность получить как дозволено больше тонкую прослойку для создания компонент. Хотелось минимизировать работу по построению компонент так, Дабы на их построение расходовалось минимум усилий, а огромную часть времени дозволено было потратить на разработку логики, которая должна была помещаться в эти самые компоненты / плагины. Не страдать комплексом Наполеона, т.е. не усердствовать из маленький компонентной модели сделать всеохватывающий framework, тот, что бы помимо стержневой работы по созданию компонент еще делал бы все, что только в голову взбредет. Для этого есть иные библиотеки: boost, Qt, wxWidgets, poco и т.д. Этого не неизменно легко добиться. Неизменно есть желание добавить чего-то еще на случай «может сгодиться». Этим грешат многие, я не исключение. С этим желанием борюсь. Помимо легкости самой прослойки хотелось сделать и как дозволено больше короткий путь по созданию компонент, чураясь трудных обрядов, которые нужно совершить, чтоб хоть что-то предисловие трудиться.

Поработав в нескольких больших и не дюже компаниях подметил, что мое желание сделать сходственное не первое. Во многих компаниях есть свои «компонентные корпоративные крупныframework’и». В определенный момент я написал свой маленький компонентный движок с видами на кроссплатформенность, и с реализацией только под Windows. Для Linux части были заглушки. На нем же разрабатывался один из маленьких планов. Эта версия не была удачна хоть как-то. Через некоторое время я написал теснее больше продвинутую версию компонентной модели с полной помощью кроссплатформенности. Учел задачи первой версии и с чистого листа приступил к 2-й. Движок параллельно прогрессировал под Windows и Linux. И на его основе прогрессировал один из планов. До сих пор где-то в беспредельном пространстве Интернета его дозволено обнаружить. Эта версия была больше удачна. Она и взята за основу плана «BOSS», как идея и как некоторые опробованные ранее тезисы. Так же была сделана некоторая выжимка, как материал для поста на Прогре [2].

Некоторая увлеченность образцами и прочтением трудов Александреску дали основу для реализации тезисов минимализма в разрабатываемой компонентной модели. Списки типов в жанре Александреску вдалеке не всеми разработчиками понимаются позитивно. А вот возникновение эталона C 11 дало вероятность реализовать желаемое теснее на variadic templates. К тому же эталон C 11 дал и такую вещь как decltype, что дало вероятность в духе минимализма создавать Proxy/Stub для организации маршалинга интерфейсов между процессами. В целом C 11 дал мне много пригодных инструментов.

Получив определенный навык сходственной разработки для маленьких планов, посмотрев как это делают другие, решил сделать свой компонентный движок со своими особенностями и довести его до продуктового вида. Изредка в «личку» приходили вопросы как с поддержкой предлагаемой модели что-то реализовать. Были и вопросы о планах становления плана и поддержке.

Некогда я все же решил довести план до продуктового вида: довести код до продуктового вида, написать документацию, написать примеры, сделать сайт плана и т. д. Т.е. довести правда бы до минимальной точки завершенности, когда этим теснее дозволено воспользоваться, а не только прочитать как о тоскливой теории. Возник продукт «BOSS». Дозволено сказать, что его зачатки идей были на рубеже 2000-2001 годов, первая реализация была в 2007 году, в 2009 году возникла вторая версия и план на ее основе. Теперь в 2014 году «материалы прошлого» приобрели рамки плана хоть и на одном энтузиазме и моем интересе развиваемые.

О наименовании плана


Как-то привык раскладывать код по пространствам имен. Попытка подобрать что-то благозвучное и легко произносимое в качестве имени пространства имен стремительно обнаружила решение. Вертя в голове что-то типа: COM, std, stl, ATL, boost, component, model, service, plugin, model и т.д. сложился пазл: придумано немножко шутливое наименование BOSS– Base Objects for Service Solutions.

BOSS — это некоторая сокращение, не имеющая никакого отношения к начальникам (правда такая ассоциация в комментариях ранее была). Если все же есть упрямая ассоциация BOSS’а с руководством, то давайте его воспринимать в наилучших традициях западного подхода начальник-слуга. О таком подходе к управлению дозволено почитать в книге «Первенство: к вершинам триумфа» (Кен Бланшар, 2011 г.) либо «От результативности к величию» (Стивен Кови, 2004 г.). К сожалению, среди начальников есть и поклонники Макиавелли (дозволено прочесть на эту тему книги: «Правитель» (Никколо Макиавелли, 1532 г.) и «Администратор мафии. Начальство для корпоративного Макиавелли» (V., 2010 г.) ). Это для некоторых может стать поводом упрямой ассоциации того, что BOSS — толстый, гневный дядька с палкой, стоящий за спиной работника. Но все же давайте развеем эту отрицательную ассоциацию в пользу больше высокоэффективной и положительной!

И если от ассоциации того, что BOSS — это начальник, отделаться не получается, то предлагаю рассматривать его со стороны начальник-слуга. В рамках данной компонентной модели BOSS как раз будет являться слугой: некоторой тонкой прослойкой между работниками (в свете рассматриваемой модели «компонентами»), налаживая между ними коммуникации для достижения максимального итога решения бизнес задач (логики, располагаемой в компонентах). А что как не коммуникации дозволено еще отнести к одной из ключевых обязанностей руководителя…

Для чего необходима компонентизация


При разработке планов на C в определенный момент возникает желание, а изредка и надобность декомпозиции системы на отдельные компоненты с разнесением их по отдельным модулям. Разрабатываемая система может от небольшого приложения с одним исполняемым модулем подрасти до трудной многомодульной системы. Допустим и вариант первоначальной ориентации на большой программный комплекс. В такие моменты появляется вопрос: «Что применять для декомпозиции системы на компонентном ярусе и как организовать взаимодействие компонент?».

Основная идея компонентной модели BOSS — дать вероятность декомпозировать систему на компоненты с минимальными расходами на обеспечение взаимодействия между компонентами.

Разработка модели BOSS была первоначально ориентирована на олицетворение доктрины «Минимализм во каждому, где это только допустимо». Минимально нужная реализация, минимальное число шагов для создания и применения компонент.

BOSS не пытается предоставить всевозможные вспомогательные сервисы, которые могли бы быть пригодны. Для этого есть иные библиотеки. BOSS — это вероятность наладить коммуникации между частями программного комплекса и ничего больше.

Помощь различных ОС и компиляторов


При применении одной и той же ОС, одного и того же компилятора и к тому же не меняя версии, разработка становится все стремительней и стремительней, т.к. становятся вестимы особенности используемых продуктов, их недочеты, а так же становятся вестимы и их «плюшки», которыми они прикармливают. Разработка становится стремительней, а в долгосрочной перспективе ведет к задачам.

При разработке кроссплатформенного кода работа немножко усложняется. Нужно как-то реализовывать одно и то же на различных платформах. Благо здесь имеются кроссплатформенные библиотеки. Не неизменно они могут применяться в том либо другом плане, как по техническим, так и по «политическим» причинам. Здесь вспоминается pImpl и прочие идиомы. Так же нужно поддерживать в адекватном состоянии ветки для всякой из платформ. Бывают моменты, что кажется код не содержит ничего зависящего от платформы, должно собираться и на других платформах. Обнаружив временные источники, делается попытка собрать на иной платформе и оказывается, что это не так. Не собирается с первого раза. Допустимо в одной из ОС некоторые функции помещены в одной библиотеке, а в иной вовсе другой (скажем, при применении функции dlopen для Linux компоновка должна быть с -ldl, а для FreeBSD это расположено в libc; казалось бы один и тот же интерфейс). Эти мелочи стремительно правятся. Появляются и схожие, которые так же не вызывают крупных сложностей.

Еще одна задача — это при сборке под различные операционные системы доводится пользоваться изредка компиляторами от различных изготовителей. И здесь начинается самое неприятное: язык C он как бы один и имеет эталон, а компиляторы различные и всякий из них немножко по своему трактует эталон. В целом если же писать как дозволено примитивнее, применяя как дозволено меньшее подмножество языковых конструкций и минимум из стандартной библиотеки, то код с огромный вероятностью соберется на различных компиляторах примерно с первого раза. Но это все равно, что быть эмигрантом в англоговорящей стране с резервом слов в три с половиной сотни. Жить дозволено, но полновесно общаться проблемно. Хочется-то большего. При желании получить огромнее начинаются искания пересечений множеств поддерживаемых вероятностей в используемых компиляторах.

Эталон C 11 много обговаривали. Ожидали поддержки той либо другой фичи в следующий версии компилятора и пробовали ее как-то применять. Данный путь могли пройти многие увлеченные C программисты. Для себя я обнаружил приемлемый вариант в gcc 4.7.2, тот, что покрывал мои надобности реализации C 11 для написания замышленного. Либо правда бы 4.7. На данный же момент я собирал BOSS’ а с поддержкой gcc 4.8 / gcc 4.8.1.

Когда у меня все заработало на gcc 4.7.2 я решил испробовать собрать на Microsoft Visual C 2013. Одна из распространенных операционных систем — Windows. Де факто один из основных инструментов разработки на C под нее — Microsoft Visual C . Получается, что данный компилятор дюже отлично было бы поддержать при сборке BOSS, Дабы дать вероятность применения системы плагинов BOSS в привычной для многих Windows-разработчиков среде. Благо версия 2013 года теснее позиционировалась с помощью C 11 и самое увлекательное — это в ней возникла типичная работа с образцами с переменным числом параметров. А это основное, что требовалось мне для сборки моей системы плагинов.

Воодушевленный этим я испробовал сделать сборку на Microsoft Visual C 2013 и здесь меня ожидало первое разочарование. Образцы-то с переменным числом параметров работают, а constexpr’а нет. Не поддерживается. А он мне был нужен для расчета идентификаторов сущностей, которые вычисляются в момент компиляции как CRC32. Решив отказаться от блага расчета CRC32 в момент компиляции от переданной строки, я убрал его из кода. Мне необходимы были константы для идентификации интерфейсов, классов-реализаций и сервисов, которые применялись как параметры образцов. Рассчитав их отдельно, я заменил каждый код расчета в момент компиляции на готовые значения. Испробовал еще раз собрать. Здесь было второе разочарование — в одном из ключевых мест компилятор легко выдал сообщение о внутренней ошибке компилятора. Несколько маленьких попыток что-то поменять так же не увенчались триумфом. Говорят, что один раз — это случайность, два — совпадение, а три — обоснованность. Не доходя до итога обоснованности, я на время отложил попытки сделать сборку на Microsoft Visual C 2013. Для сборки под Windows применял MinGW той же версии gcc, что и для *nix.

Вторым компилятором, на котором пробовал сделать сборку был clang. С версией 3.4 не заладилось сразу. На версии 3.5 есть некоторые пока задачи. Но тропинка нащупана и допустимо в скором грядущем будет сборка под clang. А вот необходима ли она…

Собрав все удачно под Windows и Linux (Ubuntu), решил испробовать сделать сборку под FreeBSD. Поставив на виртуальную машину FreeBSD 9.2, обнаружил в портах компилятор gcc 4.9. Заинтересовался сборкой на больше новой версии (ранее как было сказано я экспериментировал с gcc 4.8.1 / gcc 4.8 и gcc 4.7.2). Установив компилятор gcc 4.9, он мне преподнес малоприятный сюрприз отсутствием std::to_string. Стремительно заглянув в интернет, осознал, что баг такой есть. Поставил больше раннюю версию gcc 4.8 и поменял код, использующий эту функцию. Подобно была задача и с std::stoul. Версия 4.9 сейчас может быть использована для сборки.

Позже всех экспериментов пока только на gcc и остановился, невзирая на то, что ранее много работал с Microsoft Visual C . Допустимо кто-то скажет, что пока еще не стоит применять C 11. На мой взор, все же стоит применять новейший эталон, т.к. он дает много вероятностей. Язык стал немножко труднее, а применение его проще. К тому же стандартная библиотека стала «еще больше кроссплатформенной». Так в BOSS применяются потоки и примитивы синхронизации, которых ранее не было и доводилось писать либо свою обертку либо применять что-то готовое, скажем, boost. К тому же и с помощью одинаковости на различных компиляторах от различных изготовителей для C 03 не все обстоит гладко.

Остановившись на gcc, как среду разработки я предпочел QtCreator. Он разрешает не только планы с применением Qt разрабатывать, но дозволено и всякий план с наличием Makefile в нем применять, чем я пользовался как для *nix, так и для Windows.

Основные идеи и решения


От всеобщих слов предлагаю перейти к определенным особенностям предлагаемой системы плагинов. Основные идеи, которые первоначально были заложены и которые привели к происхождению плана — это

  • Сделать систему плагинов кроссплатформенной
  • Сделать как дозволено больше тонкой прослойку для реализации компонент
  • Дать вероятность делать как дозволено поменьше «ритуальных» действий при создании компонент


О кроссплатформенности теснее сказано выше. Могу лишь добавить, что огромная часть ранее зависимого кода от ОС сейчас поддерживается стандартной библиотекой. Остался маленький фрагмент по загрузке динамических библиотек, в которых размещаются плагины.

О том насколько тонкой получилась прослойка всякий может судить сам. И может быть написать увлекательный конструктивный комментарий с предложениями об совершенствовании. Я же при реализации усердствовал избавиться от мечты добавить чего-то еще, что может быть когда-то в редких случаях будет пригодным либо то, что дозволено обнаружить в том же boost, poco и т. д.

Когда я прежде применял MS COM либо пользовался аналогичными «корпоративными framework’ами» мне неизменно хотелось отделаться от кучи макросов и исключительно от карты интерфейсов, в которой нужно указывать какие из интерфейсов я хочу экспортировать из класса-реализации. При поддержке очередного интерфейса классом-реализацией, нужно было от него наследоваться и его же разместить в карту интерфейсов. Как ни необычно, последнее я дюже Зачастую забывал. Припоминал только при разработке клиентского кода, когда необходимый мне интерфейс не запрашивался из разработанного же мною компонента, так как я его позабыл включить в экспортируемые интерфейсы (в карту). Данный, по моему суждению, недочет я решил убрать в предлагаемой реализации. Получилось так, что от каких интерфейсов класс-реализация наследован, те интерфейсы механически экспортируются. Как это технически реализовано было мною детально описано в [1] и ранее в [2] (соответственно средствами C 11 и С 03). Здесь есть маленькая плата за эту автоматизацию — невозможно наследовать интерфейс и запретить его запрашивать у объекта. Надобность в этом мне кажется дюже редкой если вообще требуемой, а если и возникнет, то ее дозволено легко решить примерно нулевыми расходами без модификации системы плагинов.

C имеет такой главный камень, как множественное наследование реализаций. Некоторые современные языки программирования такое исключают, предлагая вместо пользоваться множественным наследование интерфейсов. Не хочу разжигать священной войны с посягательством на чей-либо в этом отношении духовный катехезис. Мое отношение к множественному наследованию реализаций позитивно. Оно дает вероятность экономить время, скажем, при решении такой задачи: есть пара интерфейсов, реализованных в своих классах-реализациях и появляется надобность реализовать класс с помощью этих же интерфейсов с той же реализацией и добавить еще один интерфейс к нему. Писать класс наследованный от 3 интерфейсов, в нем в качестве полей указывать готовые реализации и к ним перенаправлять все способы 2-х интерфейсов, реализуя по настоящему только 3-й, неохотно. Да у множественного наследования реализаций есть и свои недочеты, но при применении инструмента при потребности, а не потому что он есть, от них дозволено укрыться при этом еще и получив выгоду.

В силу этого «философствования» компонентная модель по максимуму может так же применять готовые реализации интерфейсов при создании очередного компонента из готовых «кубиков» с добавлением чего-то нового. А для того, Дабы интерфейсы, реализованные ранее были видны из нового класса реализации ничего добавочного делать не нужно. Как и реализуемые интерфейсы в очередном классе-реализации, готовые реализации легко указываются в списке наследования. В различии от тех моделей, с которыми мне доводилось иметь дело ранее, в BOSS не нужно вести карту интерфейсов и не нужно в ней указывать в какой базовый класс-реализацию перенаправить вызов при поиске очередного интерфейса. Это все делается «под капотом» BOSS. Это превосходство множественного наследования и некоторой реализации в BOSS приближает еще на шаг к поставленной цели минимизировать «ритуальные действия» при создании компонента из готовых частей (создание наборного компонента из готовых кубиков).

Все идентификаторы интерфейсов — CRC32, вычисленное от строки. Ранее в [2] я применял строку. С возникновением C 11 возникла вероятность от этой строки вычислить CRC32 в момент компиляции. Это так же дало вероятность немножко сократить нужные действия. Для интерфейсов специального сокращения не получилось в [1] в различии от [2], а вот для идентификации классов-реализаций, в которых реализуются интерфейсы получилось сокращение и вероятность избавить от забывчивости. Если в [2] при разработке класса-реализации позабыть указать его идентификатор, то это может обнаружиться маленький неприятностью в момент исполнения программы. В [1] же если не указать идентификатор класса-реализации, то компилятор об этом не помнит не двусмысленно. К тому же сейчас идентификатор нужно указывать как параметр базового шаблонного класса для классов-реализаций, а не где-то отдельно. А применение числа взамен строки дает это сделать без специальных задач. Больше детально о превосходствах и кажущихся загвоздках применения числа как идентификатора взамен строки рассказано в [1].

Применение BOSS’а


Все компоненты системы плагинов BOSS — это реализации интерфейсов. Всякий интерфейс должен иметь свой идентификатор и быть наследован либо от базового интерфейса Boss::IBase, либо от другого иного интерфейса, тот, что так же ведет к Boss::IBase. При изложении интерфейсов допустимо и множественное наследование интерфейсов.
Пример изложения простого интерфейса:

namespace MyNs
{
  struct ISimpleObject
    : public Boss::Inherit<Boss::IBase>
  {
    BOSS_DECLARE_IFACEID("MyNs.ISimpleObject")
    virtual Boss::RetCode BOSS_CALL HelloWorld() = 0;
  };
}


Некоторая сущность Boss::Inherit дает вероятность обходить список переданных интерфейсов при поиске надобного в случае множественного наследования. Как это реализовано и отчего в ней возникла надобность описано в [1], а так же как это дозволено применять больше детально описано в документации [3]. Под макросом BOSS_DECLARE_IFACEID спрятана работа с идентификатором интерфейса (она в последующем может меняться в зависимости от компилятора, так как есть задачи, описанные выше). Макрос BOSS_CALL так же предпочтительно применять при изложении способов интерфейса. Это даст вероятность в грядущем воспользоваться некоторыми превосходствами, которые пока в разработке.

Все способы интерфейса — это чисто виртуальные функции, которые могут принимать и возвращать всякие типы, которые разработчик сочтет допустимыми передавать между динамическими библиотеками. Впрочем в рамках рассматриваемой системы плагинов было бы отлично применять в качестве типа возвращаемого значения Boss::RetCode, а в качестве передаваемых параметров применять только интегральные типы, типы с плавающей точкой, указатели и ссылки на них и указатели и двойные указатели на интерфейсы и cv-квалификатор. Об этом рассказано в документации [3] и допустимо станет материалом одного из последующих постов, когда вышедший продукт [4] из полуготовой реализации межпроцессного взаимодействия плагинов будет доведен до конца, куда [4] обратно будет размещен как база для больше трудного взаимодействия плагинов через границы процессов.

Пример интерфейсов с множественным наследованием

namespace MyNs
{

  namespace IFaces
  {
    struct IFace1
      : public Boss::Inherit<Boss::IBase>
    {
      BOSS_DECLARE_IFACEID("MyNs.IFaces.IFace1")
      virtual Boss::RetCode BOSS_CALL Method1() = 0;
    };

    struct IFace2
      : public Boss::Inherit<Boss::IBase>
    {
      BOSS_DECLARE_IFACEID("MyNs.IFaces.IFace2")
      virtual Boss::RetCode BOSS_CALL Method2() = 0;
    };

    struct IFace3
      : public Boss::Inherit<IFace1, IFace2>
    {
      BOSS_DECLARE_IFACEID("MyNs.IFaces.IFace3")
      virtual Boss::RetCode BOSS_CALL Method3() = 0;
    };
  }
}

Сделанные интерфейсы обязаны быть где-то реализованы. Ниже приведен пример примитивный реализации интерфейса

class SimpleObject
  : public Boss::CoClass<Boss::MakeId("MyNs.SimpleObject"), ISimpleObject>
{
public:
  // ...
private:
  // ISimpleObject
  virtual Boss::RetCode BOSS_CALL HelloWorld()
  {
    // ...
    return Boss::Status::Ok;
  }
};

Из примера видно, что класс-реализация наследован от класса Boss::CoClass. Это класс является базовым для всех классов-реализаций интерфейсов. Первым параметром передается идентификатор (CRC32) класса-реализации. Всякий интерфейс может иметь различные реализации и для выбора требуемой нужен идентификатор. Получить его из строки дозволено с поддержкой Boss::MakeId. Строки в коде больше читаемы, а компилятор и финальный программный продукт будут пользоваться числами (CRC32), рассчитанными от этих строк. Последующими параметрами образца передаются интерфейсы, которые реализует класс. Так же в этом списке могут быть переданы и готовые классы-реализации, если разрабатываемый класс захочет их поддержать без реализации у себя (сборка компонента из готовых кубиков).

Пример больше трудного компонента

namespace MyNs
{
  namespace Impl
  {

    class Face4
      : public Boss::CoClass
          <
            Boss::MakeId("MyNs.Impl.Face4"),
            IFaces::IFace4
          >
    {
    public:
      // ...      
    private:
      // IFace4
      virtual Boss::RetCode BOSS_CALL Method4()
      {
        // ...
        return Boss::Status::Ok;
      }
    };

    class Face_1_2_3_4
      : public Boss::CoClass
          <
            Boss::MakeId("MyNs.Impl.Face_1_2_3_4"),
            IFaces::IFace3,
            Face4
          >
    {
    public:
      // ...
    private:
      // IFace1
      virtual Boss::RetCode BOSS_CALL Method1()
      {
        // ...
        return Boss::Status::Ok;
      }
      // IFace2
      virtual Boss::RetCode BOSS_CALL Method2()
      {
        // ...
        return Boss::Status::Ok;
      }
      // IFace3
      virtual Boss::RetCode BOSS_CALL Method3()
      {
        // ...
        return Boss::Status::Ok;
      }
    };
  }

}


На рисунке приведена упрощенная иерархия наследования интерфейсов и классов-реализаций без отображения промежуточных классов системы плагинов. В приведенном примере дозволено видеть как класс-реализация наследуется от пары интерфейсов, реализует их и пользуется теснее готовой реализацией одного из интерфейсов, легко унаследовав ее. При этом интерфейс, расположенный в наследованной реализации так же доступен для механизма приобретения интерфейсов.

Интерфейсы, реализации… Думаю стоит немножко возвратиться назад и посмотреть, что представляет из себя базовый интерфейс Boss::IBase. И если его показать в упрощенном виде, то он будет выглядеть так:

struct IBase
{
  BOSS_DECLARE_IFACEID("Boss.IBase")
  virtual ~IBase() {}
  virtual Boss::UInt BOSS_CALL AddRef() = 0; 
  virtual Boss::UInt BOSS_CALL Release() = 0; 
  virtual Boss::RetCode BOSS_CALL QueryInterface(Boss::InterfaceId ifaceId, Boss::Ptr *iface) = 0;
};

В действительности он немножко напротив выглядит, но сводится к приведенному выше. О тонкостях реализации этого интерфейса написано в [1]. Интерфейс имеет способы управления временем жизни объекта (AddRef и Release), а оно основывается на подсчете ссылок. Эти способы желанно никогда не обязаны вызываться пользователем, их применение спрятано в мудрые указатели. Пользовательский код должен ориентироваться на применение разумных указателей. 3-й способ (QueryInterface) предуготовлен для приобретения надобного интерфейса по его идентификатору из имеющегося указателя на иной интерфейс.

Так же при создании компонент в BOSS имеется вероятность применять для больше тонкой настройки трудных объектов механизм «постконструкторов» и «преддеструкторов». Больше детально в [1] описано как это реализовано, а в [3] для чего и как этим дозволено воспользоваться (см. FinalizeConstruct и BeforeRelease). Тут лишь коротко скажу, что он необходим при реализации компонент из готовых кубиков и при необходимости сделать что-то когда объект теснее всецело сделан либо пока он еще не начал разрушаться, т.е. то, что невозможно сделать в конструкторах и деструкторах (скажем вызвать воображаемый способ из конструктора / деструктора, переопределенный в иерархии ниже).

Все компоненты где-то обязаны обитать. Местом такого обитания служат динамические библиотеки. Для того Дабы все реализованные в динамической библиотеке компоненты были видны коду заказчика, библиотека должна содержать точку входа. Точка входа — интерфейс для фабрики классов, которым она пользуется при создании объекта по запросу пользователя.

Пример точки входа

#include "face1.h"
#include "face2.h"
#include "plugin/module.h"
namespace
{

  typedef std::tuple
    <
      MyNs::Face1,
      MyNs::Face2
    >
    ExportedCoClasses;
}
BOSS_DECLARE_MODULE_ENTRY_POINT("MultyComponentExample", ExportedCoClasses)

Из примера видно, для создания точки входа создается список экспортируемых классов-реализаций (ExportedCoClasses) и передается в макрос BOSS_DECLARE_MODULE_ENTRY_POINT. Данный макрос так же принимает идентификатор модуля (строка для приобретения CRC32).

Позже этого компонент готов к применению. Для применения компонент нужно зарегистрировать. Если говорить о системе плагинов в рамках одного процесса, то она состоит из пользовательских компонент и компонент ядра системы плагинов (реестра сервисов и фабрики классов).

Подводя выводы этой части поста дозволено сказать, что для создания и применения компонент / плагинов необходимо следующее.

  1. Определить интерфейсы
  2. Сделать классы-реализации
  3. Разместить классы-реализации в модули либо модуль, где сделать и точку входа
  4. Зарегистрировать модули
  5. В части кода заказчика загрузить ядро и создавать надобные объекты, применяя их идентификаторы реализаций.

Хотелось подметить, что при загрузке фабрикой во все модули передается всеобщий ServiceLocator, в котором имеется указатель на фабрику классов. Ранее была реализация без такого глобального ServiceLocator’а. Доводилось из компонента в компонент передавать указатель на фабрику классов, если компонент нуждался в создании других объектов с поддержкой фабрики. Но как вестимо, больше живучими являются смешанные решения. В чистом виде что-то одно изредка проблемно применять. Поэтому имеется соглашение — всеобщий ServiceLocator. Он так же может быть использован для размещения пользовательских объектов, а не только фабрики классов. В то же время злоупотреблять применением глобального ServiceLocator’а не стоит, дозволено легко запутаться и сделать немыслимой правильную выгрузку модулей в момент когда они не необходимы либо при заключении программы. Это задача систем с подсчетом ссылок, когда возникают циклические ссылки.

Примеры


В качестве первого примера, предлагаю разглядеть создание простого интерфейса, реализовать его и написать код заказчика без применения инфраструктуры (фабрики классов, реестра сервисов).

Пример создания и применения простого объекта

#include "core/ibase.h" // Интерфейс IBase
#include "core/co_class.h"  // Базовый класс для всех классов-реализаций
#include "core/base.h"  // Реализация базового интерфейса IBase
#include "core/ref_obj_ptr.h" // Разумный указатель RefObjPtr
#include "core/module.h"  // Данный файл содержит определение некоторых
                          // способов системы плагинов BOSS. Должен быть
                          // включен один раз в план если не применяется
                          // инфраструктура. Если применяется инфраструктура,
                          // то должно быть исключительное включение в всякий
                          // модуль файла "plugin/module.h" взамен "core/module.h"

  #include <iostream>
namespace MyNs
{

  // Определение интерфейса с базовым интерфейсом IBase
  struct ISimpleObject
    : public Boss::Inherit<Boss::IBase>
  {
    // Идентификатор интерфейса
    BOSS_DECLARE_IFACEID("MyNs.ISimpleObject")
    virtual Boss::RetCode BOSS_CALL HelloWorld() = 0;
  };

  // Класс-реализация интерфейса ISimpleObject
  class SimpleObject
    : public Boss::CoClass  // Базовый класс для всех классов-реализаций
        <
          Boss::MakeId("MyNs.SimpleObject"),  // Идентификатор реализации
          ISimpleObject // Реализуемый интерфейс
        >
  {
  public:
    SimpleObject()
    {
      std::cout << "SimpleObject" << std::endl;
    }
    ~SimpleObject()
    {
      std::cout << "~SimpleObject" << std::endl;
    }

  private:
    // ISimpleObject
    virtual Boss::RetCode BOSS_CALL HelloWorld()
    {
      // Реализация способа интерфейса
      std::cout << "BOSS. Hello World!" << std::endl;
      // Возвращает один из рангов заключения выполнения способа
      return Boss::Status::Ok;
    }
  };
}
int main()
{
  try
  {
    // Создание объекта, реализующего интерфейс ISimpleObject
    Boss::RefObjPtr<MyNs::ISimpleObject> Inst = Boss::Base<MyNs::SimpleObject>::Create();
    // Вызов способа интерфейса
    Inst->HelloWorld();
  }
  catch (std::exception const &e)
  {
    std::cerr << "Error: " << e.what() << std::endl;
  }
  return 0;
}


Все, что приведено в примере теснее рассматривалось выше. Казалось бы для чего в системе плагинов создавать объекты без применения фабрики классов. Причем здесь плагины? Да, плагины — это финальная цель, а создавать объекты без фабрики необходимо, скажем, при создании возвращаемого объекта из способа интерфейса. Способ интерфейса создает определенный маленький объект, и возвращает его как выходной параметр, при этом данный класс-реализация не регистрируется нигде, в этом нет нужды. Дабы продемонстрировать это приведу пример, в котором способ интерфейса возвращает коллекцию строк в качестве выходного параметра.

Пример работы с коллекцией строк

#include "core/module.h"
#include "core/ibase.h"
#include "core/base.h"
#include "core/co_class.h"
#include "core/ref_obj_ptr.h"
#include "common/string.h"  // Реализация интерфейса IString
#include "common/enum.h"  // Реализация интерфейса IEnum
#include "common/string_helper.h" // Вспомогательный класс для IString
#include "common/enum_helper.h" // Вспомогательный класс для IEnum
  #include <iostream>
namespace MyNs
{
  // Изложение интерфейса
  struct ISimpleObject
    : public Boss::Inherit<Boss::IBase>
  {
    // Идентификатор интерфейса
    BOSS_DECLARE_IFACEID("MyNs.ISimpleObject")
    // Способ, возвращающий коллекцию строк
    virtual Boss::RetCode BOSS_CALL GetStrings(Boss::IEnum **strings) const = 0;
  };
  // Реализация интерфейса
  class SimpleObject
    : public Boss::CoClass  // Базовый класс для всех классов-реализаций
        <
          Boss::MakeId("MyNs.SimpleObject"),  // Идентификатор реализации
          ISimpleObject // Реализуемый интерфейс
        >
  {
  public:
    SimpleObject()
    {
      // Создание коллекции
      auto StringEnum = Boss::Base<Boss::Enum>::Create();

      // Добавление строк в коллекцию
      StringEnum->AddItem(Boss::Base<Boss::String>::Create("String 1"));
      StringEnum->AddItem(Boss::Base<Boss::String>::Create("String 2"));
      StringEnum->AddItem(Boss::Base<Boss::String>::Create("String 3"));

      Strings = std::move(StringEnum);

      std::cout << "SimpleObject" << std::endl;
    }
    ~SimpleObject()
    {
      std::cout << "~SimpleObject" << std::endl;
    }

  private:
    // Коллекция строк
    mutable Boss::RefObjPtr<Boss::IEnum> Strings;

    // ISimpleObject
    virtual Boss::RetCode BOSS_CALL GetStrings(Boss::IEnum **strings) const
    {
      // Проверка входного параметра. Должен быть пуст, Дабы исключить вероятность
      // присвоения теснее присутствующему объекту нового и минимизировать ошибки,
      // связанные с утратами
      if (!strings)
        return Boss::Status::InvalidArgument;
      // Возвращение коллекции строк, сделанной в конструкторе.
      // Конструкция Strings.QueryInterface(strings) для возврата выходного
      // параметра предпочтительнее, конструкции
      //  *strings = Strings.Get();
      //  (*strings)->AddRef();
      //  retutn Boss::Status::Ok;
      // так как больше короткая и исключает вероятность позабыть увеличить
      // счетчик ссылок на объект, что могло бы привести к длинному поиску
      // ошибки в работающей программе.
      return Strings.QueryInterface(strings);
    }
  };

}
int main()
{
  try
  {
    // Создание объекта
    Boss::RefObjPtr<MyNs::ISimpleObject> Obj = Boss::Base<MyNs::SimpleObject>::Create();
    // Разумный указатель на IEnum, в тот, что будет передано выходное значение
    Boss::RefObjPtr<Boss::IEnum> Strings;
    // Запрос коллекции строк
    if (Obj->GetStrings(Strings.GetPPtr()) != Boss::Status::Ok)
    {
      std::cerr << "failed to get strings." << std::endl;
      return -1;
    }
    // Применение вспомогательного класса для комфорта работы с коллекцией
    Boss::EnumHelper<Boss::IString> Enum(Strings);
    // Проход по коллекции строк
    for (Boss::RefObjPtr<Boss::IString> i = Enum.First() ; i.Get() ; i = Enum.Next())
    {
      // Применение вспомогательного класса для работы с интерфейсом строки
      std::cout << Boss::StringHelper(i).GetString<Boss::IString::AnsiString>() << std::endl;
    }
  }
  catch (std::exception const &e)
  {
    std::cerr << "Error: " << e.what() << std::endl;
  }
  return 0;
}


Допустимо из этого примера возникнет вопрос: «А для чего применять IString, отчего легко не возвращать char const * ?». Да, дозволено и легко строку воротить (char const *), но в последующем если планируется переход на межпроцессное взаимодействие плагинов (которое должно скоро возникнуть; о нем постараюсь написать пост) то это решение будет проблемным. При ориентации только на применение плагина в рамках одного процесса такой задачи нет и дозволено возвращать char const *. Так же пример с IString отлично подходит для демонстрации работы с объектами.

От примитивных примеров с созданием объектов без применения инфраструктуры предлагаю перейти к полновесному примеру создания плагина, его регистрации в реестре и т.д., т.е. полновесная работа в системе плагинов. А в качестве примера предлагаю разработать примитивный интерфейс с исключительным способом для сложения пары чисел.

Определение интерфейса

// Файл isum.h
#include "core/ibase.h"

namespace MyNs
{
  struct ISum
    : public Boss::Inherit<Boss::IBase>
  {
    BOSS_DECLARE_IFACEID("MyNs.ICalc")
    virtual Boss::RetCode BOSS_CALL CalcSum(int a, int b, int *sum) = 0;
  };
}


Для класса-реализации дозволено определить его идентификатор отдельно. Так как данный идентификатор сейчас будет применяться в нескольких местах: при создании класса-реализации и в коде заказчика при создании объекта через фабрику классов.

Определение идентификатора реализации

// Файл class_ids.h
#include "core/utils.h"

namespace MyNs
{
  namespace Service
  {
    namespace Id
    {
      enum
      {
        Sum = Boss::MakeId("MyNs.Service.Id.Sum")
      };
    }
  }
}

Реализация интерфейса ISum

// calc_service.h
#include "isum.h"
#include "class_ids.h"
#include "core/co_class.h"
namespace MyNs
{
  class Sum
    : public Boss::CoClass
          <
            Service::Id::Sum,
            ISum
          >
  {
  public:
    // ISum
    virtual Boss::RetCode BOSS_CALL CalcSum(int a, int b, int *sum);
  };

}

// calc_service.cpp
#include "calc_service.h"
#include "core/error_codes.h"
namespace MyNs
{

  Boss::RetCode BOSS_CALL Sum::CalcSum(int a, int b, int *sum)
  {
    if (*sum)
      return Boss::Status::InvalidArgument;
    *sum = a   b;
    return Boss::Status::Ok;
  }
}


Реализовав интерфейс, для полновесного компонента требуется сделать точку входа и дозволено собирать его как динамическую библиотеку.

Точка входа

// Файл  module.cpp

#include "calc_service.h"
#include "plugin/module.h"
namespace
{

  typedef std::tuple
    <
      MyNs::Sum
    >
    ExportedCoClasses;
}
BOSS_DECLARE_MODULE_ENTRY_POINT("Calc.Sum", ExportedCoClasses)


Позже создания плагина его регистрация делается утилитой regtool.
Для *nix систем: ./regtool -reg Registry.xml ./libcalc_service.so
Для Windows: regtool.exr -reg Registry.xml calc_service.dll

Осталось только код заказчика написать…

Заказчик

#include "isum.h"
#include "class_ids.h"
#include "plugin/loader.h"  // Загрузчик системы плагинов

#include <iostream>
int main()
{
  try
  {
    // Загрузка ядра системы плагинов. Указывается файл реестра и модули
    // реестра сервисов и фабрики классов. Позже чего пользователь всецело
    // абстрагируется от модулей, путей к ним и их загрузке.
    Boss::Loader Ldr("Registry.xml", "./" MAKE_MODULE_NAME("service_registry"),
                     "./" MAKE_MODULE_NAME("class_factory"));
    // Создание объекта через фабрику классов, доступ к которой допустим через
    // всеобщий ServiceLocator. Вспомогательная функция Boss::CreateObject
    // получает всеобщий ServiceLocator, находит в нем фабрику классов,
    // создает через нее необходимую реализацию, запрашивает требуемый интерфейс
    // и возвращает разумный указатель на сделанный объект с требуемым интерфейсом.
    // Так же эта функция может быть использована внутри всякого из плагинов.
    auto Obj = Boss::CreateObject<MyNs::ISum>(MyNs::Service::Id::Sum);
    int Res = 0;
    // Вызов способа интерфейса
    if (Obj->CalcSum(10, 20, &Res))
      std::cerr << "Failed to calc sum." << std::endl;
    std::cout << "Sum: " << Res << std::endl;
  }
  catch (std::exception const &e)
  {
    std::cerr << "Error: " << e.what() << std::endl;
  }
  return 0;
}


Для подсчета суммы 2-х чисел кода дюже много. В параллель дозволено привести сложение 2 2 с применением ООП. Но это каждого лишь демонстрация функциональности и в реальной разработке код логики куда большего размера, на фоне которого компонентная прослойка теснее не так приметна. Все же компонентизация предуготовлена не для «сложения 2 2» и коротких программ, а ее существование обусловлено желанием декомпозировать системы, которые с трудом могут существовать в рамках одного исполняемого файла.

Завершение


В завершении хотелось бы сказать что и где находится и планах становления.

Все примеры, приведенные в этом посте, а так же иные, маленькая документация, сама система плагинов (компонентная модель), информация по сборке и т.д. доступны на www.t-boss.ru Так же начальные файлы доступны для скачивания в виде zip-архива. При сборке необходимо в Makefile раскомментировать строку, определяющую целевую платформу, закомментировав все остальные. Либо дозволено собирать из командной строки на_rqvmk!   Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовало 2 человека. Воздержавшихся нет.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB