Пример системы плагинов


Пример системы плагинов

В этой статье будет рассмотрен способ реализации поддержки плагинов в своем приложении при помощи интерфейсов в среде разработки Delphi.

Данная реализация системы плагинов имеет ряд преимуществ по сравнению с другими системами (dll, packages, classes):

  • Независимость от языка программирования, т.е. разработчики плагинов смогут создавать плагины к вашему приложению, используя ту среду разработки (язык программирования), которая им нравится. Требований к среде лишь два: она должна поддерживать работу с интерфейсами, а также позволять создавать DLL.
  • Простота написания плагинов. DLL плагина должна будет экспортировать всего лишь одну функцию. По сути, плагин является всего лишь классом (объектом), который реализует методы одного или нескольких интерфейсов. Разработчикам плагинов можно будет предоставить готовые к использованию заготовки классов. Все что им останется сделать, это создать наследника от предоставленных классов и реализовать нужную функциональность.
  • Огромные возможности расширения. Никогда сразу нельзя предугадать какие типы плагинов захочется иметь разработчикам плагинов, какими возможностями должны обладать плагины.
    Система плагинов основанная на интерфейсах позволит легко и безболезненно добавлять новые типы плагинов, расширять функциональность уже существующих типов плагинов.
  • Один модуль плагина (DLL) может реализовывать функциональность нескольких типов плагинов. Т.к. система построена на интерфейсах, то плагин может реализовать методы нескольких интерфейсов сразу.

Если вы незнакомы с интерфейсами, то почитать о них вы можете в справочной системе, Object Pascal Language Guide. Или в книге Тейкстры и Пачеко «Delphi 5»

Эта статья содержит уже готовую к работе систему, а также примеры пары плагинов.

Описание системы

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

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

Какие же свойства и функции общие для всех плагинов?

Во-первых, это уникальный идентификатор плагина, в качестве которого можно использовать GUID — это будет гарантировать уникальность идентификатора (для его генерации в среде Delphi нужно нажать . Этот идентификатор можно будет использовать, например, для того, чтобы позволить пользователю отключить загрузку указанного им плагина, или для того чтобы хранить настройки плагина.

Во-вторых, это данные об авторе плагина, например Имя и E-mail.

Название плагина, также будет не лишним.

Рано или поздно любой плагин будет выгружен, поэтому мы должны иметь возможность предупредить его об этом.

Таким образом, базовый интерфейс может иметь такой вид:

Из названий ясно назначение того или иного метода/свойства.

Так как мы описали базовый интерфейс для всех плагинов, то теперь можно более подробно рассмотреть, что же такое плагин в нашей системе. Плагин — это просто DLL, которая содержит экспортируемую функцию LoadPlugin, результат которой — указатель на класс реализующий, по крайней мере, базовый интерфейс.

Т.е. исходный текст простейшего плагина выглядит следующим образом:

Полные исходные коды плагина вы найдете в разделе «Ссылки». Там есть пример плагина IInfoPlugin возвращающего имя пользователя.

Менеджер плагинов

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

Когда вы вызовете метод LoadPlugins, то менеджер плагинов будет перебирать все файлы *.dll и загружать их. Затем он пытается получить адрес функции LoadPlugin и вызывает ее. Если ее результат не nil, то он добавляет полученный указатель в список всех плагинов. А затем он начинает перебирать интерфейсы (при добавлении новых интерфейсов, их обработку вам нужно добавить самим, по аналогии) для того, чтобы понять какого типа является данный плагин и добавляет его в список соответствующей категории.

Ну вот собственно и все, посмотреть примеры, как уже говорилось ранее вы можете в разделе «Ссылки».

Ссылки

Книги по Delphi — здесь. Особенно советую почитать Пачеко. У него очень хорошо написано про интерфейсы. Также полезно почитать «Inside COM / Основы COM».

Плагин — что это такое простыми словами, где его можно скачать, как установить и обновить plugin

Здравствуйте, уважаемые читатели блога KtoNaNovenkogo.ru. Если говорить простым языком, то плагин — это дополнение (расширение возможностей) для какой-либо программы на вашем компьютере или движка сайта в интернете. Разработчикам очень трудно бывает предусмотреть все пожелания пользователей, поэтому они дают возможность сторонним разработчикам удовлетворять эти пожелания при помощи написания плагинов (от английского plugin).

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

Что такое plugin простыми словами и где их можно скачать?

Кстати, некоторые плагины даже сами могут напомнить вам о том, что их следовало бы установить. Знаете такие примеры? Наверняка. Все тот же Adobe flash player предложит установить или обновить себя в используемый вами браузер, когда его наличие будет необходимо для проигрывания видео контента на открытой странице.

Итак, давайте подытожим, что же такое плагины? В общем случае это программный блок, который можно будет установить (подключить) в основное приложение для расширения его функционала (добавления новых возможностей). Обратите внимание, что не каждое приложение имеет возможность установки плагинов — в нем должно быть это предусмотрено. Как правило, сам plugin самостоятельно (без этого приложения) работать не может.

Если проводить параллели и взять данную публикацию как аналог приложения, то ваши комментарии, уважаемые читатели, можно будет приравнять к плагинам. Данное «приложение» позволяет это делать с помощью формы добавления комментария, равно как и реальная программа предлагает программному дополнению протокол обмена данными. Без этой формы вы при всем желании не сможете оставить свой комментарий.

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

Где скачать нужный вам плагин для каждого конкретного приложения, вы сможете узнать на официальном сайте этой программы или движка сайта. Введите в Яндексе или Google название приложения, и первый сайт в органической выдаче и будет официальным с высокой долей вероятности.

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

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

Плагины для браузеров и других программ на компьютере

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

Сейчас все браузеры, которые можно скачать в интернете, в той или иной мере поддерживают расширения. Я понимаю, что на вкус и цвет товарищей нет, но тем не менее позволю себе привести список с кратким описанием тех плагинов для браузеров, которые использую сам в повседневной жизни. Все это вылилось в три публикации, ссылки на которые я и привожу ниже:

Как установить plugin для вашего браузера? Довольно просто. Нужно зайти, находясь в этом самом обозревателе, на страницу с нужным вам расширением и нажать кнопку скачать или аналогичную ей по смыслу. Официальные сайты плагинов находятся тут:

Разработчики некоторых расширений не успевают за разработчиками браузеров и может возникнуть такая ситуация, что после обновления обозревателя какие-то из них работать перестанут (в первую очередь касается Фаерфокса). Что-то поделать с этим сложно, но популярные plugin-разработчики все же не дремлют и стараются выкатывать обновленные версии своих творений своевременно.


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

Но не браузерами едиными. Все тот же Total Commander, обросший массой расширений, может вам практически заменить операционную систему и стать своеобразным аналогом Нортон Коммандеру на первых персональных компьютерах. С помощью plugin он и картинки с фильмами показывать может, и с архивами работать поможет, и еще много чего сделает.

Читайте про плагины для Тотал Командера в приведенной статье. Самим их можно скачать с официального сайта, а установить в файловый менеджер их лишь чуток посложнее, чем в браузер, ну, а в приведенной статье этот процесс подробно расписан.

Даже такая простая вещь, как аналог обычном блокноту в Windows, который зовется Нотепад++ имеет свои расширения и с каждым обновлением этой программы все большее их количество добавляется по умолчанию. Благодаря им данный блокнот является одни из самых востребованных среди тех, кто работает с Html, PHP и другим кодом. Подробности читайте в статье про плагины для Notepad ++.

Для такой популярной в рунете программы как Фотошоп тоже имеется очень богатый ассортимент дополнений, которые называются, правда, не plugin, а фильтрами, но суть от этого не меняется. Все, что не заложено в функционал основой программы, можно будет при желании и определенном везении реализовать с помощью плагинов.

Например, осуществить сохранение графического файла в том формате, который изначально не поддерживается (ico, к примеру). Устанавливаются фильтры не сложно и об этом можно почитать на тематических ресурсах.

Плагины для движков сайтов на примере WordPress и Joomla

Для вебмастеров плагины еще ассоциируются с расширениями для тех бесплатных и платных CMS (движков сайтов), которые они используются. По статистике самыми популярными из бесплатных являются Joomla и WordPress, поэтому о них мы сейчас и поговорим.

Движок WordPress позволяет создавать блоги, и по своему функционалу и удобству использования вполне способен поспорить с платными решениями. Однако, вы практически не найдете блога, где бы не использовался хотя бы один plugin. Как бы не были богаты и разнообразны возможности самой CMS, но всегда найдется то, что реализовать нужно, но не получается это сделать без дополнительных расширений.

Плагины, которые установлены на моем WordPress блоге, я довольно подробно описал и без них сейчас не мыслю работу. Какие-то из них отвечают за снижение нагрузки блога на сервер, что позволяет мне меньше платить за хостинг. Какие-то помогают в SEO оптимизации для привлечения большего числа посетителей (трафика) из поисковых систем, а значит позволяют мне больше зарабатывать.

При этом я использую только бесплатные расширения, что вдвойне приятно. Скачать плагины для WordPress можно с официального сайта (так они проверены на вирусы и прочую гадость) или же прямо из админки, перейдя на вкладку из левого меню «Плагины» — «Добавить новый».

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

Теперь давайте обратим свой взор на Joomla. Это замечательный бесплатный движок, об установке которого я довольно подробно писал. Правда, там описана установка версии 1.5, но суть мало поменялась, да и версия эта по-прежнему в строю, хоть и не поддерживается разработчиками. Расширений же для Джумлы существует аж три вида: компоненты, плагины и модули.

Компоненты из них самые объемные и навороченные, а модули служат для вывода контента по периметру сайта (вверху, внизу или сбоку от основного содержания). Plugin же в Joomla — это особый продукт, который умеет, например, реагировать на какие-либо события и не имеет таких богатых настроек, как компонент.

Яркими примерами могут служить такие плагины для Джумлы, как TinyMCE, Load Module, Legacy, а еще ряд других, которые установлены в ней по умолчанию. Среди компонентов можно выделить такие как:

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

Система плагинов как упражнение на C++ 11

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

Имеет ли смысл что-то подобное писать или взять готовое решение. Свое видение по этому вопросу я описывал в статье «О желании изобретать один и тот же велосипед снова и снова». Так что в данной статье не будет философии на тему «А зачем оно нужно».

Ранее я уже публиковал статью «Своя компонентная модель на C++», в которой была разработана компонентная / плагинная модель, живущая в рамках процесса. Для меня решение подобной задачи интересно. В gcc 4.7.2 уже появилось все, что мне было интересно на момент начала этой статьи, а это начало этого (2013) года. И тут я дорвался до C++ 11… На работе в одном направлении, дома в другом. Чтобы поиграться с C++ 11 я и решил переписать материал из старой статьи с новыми возможностями языка. Сделать в некотором смысле упражнение на C++. Но в силу некоторых причин мне не удавалось довести материал статьи до конца более полугода, и статья провалялась в черновиках нетронутой. Достал, стряхнул нафталин. Что из этого получилось можете прочесть далее.

О решении использовать C++ 11

Мы ждали, ждали и наконец-то дождались выхода обновлений C++. Вышел новый стандарт языка — C++ 11. Данная редакция языка программирования принесла много интересных и полезных возможностей, а стоит ли его использовать это пока спорный вопрос, но не все компиляторы его поддерживают или поддерживают в неполном объеме.

Введение и немного философии

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

Интерфейсы и идентификаторы

В основу описываемой разработки будет положено взаимодействие компонент системы посредством интерфейсов. Интерфейс в данном контексте стоит понимать как C++ структуру, содержащую только чисто виртуальные методы. Интерфейс будет некоторой логической единицей, вокруг которой все постороено.

Одним из немаловажных вопросов может быть — что использовать в качестве идентификатора интерфейса, реализации, модуля и прочих сущностей. В предыдущей статье в качестве идентификатора использовалась C-строка, так как можно обеспечить большую уникальность если, например, в качестве идентификатора использовать uuid, сгенерированный каким-нибудь инструментом и переведенным в строку. Можно в качестве идентификатора использовать и числовой идентификатор. У такого решения уникальность будет более слабой, но есть преимущества — это, как минимум, большая производительность, что очевидно, сравнивать строки более трудоемко нежели числа. В качестве значения числового идентификатора можно использовать например CRC32 от строки. Предположим есть интерфейс Ibase и находится он в пространстве имен Common. В качестве идентификатора может стать CRC32 от строки «Common.IBase». Да, если вдруг совпадут где-то идентификаторы интерфейсов, так как это не uuid, то вы получите долгие часы «счастливой» отладки и неплохо прокачаетесь в освоении сильной стороны русского языка. Но если у вас нет амбиций, что вашу модель будут использовать во всем мире в глобальных системах, то вероятность исхода с долгой отладкой минимальна. В паре контор имел дело со своими «поделками» в стиле MS-COM’а, в которых в качестве идентификатора использовались числовые значения и не натыкался на проблему описанную выше, да и слухов, что у кого-то она возникала тоже не было. Следовательно, в данной реализации будет использован числовой идентификатор. Кроме производительности от такого решения есть еще один положительный момент: с числовым идентификатором в момент компиляции можно сделать много интересного, так как манипулировать строками как параметрами шаблонов не получится, а числом легко. И тут как раз первый плюс C++ 11 будет использован — это constexpr, с использованием которого можно вычислять значения хэшей в момент компиляции.

Кроссплатформенность и поддержка со стороны языка

Описываемая модель будет кроссплатформенной. Кросс чего-то там — это один из интересных моментов в разработке. Для C++ разработчика одна из наиболее понятных задач — это поддержка кроссплатформенности, но реже встречаются и задачи, связанные с кросскомпиляцией, так как то, что легко поддерживается одним компилятором, на ином может не поддерживаться. Один из таких примеров — это до появления реализации decltype попытки реализации получения типа выражения в момент компиляции. Хороший пример — BOOST_TYPEOF. Если заглянуть «под капот» BOOST_TYPEOF, вы обнаружите немалый набор палок и костылей, так как подобные вещи не могли быть реализованы с использованием C++ 03, а решались в основном на каких-то расширенных возможностях конкретного компилятора. Так же в C++ 11 расширилась стандартная библиотека, которая дала возможность отказаться от написания собственных оберток над потоками, объектами синхронизации и т.д. За библиотечные функции по поддержке типов разработчикам стандарта можно сказать отдельное спасибо, так как избавили от необходимости написания своего кода во многих случаях, и, самое главное, дали реализацию таких методов как std::is_pod и прочих, реализовать которые стандартными средствами языка C++ 03 без использования расширений компиляторов было невозможно.

Использовать ли сторонние библиотеки

Было желание минимизировать использование сторонних библиотек и по возможности свести их использование к нулю. Это будет разработка на чистом C++. При реализации конечных компонент может быть использовано что угодно в силу задачи, любые библиотеки, но сама модель, приводимая здесь, будет в смысле использования сторонних библиотек чистой.

К использованию сторонних библиотек у меня сложилось определенное отношение: не использовать библиотеку в проекте если ее функционал не используется клиентским кодом максимально возможным образом. Не стоит тащить в проект Qt только потому что некоторым нравится использовать QStrung и QList. Да, встречал проекты, в которых были притянуты за уши некоторые библиотеки и фреймворки только ради того, чтоб использовать какую-то малую и неважную ее часть просто из-за привычки некоторых разработчиков. В целом же, нельзя отрицать использование таких библиотек как boost, Qt, Poco и прочих, но они должны применяться к месту, включаться в проект только когда в них есть большая необходимость. Не стоит разводить зоопарк, так, завести в проекте пару-тройку экзотических зверушек и не более :) чтоб не получить проект в котором есть штук 5-7, а то и более типов строк, 2-3 из которых — это собственные велосипеды, а остальные пришедшие из других библиотек и написанная куча конвертеров из одних реализаций в другие. Как результат разработанная программа вместо полезной работы тратит вполне может быть заметное время на конвертацию между разными реализациями одних и тех же сущностей.

Как-то привык я раскладывать код по пространствам имен. В качестве названия пространства имен и всей модели будет выбрано Boss (base objects for service solutions). Об истоках происхождения имени можно прочесть впредыдущей статье на эту тему. В комментариях к статье было отмечено, что «Boss» может смущать в коде, в силу напоминании о начальстве и стереотипах с этим связанных. Изначально не было цели сделать акцент в названии на некоторого «насяльника» (© Наша Раша). Но если у кого-то вызывает негативные ассоциации, то почему бы не посмотреть под другим углом на это? Есть замечательная книга Кена Бланшара «Лидерство к вершинам успеха», в которой описываются высокоэффективные организации и руководители-слуги, цель которых сделать максимум, чтобы работнику дать все для его работы с максимальной производительностью, а не просто стоять с палкой за спиной. Т.е. руководитель — помощник в организации эффективной работы. Boss желательно воспринимать как руководителя в высокоэффективной организации, который помогает сотрудникам достичь максимальной производительности, обеспечивая их всем необходимым для этого. В рамках компонентной модели — это именно помощь в организации тонкой прослойки для более простого взаимодействия сущностей в системе, а не монструозный фреймворк с которым надо бороться и большая часть работы направлена только на работу с ним, а не на бизнес-логику.

Минимализм в интерфейсе

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

Илон Маск рекомендует:  Что такое код setgraphmode

Множественное наследование реализаций

О множественном наследовании реализаций на просторах интернета велось и ведется немало священных войн. Я считаю, что множественное наследование — это одна из сильных сторон C++. Да, местами с ним имеются проблемы, но без него так же не всегда удается легко выкрутиться. Каждый инструмент C++ предназначен не для того, чтобы его как-то взять и использовать только потому что он есть, а когда появится необходимость, то вот и инструмент имеется.

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

Тут конечно можно развести немалую дискуссию о дизайне системы, но реальная практика не бывает столь идеалистична как теоретический дизайн кода.
Как-то на собеседовании я спросил кандидата (далеко не юнца) о том, что он знает о множественном наследовании. Ответ был примерно таков: «Да, я знаю что есть множественное наследование и, вроде бы как, есть и виртуальное множественное наследование, но это плохо. Я этим никогда не пользуюсь. И более ничего о нем сказать не могу».

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

Реализация

Как уже отмечалось, все построено вокруг интерфейсов — C++ структур с чисто виртуальными методами и некоторой примесью (идентификатором интерфейса).
Базовый интерфейс, от которого должны наследоваться все существующие в этой реализации:namespace Boss < struct IBase < BOSS_DECLARE_IFACEID("Boss.IBase") virtual

Мда, виртуальный деструктор и пара макросов… Многие воскликнут: «Макросы — это же плохо!» Да, плохо при их избытке и применении где попало. В небольших количествах и только при необходимости это бывает полезно, как яд в фармакологии — убивает и лечит в зависимости от дозировки.

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

Как идентификатор интерфейса параметром макроса передается строка. Как-то строки более приятно смотрятся в коде нежели сухие числа, да и надо иметь некоторый набор данных, от которого генерировать числовой идентификатор. В качестве идентификатора выбран crc32 от строки. И вот она сила нового стандарта: можно считать crc32 и прочие вещи от строк в момент компиляции! Такой трюк, конечно, не выйдет со строками динамически создаваемыми в программе, да и не пригодится для решения данной задачи.

Для реализации расчета crc32 потребуется некоторая таблица с данными, которую можно легко найти в интернете. С ее помощью считать crc32 можно примерно таким образом:namespace Boss < namespace Private < template struct Crc32TableWrap < static constexpr uint32_t const Table[256] = < 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, . etc >; >; typedef Crc32TableWrap Crc32Table; template inline constexpr std::uint32_t Crc32Impl(char const *str) < return (Crc32Impl (str) >> 8) ^ Crc32Table::Table[(Crc32Impl (str) ^ str[I]) & 0x000000FF]; > template<> inline constexpr std::uint32_t Crc32Impl (char const *) < return 0xFFFFFFFF; >> template inline constexpr unsigned Crc32(char const (&str)[N]) < return (Private::Crc32Impl (str) ^ 0xFFFFFFFF); >>


Почему таблица завернута в структуру, да еще и в шаблон? Чтобы избавиться от cpp-файла с определением данных, т.е. все только в подключаемом файле и без прелестей static-данных в подключаемых файлах.

Crc32 рассчитан, идентификатор сформирован. Теперь к рассмотрению того, что кроется под вторым макросом:

Однако! Неужто нельзя было просто взять и поместить три метода в структуру? Зачем макрос? И добить вопросом о наличии родственников в Индии… Но так как от множественного наследования нет отказа и, более того, оно очень приветствуется в данной модели, то для того чтобы успокоить тревоги компилятора о том что ему не понятно из какой ветки наследования брать какой-нибудь из методов, описанных под макросом, этот макрос будет использован еще в нескольких местах.

Управление временем жизни объектов реализовано через подсчет ссылок. В функции интерфейса IBase входят методы для работы со счетчиком ссылок и метод для запроса интерфейсов у объекта.

Пример определения пользовательского интерфейса:
struct IFace : Boss::Inherit < BOSS_DECLARE_IFACE >Почти все понятно: интерфейс, объявление его методов, определение идентификатора. Но почему просто не сделать наследование от Ibase?

Второй пример пользовательского интерфейса, чтоб дальнейшее объяснение было понятнее:

Теперь все раскрылось? Нет? Все просто: при наличии множественного наследования даже только интерфейсов нужно как-то иметь возможность их «обходить» в поисках нужного при реализации QueryInterface. Случай немного эзотерический, но иногда я натыкался на подобное. Предположим у вас есть указатель на IFace3, понятно, что все методы его базовых классов вы можете тут на месте и вызвать. А если передать его в иную функцию, более обобщенную, которая от некоторого интерфейса, необязательно с такой структурой наследования, всегда запрашивает IFace1 или IFace2, то она уже не на C++ механизмы опирается, а на реализованный QueryInterface, реализации которого надо как-то эту иерархию обойти. Вот тут-то и пригождается некоторая примесь: Boss::Inherit, которая имеет следующую реализацию:

Inherit() <> typedef std::tuple BaseInterfaces; BOSS_DECLARE_IBASE_METHODS() >; >

Данная примесь просто наследуется от переданного списка базовых интерфейсов, «успокаивает» компилятор от неразборчивости выбора нужного метода (использование BOSS_DECLARE_IBASE_METHODS) и «прикапывает» список унаследованных интерфейсов. Тут новый стандарт дает такое преимущество как шаблоны с переменным количеством параметров. Ура, дождались! Ранее это решалось через громоздкие списки типов в стиле Александреску. Ну также тут новые «плюсы» еще дали бонус в виде кортежа, избавив от написания своего аналогичного велосипеда.

Как, от чего и почему определить пользовательские интерфесы рассмотрено, но их где-то и как-то надо реализовывать. Для начала маленький пример реализации интерфейсов:
struct IFace1 : Boss::Inherit < BOSS_DECLARE_IFACE ), IFace1> < public: virtual void Mtd1() < // TODO: >>;

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

Трудно не заметить, что каждая реализация наследуется от CoClass. CoClass имеет весьма простую реализацию:
namespace Boss < template class CoClass : public virtual Private::CoClassAdditive , public T . < public: typedef std::tuple BaseEntities; CoClass() : Constructed(false) < >// IBase BOSS_DECLARE_IBASE_METHODS() private: template friend void Private::SetConstructedFlag(Y *, bool); template friend bool Private::GetConstructedFlag(Y *); bool Constructed; >; > Данный класс, так же как и структура Inherit, наследуется от списка переданных сущностей, «прикапывает» этот список наследования, наследуется от некоторой

(по которой будет производится классификация сущностей: интерфейс или реализация), так же избавляет от неразборчивости компилятор (подпихнув ему методы через BOSS_DECLARE_IBASE_METHODS) и содержит признак сконструированности объекта (Constructed).

Есть интерфейсы, есть их реализации, но пока не было реализации IBase. Реализация этого интерфейса, пожалуй будет одной из сложных.

Для создания объекта из приведенного выше большого примера будет выглядеть примерно так: auto Obj = Boss::Base ::Create();
Boss::Base — это класс-реализация Boss::IBase. В реализации для выполнения тех или иных операций придется обходить иерархию класса. Так для примера, приведенного выше, упрощенная иерархия будет выглядеть так:

Обход иерархии классов в поисках нужного ненадолго отложу. Быстро пройдусь по более простым методам.

Подсчет ссылок осуществляется вызовом методов AddRef (увеличивает счетчик ссылок) и Release (уменьшает счетчик ссылок и при достижении нуля удаляет объект, делая delete this). Так как предполагается возможность использования объектов в многопоточной среде, то работа со счетчиком осуществляется через std::atomic, что позволяет атомарно увеличивать и уменьшать счетчик в многопоточной среде. Да, наконец-то C++ признало существование потоков и появилась поддержка работы с потоками и примитивы синхронизации.

Метод Create имеет такую реализацию:

template static RefObjPtr Create(Args const & . args)

Наличие шаблонов с переменным количеством параметров позволяет создать метод для конструирования объектов и в конструктор передавать нужные параметры. Ранее такого сделать нельзя было и если объект нуждался в каких-то изначальных настройках, то приходилось создавать объект, у которого был некоторый метод (свой специфичный) типа Init, в который необходимое и передавалось.

Управляет счетчиком ссылок модуля. Имеется два счетчика ссылок — это счетчик ссылок непосредственно у объекта и счетчик всех ссылок модуля. Счетчик ссылок модуля нужен для того, чтобы можно было понять, когда в модуле есть «живые» объекты, а когда нет ни одного и модуль можно выгрузить.

Чтобы отказаться от статических библиотек и реализовать паттерн «одиночка» (для каждого из модулей) нужно для сущности ModuleRefCounter реализовать его только во включаемом файле, то тут вполне пригодится трюк с шаблонами и статическими объектами. Более подробно об этом можно прочесть в предыдущей статье. Кратко описать это можно так: если создать шаблон типа со статическим полем и инстанцировать его любым типом, то экземпляр этого объекта будет единственный на весь модуль. Получается небольшая хитрость, применяемая для написания одиночек во включаемых файлах без реализации где-то в cpp-файле (одиночки в include’ах).
И в этом красивом решении есть грабли, детские грабли: черенок в два раза короче, бьет точнее и больнее… Это решение прекрасно работает в .dll, но в .so поймал проблему: шаблон со статическими полями, инстанцированный одним и тем же типом стал одним на все .so с компонентами данной модели в рамках процесса! Почему я немного позднее осознал, но пришлось от красивого решения отказаться в пользу более простого, основанного на безымянных пространствах имен и включаемом файле, который в каждый модуль включается не более одного раза (кому интересно — boss/include/plugin/module.h).

Язык C++ многие считают языком, позволяющим легко «выстрелить себе в ногу». И, как правило, часто гонения на него идут именно из-за учета парности операций по выделению/освобождению ресурсов, а в частности памяти. Но если использовать умные указатели, то одной головной болью становится меньше. RefObjPtr как раз и является умным указателем, вызывающим AddRef и Release для управления временем жизни объекта и в программе при его использовании, методы AddRef и Release не должны встречаться в пользовательском коде.

Такая плюшка нового стандарта как r-value позволяет писать более оптимальные сущности; например, все тот же RefObjPtr возвращать объект не вызывая лишний раз AddRef/Release на конструкторах копирования (return std::move(NewInst)).

Есть еще в Create и обращение к некому FinalizeConstruct. Что это и зачем? Предположим у вас есть иерархия, примерно не проще приведенной на рисунке выше и в одной из реализаций интерфейса нужно что-то вызвать, что определено в классе уровнем ниже. Можно использовать виртуальные функции, но, если сказать по-простому, то в конструкторе еще нет виртуальной таблицы функций, а в деструкторе ее уже нет. Все вызовы виртуальных функций будут как вызовы обычных функций класса и вызвать переопределенную функцию уровнем ниже не получится из конструктора. На этот случай и сделан FinalizeConstruct, который будет вызван после того как объект уже полностью создан. Получается, что надо реализовать некоторую логику, аналогичную логике вызова конструкторов, только своими силами, т.е. обойти всю иерархию и вызвать FinalizeConstruct у каждого класса в том порядке, в котором и вызываются конструкторы.

Разработчик класса не обязан определять FinalizeConstruct в своем классе. При обходе иерархии классов реализованная в модели логика FinalizeConstruct определит с помощью старого доброго SFINAE наличие в классе FinalizeConstruct и при наличии этого метода вызовет его. Основное правило: реализация в пользовательском коде FinalizeConstruct не должна быть виртуальной! В противном случае получится неразбериха при построении сущностей из готовых кубиков.
Наличие в классе FinalizeConstruct определяется таким кодом:
template > struct CheckMtd < typedef Yes Type; >; template static typename CheckMtd ::Type Check(U const *); static No Check(. ); public: enum < Has = sizeof(Check(static_cast (0))) == sizeof(Yes) >; >;

Вся логика по вызову FinalizeConstruct

построена на частных специализациях шаблонов и хождению по иерархии через «прикопанные» кортежи с типами базовых классов. Стандартная библиотека стала иметь средства по работе с типами, так, для определения того принадлежит ли класс к классу реализации, можно теперь использовать std::is_base_of, а не писать свою реализацию. Так же вместо списков типов в стиле Александреску можно использовать std::tuple.

Аналогия конструкторам готова, а как же аналогия деструкторам? Куда же без нее. В модели реализована логика по обходу иерархии классов в порядке вызова деструкторов, поиска в классе реализации все через тот же SFINAE метода BeforeRelease и при наличии вызова его. Реализация логики по работе с BeforeRelease аналогична логике FinalizeConstruct, но только в обратном порядке обход.

Теперь есть возможность доконструировать объект после его полного создания и высвободить что-то перед разрушением объекта. Но в конструкторе можно сообщить о проблеме, кинув из него исключение. Такое же поведение реализовано в данной модели: в любом методе FinalizeConstruct в иерархии можно кинуть исключение и остальная цепочка FinalizeConstruct уже не будет вызываться, кроме того, для объектов иерархии, для которых FinalizeConstruct уже прошел успешно будет вызван BeforeRelease. Получается полная аналогия конструкторам и деструкторам C++. BeforeRelease вызывается из реализации метода Release и при обходе иерархии BeforeRelease будет вызван только для тех объектов, для которых прошел успешный вызов FinalizeConstruct, а успешность вызова определяется флагом Constructed, находящегося в CoClass’е (помните?). Так же стоит отметить, что если нет необходимости в парности этих методов в классе, то может присутствовать только один, если он вообще нужен.

Осталось реализовать логику

которая по большому счету не особо отличается от обхода иерархий, описанному выше. При обходе иерархии и при встрече в дереве класса реализации берется его «прикопанный» список базовых сущностей и рекурсивно обходится он в поисках нужного интерфейса. Есть одно дополнение. Так как есть возможность работы с интерфейсами, множественно наследованными от других интерфейсов, то при встрече в иерархии поиска интерфейса, он обходится в поиске нужного интерфейса аналогично обходу класса реализации, но только с «прикопанным» списком только одних интерфейсов.

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

Ядро готово! Все самое сложное и интересное описано. Далее будет все гораздо проще и уже все ровнее, без головоломок.

Плагины

В данной части речь пойдет по организации плагинов. Под плагинами стоит понимать в текущем контексте динамические библиотеки (.so / .dll), в которых размещаются классы-реализации интерфейсов (компоненты) и небольшой набор функций для доступа к объектам этих классов-реализаций.

Эта часть статьи, на мой взгляд, самая простая, так как тут нет никакого «программирования на шаблонах» и прочего глумления над компилятором. Всего лишь создание некоторого набора интерфейсов и реализаций для организации системы плагинов.

Для «жизни» компонент в своих жилищах (плагинах) в рамках одного государства, именуемого процессом, нужно не так много:

  • Реестр плагинов / компонент / сервисов
  • Фабрика классов
  • Загрузчик

Реестр сервисов — место хранения всей информации о сервисе:

  • Идентификатор сервиса
  • Список содержащихся в плагине классов-реализаций
  • Путь к модулю (so / dll) в случае плагинов, обитающих в рамках одного процесса
  • Некоторая информация по загрузке Proxy/Stub’ов и организации канала связи между клиентом и сервером. Это немного забег вперед в преодоление границ процесса

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

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

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

Но сам класс реализации реестра сервисов может поставлять несколько интерфейсов. Ради чего все затевалось? Делать наборные компоненты.

т.е. реализация предоставляет интерфейс для манипулирования реестром (IServiceRegistryCtrl,) и его загрузкой и сохранением (ISerializable).


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

Код загрузчика весьма прост, но, к сожалению, С++11 мало признал платформу (ОС). Многопоточность они признали, а вот существование таких вещей как динамические библиотеки пока нет. Так что для загрузки модулей будет использоваться код, зависящий от операционной системы. Конечно же запрятанный глубоко. Тут неплохо было бы вспомнить про pImple, но так как взят курс на отказ от статических библиотек, то будет немного иное: реализация для каждой ОС в своем заголовочном файле и файл-интерфейс, разбирающий что включить на основе макросов __linux__ и _WIN32.

Маленький пример использования сервисов в рамках модели плагинов, обитающих в одном процессе:

Теперь попробую описать, что здесь происходит…
Для создания класс-реализации одного или нескольких интерфейсов нужно создать класс производный от класса-шаблона CoClass. Этот класс в качестве параметров принимает идентификатор класс-реализации (который уже может быть использован при создании объекта через фабрику классов) и список наследуемых интерфейсов или уже готовых реализаций интерфейсов. Если взглянуть на приведенный класс-реализацию реестра сервиса, то в ней как раз и виден идентификатор (Service::Id::ServiceRegistry) и далее перечислены интерфейсы, которые реализованы в этом классе (IServiceRegistry — интерфейс реестра сервисов, который будет использован фабрикой классов; ISrviceRegistryCtrl — интерфейс управления реестром; ISerializable — реестр должен быть куда-то сохранен и откуда-то загружен и этот интерфейс позволяет выполнять требуемое). На этом вся работа по созданию компонента закончена и нужно всего лишь реализовать его методы.

Компонент готов. Осталось его как-то опубликовать, т.е. дать возможность доступа к нему извне модуля, в котором он находится.

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

Еще один аналогичный пример: реализация фабрики классов, который так же уже был приведен выше.

Полностью аналогичный пример. Так же наследование от CoClass, идентификатор и список реализуемых интерфейсов. Фабрика классов размещена в отдельном модуле, соответственно она имеет и свою

аналогичную точке входа реестра сервисов.

Это были простые реализации компонент, в которых каждая компонента наследовала только список интерфейсов, реализовывала их методы и все. Не было наследования уже готовых реализаций. А если Вы посмотрите еще раз на интерфейс реестра сервисов, то в нем Вы увидите работу с IServiceInfo, через который и передается вся информация. IServiceInfo может передавать только общую информацию о сервисе, но есть и частная. Изначально хотел сделать плагины, живущие не только в динамических библиотеках, но и разбросанные по процессам, в своих исполняемых модулях. Отсюда и разная информация: для плагинов в динамических библиотеках только дополнение о пути к ней, а для плагинов в отдельных исполняемых модулях куча дополнительной информации: информация о Proxy/Stub’ах, транспорте и т.д. (но, к сожалению, я не довел эту часть до конца, а зачатки отрезал, чтоб не засорять код недоделками). Теперь как раз и приведу пример, в котором уже компоненты наследуются не только от интерфейсов, но и от реализаций.

Реализация ServiceInfo может показаться немного сложноватой. Зачем и тут шаблон? Это уже тонкость реализации структуры данных, которая мне пришла в голову, а не дань, отдаваемая компонентной модели / плагинной системе. Чтобы немного прояснилась причина такой реализации, приведу интерфейс:

Чуть более понятная реализация с наследованием интерфейсов и реализаций приведена в описании ядра с причудливым классом Face123456 без всяких шаблонов :)

Как компоненты реализовывать все прояснилось. Все просто. А как запрашивать и работать с интерфейсами, запрашивать из одного другой — можно рассмотреть на примере загрузчика, который загружает реестр сервисов, получает из него нужные интерфейсы, настраивает этот самый реестр, загружает фабрику классов и настраивает ее на работу с реестром. Далее, конечно, вся работа клиента идет уже с фабрикой классов и клиент уже не должен работать с модулями, а иначе ради чего вся эта абстрактность затевалась-то.

Кроме приведенных примеров, актуальны примеры из статьи с реализацией предыдущей версии на C++ 03. Единственное отличие — работа с идентификаторами. В новой модели не надо добавлять отдельным макросом в класс-реализацию, про который можно забыть. Если в новой модели забыть про идентификатор, то компилятор об этом Вам напомнит, так как теперь это параметр шаблона.

Заключение

Была некоторая большая задумка, но реализовалась только на 2/3:

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

Как-то так сложилось, что наиболее интересен для меня момент построения каркаса или скелета системы, а вот наращивание мышц и вливание жира (разработка всяких полезностей / псевдополезностей) это уже может иногда быть работой, которая выполняется весьма быстро в силу хорошей осведомленности в системе. В силу этого получилось весьма полное (местами может быть избыточно полное) ядро (сферические кони в вакууме меня всегда привлекали). Есть небольшая часть мышц (основные компоненты плагинной системы: реестр сервисов и фабрика классов), чтобы модель хоть как-то могла существовать. Но данная реализация получилась полностью обезжиренной: в ней нет ничего вспомогательного. Собран скелет системы, наращено немного мышц и дан пинок под зад, чтоб это хоть как-то двинулось с места — стало материалом и статьей Хабра.

Проект обязательно должен быть или выпущен, или прекращен как можно ранее, пока он не съел все ресурсы и благополучно не пропал из поля внимания. В силу этого суждения и того, что материал статьи получился великоват и, возможно, местами сложноват, и той причины, что мне не удалось больше полугода уделить внимания данной статье, часть с плагинами пока отсутствует. Скоро может например появиться C++14 и тогда материал этой статьи, посвященный C++11 уже может стать неактуальным. Вполне может быть нереализованная часть выйдет отдельным постом… Этот материал будет базироваться на материале статьи «Proxy/Stubs своими руками, который я хотел переработать с учетом стандарта C++11, добавить маршалинг интерфейсов и подложить под это все транспорт (реализовать один из механизмов IPC).

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

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

Рекомендуемые ссылки

Ссылки по теме

Популярные статьи
Информационная безопасность Microsoft Офисное ПО Антивирусное ПО и защита от спама Eset Software


Бестселлеры
Курсы обучения «Atlassian JIRA — система управления проектами и задачами на предприятии»
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год. Электронный ключ
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2020. Все языки. Электронный ключ
Курс «Oracle. Программирование на SQL и PL/SQL»
Курс «Основы TOGAF® 9»
Microsoft Windows Professional 10 Sngl OLP 1 License No Level Legalization GetGenuine wCOA (FQC-09481)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Windows Server 2020 Standard
Курс «Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации»
Антивирус ESET NOD32 Antivirus Business Edition
Corel CorelDRAW Home & Student Suite X8

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

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

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

Основы

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

Первое что нужно для создание плагина — это создать отдельный каталог (папку) для содержимого плагина, например: my-plugin-name . В этой папке будут все файлы плагина. Среди них особое место занимает главный файл плагина, который желательно, должен совпадать с названием самой папки плагина, например my-plugin-name.php . В результате должна получиться такая структура: wp-content/plugins/my-plugin-name/my-plugin-name.php .

После создания файла плагина, в него нужно поместить заголовки, чтобы wordpress распознал плагин как плагин. Для этого нужно указать его имя так:

После сохранения, плагин можно увидеть в списке плагинов на сайте WordPress. Войдите в админку WordPress в раздел Плагины (слева в навигации админки). Там вы должны увидеть свой новый плагин!

Если плагин является всего одним файлом как Hello Dolly, то он мог бы находится непосредственно в папке плагинов. Но рекомендуется соблюдать стандарт: файлы плагина должны находиться в своей собственной папке, а главный файл плагина должен иметь название папки плагина.

Плагин может быть личный (создается только для одного сайта), а может быть публичный (для общего пользования, выкладывается в репозиторий плагинов WordPress).

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

Лицензия сообщает пользователям, как они могут использовать код плагина в своих целях. Для поддержания совместимости с ядром WordPress рекомендуется выбрать лицензию, работающую с GNU General Public License (GPLv2+).

Существуют и другие параметры в заголовке

Описание дополнительных параметров улучшит отображение плагина в консоли WordPress. Если планируете отправлять плагин на WordPress.org, нужно следовать требованиям к заголовку плагина WordPress.

Шаблон для разработки WordPress плагинов

Чтобы не создавать файлы и структуру с нуля, можно использовать шаблон для создания плагина:

  • WordPress Plugin Boilerplate — генератор шаблона, где указывается название плагина, которое будет использовано в названиях папок, классов и функций — WordPress Plugin Boilerplate Generator.

Шаблон представляет собой стандартную и организованную объектно-ориентированную основу.

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


Хуки: экшены и фильтры

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

Существует два типа хуков в WordPress:

События позволяют добавлять или изменять функционал WordPress, в то время как фильтры позволяют изменять данные или строки. Хуки нужны не только для разработчиков плагинов — они используются везде: в самом ядре WordPress, в плагинах и темах. Именно хуки делают WordPress таким гибким.

Непосредственно к плагинам относятся три функции отвечающие за хуки плагина:

register_activation_hook() — регистрирует функцию, которая будет срабатывать во время активации плагина.
Используется для добавления настроек плагина и т.п.

register_deactivation_hook() — регистрирует функцию, которая должна запускаться после деактивации плагина.
Используется для удаления временных данных плагина.

  • register_uninstall_hook() — регистрирует функцию, которая вызывается при удалении плагина.
    Используется при удалении плагин для удаления всех данных плагина: в настройках, в файлах, в базе данных и т.д.
  • Кроме этих трех функций к API плагинов относятся все функции хуков и некоторые функции плагинов.

    Можно создавать свои собственные хуки в исходном коде плагина с помощью do_action() или apply_filters(). Они позволят разработчикам расширить возможности плагина и сделают его расширяемым — таким же как ядро WordPress.

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

    Используйте WordPress API

    WordPress предоставляет ряд API. API могут значительно упростить написание кода. Т.е. не нужно изобретать колесо — оно уже есть и 100 раз улучшено.

    API настроек — упрощает создание и управление опциями плагина, которые сохраняются в базу данных.

  • HTTP API — упрощает создание HTTP запросов в PHP. Отличная замена велосипедов на cURL.
  • Как WordPress загружает плагины

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

    Из всего этого следует: просто своим присутствием плагины не влияют на скорость работы сайта (за исключением подключения файла плагина, а это супер быстрая операция). Неактивные плагины не влияют вообще никак. Подробнее читайте здесь.

    Здравствуйте. Объясните пожалуйста, с какой целью создают и размещают плагины в бесплатный каталог WordPress? Какая польза от этого разработчику?

    1. Чаще, для самореализации, самоутверждения, желание поделиться.
    2. Реже, чтобы была возможность обновляться, когда свой плагин юзаешь на разных сайтах. Тут еще плюшки, пользователи могут предложить норм идеи для развития плагина, помогут найти баги.
    3. Реже, чтобы продавать PRO версию.

    95% плагинов урезаны в функциях и заявленный функционал доступен только при покупке PRO версии. У некоторых особо отбитых плагин вообще ничего не делает, кроме как перенаправляет на сайт автора, где его можно купить.
    Остальные 5% это либо альтруисты, либо большие плагины типа woocommerce, которые зарабатывают тем, что продают темы для магазинов и доп рюшечки и т.д.

    Здравствуйте.
    опишите пожалуйста процесс обновления плагина,
    то есть, создан плагин и подключен с версией, например 1, и как осуществить процесс обновления, чтоб вордпрес увидел что появилась новая версия и предложил администратору обновить плагин

    Для этого нужно добавить плагин в каталог плагинов WordPress. Или нужно написать свой сервер обновлений и держать плагины там, но это уже совсем другая история.

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

    Здравствуйте, исправьте пожалуйста ссылку wppb.me на wppb.io

    Добрый день. Спасибо, поправлено!

    Есть ли способ, внутри плагина, определить, что на сайте уже установлена ранняя версия и происходит именно обновление плагина, а не его установка?

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

    Подскажите пож-та, как я могу изменять плагин, не изменяя кода самого плагина?
    Допустим есть такой код, и я хочу использовать свой префикс в ЧПУ:
    ‘rewrite’ => array(
    ‘slug’ => $slug?$slug:’team’

    Исходник:
    register_post_type( ‘joomsport_team’,
    apply_filters( ‘joomsport_register_post_type_team’,
    array(
    ‘labels’ => array(
    ‘name’ => __( ‘Team’, ‘joomsport-sports-league-results-management’ ),
    ‘singular_name’ => __( ‘Team’, ‘joomsport-sports-league-results-management’ ),
    ‘menu_name’ => _x( ‘Teams’, ‘Admin menu name Teams’, ‘joomsport-sports-league-results-management’ ),
    ‘add_new’ => __( ‘Add Team’, ‘joomsport-sports-league-results-management’ ),
    ‘add_new_item’ => __( ‘Add New Team’, ‘joomsport-sports-league-results-management’ ),
    ‘edit’ => __( ‘Edit’, ‘joomsport-sports-league-results-management’ ),
    ‘edit_item’ => __( ‘Edit Team’, ‘joomsport-sports-league-results-management’ ),
    ‘new_item’ => __( ‘New Team’, ‘joomsport-sports-league-results-management’ ),
    ‘view’ => __( ‘View Team’, ‘joomsport-sports-league-results-management’ ),
    ‘view_item’ => __( ‘View Team’, ‘joomsport-sports-league-results-management’ ),
    ‘search_items’ => __( ‘Search Team’, ‘joomsport-sports-league-results-management’ ),
    ‘not_found’ => __( ‘No Team found’, ‘joomsport-sports-league-results-management’ ),
    ‘not_found_in_trash’ => __( ‘No Team found in trash’, ‘joomsport-sports-league-results-management’ ),
    ‘parent’ => __( ‘Parent Team’, ‘joomsport-sports-league-results-management’ )
    ),
    ‘description’ => __( ‘This is where you can add new team.’, ‘joomsport-sports-league-results-management’ ),
    ‘public’ => true,
    ‘show_ui’ => true,
    ‘show_in_menu’ => (current_user_can(‘manage_options’)?’joomsport’:null),
    ‘publicly_queryable’ => true,
    ‘exclude_from_search’ => false,
    ‘hierarchical’ => false,
    ‘query_var’ => true,
    ‘supports’ => array( ‘title’,’thumbnail’ ),
    ‘show_in_nav_menus’ => true,
    ‘capability_type’ => ‘jscp_team’,
    ‘capabilities’ => array(
    ‘edit_post’ => ‘edit_jscp_team’,
    ‘edit_posts’ => ‘edit_jscp_teams’,
    ‘edit_others_posts’ => ‘edit_others_jscp_team’,
    ‘publish_posts’ => ‘publish_jscp_team’,
    ‘read_post’ => ‘read_jscp_team’,
    ‘delete_post’ => ‘delete_jscp_team’,
    ‘delete_posts’ => ‘delete_jscp_team’
    ),
    ‘map_meta_cap’ => true,
    ‘rewrite’ => array(
    ‘slug’ => $slug?$slug:’joomsport_team’
    )

    Создание приложения с полной поддержкой плагинов

    Опубликовано 28 декабря 2009 14:43 | Coding4Fun |

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

    С момента создания моей первой версии приложения Utility Runner прошло уже несколько месяцев. Utility Runner позволяет выполнять системные утилиты с минимальными издержками: вместо кучи индикаторов на панели задач, бесчисленных EXE-файлов (и вызванных ими неудобств, связанных с памятью и временем запуска) это приложение управляет множеством утилит из одного места.

    Чтобы открыть это решение, вам понадобится, как минимум, Visual Studio 2008 Express Edition (Visual C# или Visual Basic). Если у вас еще нет этой среды разработки, вам стоит позаботиться о ее получении!

    Функциональность приложения мало изменилась по сравнению с предыдущей версией, если не считать некоторой ее переработки в связи с выпуском новой версии MEF. Начнем с того, что атрибут, с помощью которого помечается экспортируемая функция, больше не является запечатанным (sealed), поэтому теперь вы можете создать подкласс атрибута, охватывающий имя экспортируемой функции и любые другие включенные в нее метаданные. Такой новый атрибут пользователи смогут применять просто для более строгой типизации. В данном случае я создал класс WpfServiceMetadata. Подробности о нем написано дальше.


    Большая проблема для меня заключалась в том, чтобы реализовать диспетчер для поддержки множества надстроек-утилит — как в Firefox. MEF позволяет легко помечать, какие классы являются надстройками и как они должны быть связаны с вашим кодом в целом, но мне хотелось большего. Поразмыслив, я счел, что мои цели таковы:

    • возможность загрузки надстройки через UI;
    • возможность отключения/включения надстроек;
    • возможность удаления надстройки.

    Достичь этих целей можно самыми разными способами. Мой способ — отнюдь не идеален, но работает вполне нормально.

    Загрузка надстроек в основном обрабатывается самой инфраструктурой MEF, тем не менее, кое-какая ручная работа все же потребуется. Я решил, что вместо проверки папки на наличие нужных DLL, как в первой версии, лучше использовать простой формат упаковки и помещать надстройку и связанные с ней файлы в один контейнер.

    В качестве контейнера я задействовал Zip-файл с расширением, переименованным в .util. Этот файл содержит, как минимум, DLL самой надстройки, но может включать связанные библиотеки, изображения, звуки и другие ресурсы. Одно время я создавал файл манифеста, предоставляющего информацию о надстройке, которая не требовала загрузки класса, но потом предпочел не усложнять простые вещи.

    Перед загрузкой DLL с надстройками класс AddinManager сканирует папку надстроек на наличие файлов с расширениями .util и распаковывает их в одноименные папки. Каждая из таких папок добавляется в список папок, которые просматриваются MEF. После этого исходный файл удаляется.

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

    Если пользователь щелкает кнопку Add New Addin From File, он фактически открывает диалоговое окно для поиска файла. Выбранный файл копируется в папку Addins, и приложение уведомляет пользователя о том, что новая надстройка будет загружена при следующем запуске приложения. Было бы здорово загружать надстройку немедленно, и MEF поддерживает это, но ради простоты я решил не применять динамическое управление надстройками.

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

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

    Для отключения надстройки достаточно переименовать папку надстройки. Однако это невозможно в период выполнения, так как все загруженные DLL блокируются в файловой системе. В качестве альтернативы я создал файл (нулевой длины) с тем же именем и добавил к нему расширение .disable. Проверка показала, что этот вариант вполне работоспособен.

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

    Чтобы создать надстройку, вам нужно, во-первых, реализовать интерфейс IWpfService, а во-вторых, добавить атрибут WpfServiceMetadata с именем вашей надстройки. После этого не забудьте реализовать все методы интерфейса. Метод Start вызывается при инициализации (старайтесь не проводить много времени в конструкторе), а Stop — в конце, при очистке. Обновляя Status, обязательно генерируйте событие StatusChanged.

    Наконец, вам понадобится скопировать DLL своей надстройки в папку с именем этой надстройки и расширением .util. (Да, именно так: расширение присваивается имени папки.) Я делаю это в Visual Studio с помощью события, генерируемого после сборки проекта. В режиме отладки приложение ищет папку Addins в текущем каталоге. При нормальном запуске (например, после установки) просматривается локальный профиль пользователя.

    Когда вы будете готовы к распространению надстройки, заархивируйте DLL и любые сопутствующие файлы в ZIP-файл, а затем переименуйте расширение .zip в .util. Этот файл можно загружать со страницы Addins в параметрах приложения или вручную перемещать его в папку MefUtilRuner\Addins, создаваемую в папке вашего локального профиля (c:\users\<USERNAME>\AppData):

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

    Основное окно создает экземпляр класса TimedQueue. Всякий раз, когда надстройке нужно вывести сообщение, оно добавляется в набор. Когда срабатывает событие ItemAvailableEvent, оно направляется UI-потоку для отображения сообщения. Диспетчеризация предотвращает проблемы прямой передачи сообщений между фоновым потоком TimedQueue и UI-потоком.

    Хотя этот экземпляр используется для сообщений состояния, вы можете применять TimedQueue и для других целей, когда есть необходимость в удалении устаревших сообщений. Один из примеров — «регулировка» пользовательского ввода в игровой программе, где вряд ли нужно разрешать постоянный огонь из оружия или слишком частые прыжки.

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

    Кроме того, для развертывания утилит была бы полезна технология ClickOnce. Она удобна и понятна пользователю, но у нее есть своя цена: ClickOnce сильно ограничивает то, как приложения могут взаимодействовать с системой. Приложения всегда устанавливаются индивидуально для конкретного пользователя и помещаются в «секретные» папки. Разработчики не могут считывать или записывать на жесткий диск — только в защищенное хранилище (аналогично iPhone, полагаю). Не уверен насчет других ограничений, но и это может создать трудности.

    Также было бы замечательно иметь возможность добавлять надстройки в период выполнения. Это сравнительно простая функция, но у меня пока не дошли руки до нее. При этом можно было бы ожидать и реализации включения/отключения/удаления в период выполнения. Увы, истинное отключение и удаление на самом деле невозможны. Конечно, я мог бы вызывать Stop для конкретной утилиты и удалять ее из UI (и при следующем запуске не вызвать Start для нее), но на практике эта утилита по-прежнему выполнялась бы до перезапуска приложения. Принудительно удалить ее нельзя, если не использовать раздельные домены приложения, а возиться с ними мне совсем не хочется.

    Наконец, мне очень хотелось бы создать хранилище наподобие «магазина приложений». В нем можно было бы публиковать разнообразные утилиты вроде гаджетов для боковой панели или Windows Live Writer Plugins, чтобы пользователи могли их просматривать, читать информацию и устанавливать одним щелчком.

    На данный момент в приложении хватает острых углов, но процесс шлифовки потихоньку движется. Его исходный код полностью доступен, и я готов выдать права на его модификацию любому, кто заинтересован в ускорении его развития. Просто черкните мне пару строк!

    Arian Kulp — разработчик ПО, живет в Западном Орегоне. Создает примеры, демо-ролики, лабораторные занятия и пишет статьи, выступает на различных мероприятиях, посвященных вопросам программирования, а также с удовольствием проводит свободное время со своей семьей.

    Система плагинов для своего сайта на PHP

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

    Теперь переходим к созданию самой системы плагинов. Создаем папку, куда будем заливать все свои плагины, например plugins. Наша задача получить содержимое этой папки, для этого будем использовать функцию scandir(). Затем в цикле foreach() будем проверять соответствует ли файл нашим требованиям, и если да, то подключаем файл при помощи функции include().
    Код системы плагинов:

    Если так, все все сделано правильно.

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

    Пример системы плагинов

    Как быстро летит время, казалось бы, только вчера вышел RPG Maker MV, а уже пролетело 2 года. За это время много что изменилось, выходили новые версии самого редактора, поменялось много чего в самом движке MV, а главное за эти два года мои личные знания тоже изменились от «ну эт вот тут такая штука» до «класс наследует свойства родителя».

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

    Начну, пожалуй, с редакторов. В своё время мне понравился редактор «Brackets». Не скажу, что он плох, но бывает немного порой медлительным. Как альтернативу стоит упомянуть как минимум «Visual Studio Code» и «Sublime Text». Не буду на них особо заострять внимание, скажу лишь то, что в «Visual Studio Code» очень большая база всевозможных плагинов и дополнений (выпадающие подсказки, отладка итд), а «Sublime Text» на сегодняшний день однозначно самый быстрый редактор, с не менее большими возможностями.

    При желании я думаю вы и сами с ними разберётесь, в крайнем случае можно писать скрипты и в обычном блокноте.

    Но вернёмся к RPG Maker MV. Как пример мы будем рассматривать всё тот же плагин отображения предметов на экране «ItemsOnMap». С недавних пор в MV немного изменилось окно отображения параметров плагинов, и мы рассмотрим всё это более подробно.

    Начнём с самого простого, с оформления нашего нового скрипта. Для этого создадим новый файл «MUR_ItemsOnMap_2.js» в папке нашего проекта «js/plugins/MUR_ItemsOnMap_2.js» с таким вот минимальным кодом:

    Этого вполне достаточно, чтобы наш плагин вот так отображался:

    Пройдёмся более детально по частям:

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

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

    Здесь в дальнейшем будет расположен код нашего плагина. Обрамление function необходимо для того, чтобы ограничить область видимости нашего скрипта. То есть все переменные и функции, описанные здесь не будут доступны в других плагинах, а при совпадении названий, они не будут заменены значениями других переменных и функциями, описанными в других плагинах. Однако есть ситуации, когда этим правилом можно пренебречь, но в данном примере мы этого рассматривать не будем.

    Вернёмся к описанию плагина. У нас имеется два поля @plugindesc и @author. Как не трудно догадаться, первое это описание нашего плагина, а второе указание его автора. Вы может там написать абсолютно любой текст, на любом языке, при условии, что ваш файл сохранён в кодировке UTF-8 (современные редакторы предлагают это делать по умолчанию). Однако есть немного неприятный момент. Дело в том, что зарубежные пользователи скорее всего не смогут понять, о чём речь. Для того, чтобы сделать наш плагин интернациональными, по крайней мере ту часть которая отвечает за описание, мы несколько усложним описание плагина. А именно сделаем две секции описаний, на английском языке и на русском:

    Теперь, если мы запустим английскую версию RPG Maker MV, то получим описание как на скриншоте выше, а если запустим русскую версию, то соответственно описание будет на русском языке:

    Рассмотрим ещё одну часть описания плагина, а именно «Help», или как в русской версии указано «Справка». Для того что бы разместить в информацию в этом блоке достаточно добавить в наш скрипт ещё одно поле @help:

    Ограничений на количество строк справки нет, и, если их будет достаточно много, сбоку просто появится полоса прокрутки(scrollbar).


    С разделом «Справка» разобрались и теперь самое время перейти к параметрам плагина. Но для начала я напомню для тех, кто не в курсе, что в составе RPG Maker MV есть такая очень важная и очень удобная штука — отладчик. Вызывается он нажатием клавиши F8 во время запуска игры из редактора.

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

    то в закладке «Console» мы увидим своё сообщение:

    А если попробуем вызвать несуществующую функцию, например «abyrvalg()»:

    то получим соответствующее сообщение об ошибке:

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

    Но вернёмся к настройкам плагина. В отличии от предыдущей версии, мы его немного усложним для программирования, но упростим настройку для пользователя. Поскольку наш плагин будет отображать количество предметов, которое есть в наличии у игрока, то логично будет запросить эти данные у системы (а точнее у инвентаря). Но в любом случае нам нужно вынести в настройки номер(идентификатор/ID) предмета данные о котором мы хотим отображать.

    Номер предмета (или его ID) указан в рядом с названием предмета в «Базе данных» (вызывается нажатием клавиши F9) в закладке «Предметы». Дальше нам нужно сделать возможность включать и отключать отображение нашего окошка. Это можно сделать двумя способами: как и в предыдущей версии урока с помощью ключа(switch), а также с помощью вызова команды плагина. В дальнейшем мы рассмотрим оба способа, но пока зарезервируем в настройках параметр с номером ключа.

    Различные ситуации требуют различного положения окна на экране, для этого мы заведём ещё два параметра, это положение на экране по оси X и положение на экране по оси Y.

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

    Пример как это было в предыдущей версии плагина, вывод во время битвы:

    Итого у нас получилось параметров:

    • itemID — номер предмета в базе данных
    • showSwitchID — номер переключателя, разрешающего отображение
    • positionX — положение окна на экране по оси X
    • positionY — положение окна на экране по оси Y
    • enableWhileMessage — показывать ли при отображении окна сообщений

    По сути нам нет необходимости «состыковывать» слова в одно, как например enableWhileMessage или писать через символ подчёркивания (enable_while_message). Мы можем писать через пробел, и система это аккуратно «скушает» и не запутается. По сути, наверное, даже можно написать на русском языке, но опять же встаёт вопрос с зарубежными пользователями. Поэтому я предлагаю писать названия на английском, а уже в разделе «Справка» давать пояснения.

    Для того, чтобы описать какой-либо параметр используются три ключевых слова, так же начинающихся с символа @: это @param — наше название параметра, @default — значение по умолчанию, а также параметр @desc (описание), содержимое которого теперь отображается в окне где задаётся значения переменной. Так же можно описать подробности в секции @help.

    Собрав всё воедино, получим такой код заголовка:

    А при настройке плагина это будет выглядеть вот так:

    Тоже самое нужно повторить, но уже для англоязычной секции (*:en).

    Для того, чтобы в нашем коде получить доступ к параметрам плагина, мы должны обратится к менеджеру плагинов и в качестве параметра передать имя файла плагина, но без расширения js:

    Далее, уже у массива params мы можем запросить интересующие значение:

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

    Например, для того, чтобы наша переменная содержала число, а не строку, необходимо вызвать функцию Number. И в качестве параметров передать строку:

    Для того, чтобы получить логическое значение мы сравним строку на наличие слова «TRUE» (истина) и если оно указано, то наша переменная получит логическое значение — true:

    Какая страшная и непонятная конструкция скажете вы и будете правы, но давайте рассмотрим её подробнее.

    В JavaScript со строкой можно делать различные преобразования: разрезать, заменять части и другие операции. В данном примере метод «.toUpperCase()» делает все буквы большими. Например, если мы присвоим значение переменной a,

    а затем вызовем метод «.toUpperCase()»,

    то значение переменной b станет “ABC”.

    Теперь для чего нам это нужно? Дело в том, что мы не знаем, как пользователь напишет значение, он может написать «true», а может и «TRUE», а может и вообще «tRuE» и в том, и в другом, и в третьем случае это будет подразумеваться одно и тоже, но строки будут не равны. Именно для этого мы приводим к единому виду «TRUE».

    Что же касается второй «страшной» конструкции, это сокращённая форма условия «If». Её можно записать в виде:

    Согласитесь, очень громоздкая конструкция для задания значения одной переменной. Поэтому её часто заменяют такой:

    Впрочем, конечно же можно немного схитрить и задавать сразу значение false по умолчанию:

    Теперь, для отладки, мы можем вывести в «Console» наши значения переменных и проверить, что всё в порядке:

    Я намеренно сокращу код, оставив только русскоязычную часть, дабы не удлинять и без того очень длинную статью.

    Убедившись, что у нас всё отлично работает и мы забираем значения настроек правильно, мы неожиданно можем столкнуться с некоторыми трудностями. К сожалению программисты зачастую бывают очень ленивы и забывают, что пользоваться плодами их творчества будут люди далёкие от программирования. Кроме того, никто не застрахован от ошибок, очепяток итд.

    Что будет если мы вместо позиции X, равной 10, напишем в настройках плагина какую-нибудь белиберду в виде текста «АБЫРВАЛГ!». Вы думаете хоть кто-нибудь проверяет в своих плагинах, а что собственно туда вводят пользователи? Конечно нет! И, как и следовало ожидать, наш плагин получит ошибочное значение NaN (Not A Number — не является числом) и при дальнейшей попытке использовать значение переменной positionX вылетит с ошибкой, а бедный пользователь будет возмущаться, что ваш плагин не работает.

    Как же в такой ситуации быть?

    Понятно, что всех критических ситуаций не учесть, но в данном случае можно хотя бы проверить значения переменных на отсутствие значение NaN и вывести предупреждение на экран:

    Как это сделать мы поговорим во второй части, а пока ограничимся предупреждением в окне Console:

    Уже практически закончив статью и перечитывая материал ещё раз, меня заинтересовала вкладка «Текст» в окне, где задаётся значение параметра.

    Мой пытливый ум сразу же решил проверить, а не может ли быть в этом месте другой тип? Например, числовой? И как показала практика достаточно задать дополнительное поле вида «@type number», чтобы стало возможным задавать цифровое значение.

    Но на этом моя тушка не успокоилась и стала экспериментировать ещё. Опытным путём выяснилось, что, если задать тип «@type boolean» можно будет задать значения в виде радиобатонов (Да/Нет). Но и это ещё не всё! Если задать, например, «@type item», то будет выпадающий список с предметами, где результатом будет ID выбранного предмета. А если задать «@type switch», то по клику откроется окошко со списком ключей и можно задать номер ключа.

    Как теперь это выглядит в коде:

    И как это выглядит визуально (в редакторе):

    Более подробное изучение данного вопроса выявило, что, начиная с версии RPG Maker MV 1.5.0 появились дополнительные ключевые слова в параметрах.


    Если у вашего плагина слишком много параметров, то их можно сгруппировать, объединив командой @parent. В нашем примере мы можем объединить X и Y позицию на экране. Для этого немного изменим структуру параметров:

    Хочу так же обратить ваше внимание, что в этом случае немного измениться и код доступа к этим параметрам:

    Так же появилась ещё одна не менее важная конструкция это @text. Она позволяет вместо названия параметра на английском языке, выводить произвольный текст (перевод). Например:

    Теперь вместо «Position» у нас будет выводится «Положение окна на экране», а вместо «X» — «Координата X». Примечательно, что для каждого языка можно задать свой текст и у нас решается проблема перевода названий параметров на другие языки.

    Про параметр @type было уже сказано выше, дополню лишь, что могут быть ещё и другие типы:

    Например, с помощью «@type note», можно задать многострочный текст. Если указан @type как number, то можно дополнительно ограничить максимальное и минимальное значение, с помощью конструкций @max и @min. А с помощью конструкции @decimals можно указать количество цифр после запятой(точки), если необходимо задать более число с плавающей точкой.

    Например, такой код:

    И как это выглядит в редакторе:

    Причём самое интересное, что с помощью стрелочек изменяется только целая часть. Дробную же надо вводить вручную.

    С помощью «@type file» можно сделать возможным выбор файла на диске, причём в параметре @dir можно указать путь где его искать. Например, «@dir audio/bgm/» позволит выбрать аудио файл в папке «audio/bgm/». Так же есть ещё один дополнительный параметр @require. Если его значение установлено в 1, то при экспорте проекта, выбранный файл будет так же включён.

    Например, такой код:

    И как это выглядит в редакторе:

    Помимо ранее рассмотренных @type switch и @type item, есть ещё возможность задать следующие:

    • @type animation — выбрать анимацию, в значении будет ID (номер);
    • @type actor — выбрать актёра(персонажа), в значении будет ID (номер);
    • @type class — выбрать класс, в значении будет ID (номер);
    • @type skill — выбрать навык, в значении будет ID (номер);
    • @type item — выбрать предмет, в значении будет ID (номер);
    • @type weapon — выбрать оружие, в значении будет ID (номер);
    • @type armor — выбрать броню, в значении будет ID (номер);
    • @type enemy — выбрать противника, в значении будет ID (номер);
    • @type troop — выбрать сцена боя, в значении будет ID (номер);
    • @type state — выбрать состояние, в значении будет ID (номер);
    • @type tileset — выбрать набор тайлов, в значении будет ID (номер);
    • @type common_event — выбрать общее событие, в значении будет ID (номер);
    • @type switch — выбрать переключатель, в значении будет ID (номер);
    • @type variable — выбрать переменную, в значении будет ID (номер);

    Так же в этих параметрах можно указать «@require 1» и тогда при экспорте проекта данный ресурс будет учтён.

    При указании типа @type boolean, так же можно ещё переопределить название пунктов с помощью @on и @off. Например, в нашем случае можно локализовать так:

    Так же можно создать и более сложные конструкции. Например, с помощью «@type select», «@option» и «@value», можно создать выпадающий список:

    В @option задаётся название которое будет отображаться в списке, а в @value указывается значение которые сохранится в параметрах, при выборе этого пункта.

    Если есть необходимость задать значение как в текстовом виде, так и выбрать готовое значение из списка, пригодится конструкция в виде «@type combo» и «@option»:

    Конструкция такая же, как и в случае с @type select.

    Если необходимо указать или выбрать несколько значений параметра, то можно создать список (List) в виде:

    • @type text[] — список строк
    • @type note[] — список много строчных значений
    • @type number[] — список чисел
    • @type variable[] — список переменных
    • @type item[] — список предметов
    • @type combo[] — список выпадающих списков
    • @type file[] — список файлов
    • @typestruct [] — список произвольных структур

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

    Вот как например выглядит код списка строк:

    и как список строк выглядит в редакторе:

    Ну и последнее, это возможность создавать свои, произвольные структуры:

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

    В заключении как теперь выглядит финальная, на данном этапе, версия плагина:

    И как это выглядит визуально:

    В заключении хочу обратить ваше внимание, что, не смотря на возможность как-то «облагородить» вводимые пользователем данные, всё так же осталась актуальна проблема ввода некорректных данных на вкладке «Текст». Например можно всё так же в координатах задать текстовое значение, а при получении данных получить ошибку в виде NaN(Not A Number).

    Из-за большого объёма материала и обилия примеров, написание кода плагина я перенесу во вторую часть.

    Готовый пример плагина с минимальным функционалом (только заголовок), можно всегда забрать на github.

    Создание простого приложения с плагинами

    Written on 04 Февраля 2007 . Posted in Visual C++

    Динамически подключаемые модули (DLL) — это модули, которые содержат функции и данные. Эти модули загружаются во время выполнения программы, использующей эти модули (хоста). В ОС Windows модули содержат внутренние и экспортируемые функции (в UNIX подобных системах все функции являются экспортируемыми). Экспортируемые функции доступны для вызова хостом, а внутренние нет. Хотя данные тоже могут быть экспортируемыми, но обычно используются экспортируемые функции для доступа данным.

    Некоторые, особенно начинающие разработчики ПО, и не представляют, что при создании приложения, уже используют внешние модули. Хотя при разработке MFC приложений этот факт более очевиден. Просто компилятор сам вставляет код, который загружает системные библиотеки, иначе любое Windows приложение было бы на 20-30 Мб больше.

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

    Создайте новое DLL приложение (Builder и VC позволяют выбрать тип при создании нового проекта).

    Каждая библиотека имеет точку входа (но можно ее и не описывать), как функция main() в обычном приложении. Вот обычное ее описание:

    При присоединении к вызвавшему ее процессу, в эту функцию передается instance. Т.е. Вы сможете использовать ресурсы библиотеки. Используйте в этой функции только простые задачи по инициализации! Эта функция очень уязвимое место в модуле.

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

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

    Например, один из возможных вариантов этих двух функций:

    Ключевые слова __declspec( dllexport ) обозначают, что функции являются экспортируемыми.


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

    Вот пример структуры, передаваемой при инициализации:

    А вот пример функции, которая находится в плагине и заполняет эту структуру:

    Функция-обработчик в плагине:

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

    Если плагин не знает переданного кода операции, он просто вернет код «Не поддерживается» и не выполнит некорректных действий.

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

    Загрузим библиотеку хостом:

    Итак, плагин загружен и готов к работе, ожидаем когда пользователь выберет пункт меню.

    Помещаем в обработчик меню следующий код:

    Вот собственно и все описание простого примера использования плагинов в своих программах.

    Я хочу добавить несколько советов для разработчиков:

    • если планируется использовать много плагинов, то разумно будет сначала получить первоначальную информацию от плагина, а затем его выгрузить из памяти. И подгружать его в случае надобности.
    • если пользователь вводит некоторые параметры в диалогах плагинов, то разумно разработать механизм централизованного хранения последних введенных параметров.
    • делайте возможность помещения плагинов в произвольные папки внутри папки плагинов. Код рекурсивного поиска плагинов приведен ниже.
    • Задайте своим плагинам отличное от «dll» расширение. Т.к. сами плагины могут использовать внешние dll библиотеки.

    А вот пример функции, которая рекурсивно находит все файлы в папке с плагинами:

    Система плагинов

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

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

    То есть нужно сделать отдельную библиотеку с апи, которую будут использовать обе стороны (программа и плагин).

    Вот такой класс придумал в этой библиотеке. Теперь я могу спокойно ее подключать к плагину и наследоваться.

    Вот у меня уже две библиотеки, одна с моим апи, другая его использует. Теперь мне нужно как-то загрузить сборку с плагином и использовать его методы. Вот тут мне и нужна помощь.

    Так как библиотека плагина использует другую библиотеку с апи, я не смогу ее так просто загрузить, так как выскочит ошибка мол либе не хватает референсов. Как правильно загрузить плагин указав что сборка которая ему нужна уже загружена в программу?

    Вот код которым я пытаюсь что-то наколдовать:

    Падает еще на первом try с сообщением:

    Необработанное исключение типа «System.IO.FileLoadException» в mscorlib.dll Дополнительные сведения: Не удалось загрузить файл или сборку «C:\Users\anweledig\Documents\Visual Studio 2015\Projects\Anweledig\ConsoleApplication\bin\Debug\Plugins\TestPlugin.dll» либо одну из их зависимостей. Данное имя сборки или база кода недействительны. (Исключение из HRESULT: 0x80131047)

    Вы можете просто загрузить другую библиотеку. Все ее зависимости будут автоматически загружены.

    Проблема может быть только в случае, если библиотека лежит не в той же папке, в которой лежит ваше приложение, и вы при этом загружаете ее через Assembly.LoadFrom . Чтобы это обойти, вам нужно указать рантайму что зависимости надо искать в папке плагина, а не в папке вашего приложения. Тогда можно будет использовать обычный Assembly.Load и заодно заработает станадартный механизм загрузки сборок.

    Для этого можно использовать создание аппдомена с указанием AppDomainSetup.ApplicationBase

    Т.е. схема примерно такая:

    У вас есть базовая dll для плагинов — MyApp.SDK, в ней базовый класс (а лучше — интерфейс):

    в ней же код загрузки и вызова плагина (для простоты)

    есть реализация этого плагина в SomePlugin.dll:

    в SomePlugin есть референс на MyApp.SDK.

    И есть главное приложение, в котором есть ссылка на MyApp.SDK, но нет ссылки на SomePlugin:

    ApplicationBase задает папку, откуда AppDomain будет загружать сборки — т.к. это папка плагина, то все, на что он ссылается, будет корректно загружено.

    Система плагинов

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

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

    То есть нужно сделать отдельную библиотеку с апи, которую будут использовать обе стороны (программа и плагин).

    Вот такой класс придумал в этой библиотеке. Теперь я могу спокойно ее подключать к плагину и наследоваться.

    Вот у меня уже две библиотеки, одна с моим апи, другая его использует. Теперь мне нужно как-то загрузить сборку с плагином и использовать его методы. Вот тут мне и нужна помощь.

    Так как библиотека плагина использует другую библиотеку с апи, я не смогу ее так просто загрузить, так как выскочит ошибка мол либе не хватает референсов. Как правильно загрузить плагин указав что сборка которая ему нужна уже загружена в программу?

    Вот код которым я пытаюсь что-то наколдовать:

    Падает еще на первом try с сообщением:

    Необработанное исключение типа «System.IO.FileLoadException» в mscorlib.dll Дополнительные сведения: Не удалось загрузить файл или сборку «C:\Users\anweledig\Documents\Visual Studio 2015\Projects\Anweledig\ConsoleApplication\bin\Debug\Plugins\TestPlugin.dll» либо одну из их зависимостей. Данное имя сборки или база кода недействительны. (Исключение из HRESULT: 0x80131047)

    Вы можете просто загрузить другую библиотеку. Все ее зависимости будут автоматически загружены.

    Проблема может быть только в случае, если библиотека лежит не в той же папке, в которой лежит ваше приложение, и вы при этом загружаете ее через Assembly.LoadFrom . Чтобы это обойти, вам нужно указать рантайму что зависимости надо искать в папке плагина, а не в папке вашего приложения. Тогда можно будет использовать обычный Assembly.Load и заодно заработает станадартный механизм загрузки сборок.

    Для этого можно использовать создание аппдомена с указанием AppDomainSetup.ApplicationBase

    Т.е. схема примерно такая:

    У вас есть базовая dll для плагинов — MyApp.SDK, в ней базовый класс (а лучше — интерфейс):

    в ней же код загрузки и вызова плагина (для простоты)

    есть реализация этого плагина в SomePlugin.dll:

    в SomePlugin есть референс на MyApp.SDK.

    И есть главное приложение, в котором есть ссылка на MyApp.SDK, но нет ссылки на SomePlugin:

    ApplicationBase задает папку, откуда AppDomain будет загружать сборки — т.к. это папка плагина, то все, на что он ссылается, будет корректно загружено.

    Илон Маск рекомендует:  Достойный бюджетный плеер Cowon iAudio 10. Полный обзор.
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL