Архитектура плагинов
Введение
У CppCMS будет полная встроенная поддержка плагинов в готовящейся к выходу версии 1.2, которая будет выпущена позже в этом году (2012).
Между тем, в CppCMS 1.0 уже есть все основы для поддержки плагинов. Темплейты уже полностью подключаемы (загрузка разделемого объекта и темплейта готова к использованию). Также есть новый класс booster::shared_object, позволяющий легко загружать динамические библиотеки.
Однако, в зависимости от архитектуры Вашего приложения, выбор точного решения может варьироваться. В следующих разделах будут рассмотрены различные варианты развития событий. Если здесь ничего не понятно, пожалуйста, спросите об этом в мейл-листе и вернитесь сюда для надлежащего исправления этой страницы.
Темплейты подключаемые с помощью разделяемого объекта.
У каждого разделяемого объекта темплейта есть глобальный конструктор и деструктор. Как только разделяемый объект загружен, вызывается конструктор и регистрирует себя в singleton'е cppcms::views::pool, становясь видимым для всех. При выгрузке он разрегистрируется с помощью глобального деструктора и больше не может больше использоваться. Это простая и прозрачная семантика.
Если плагин связан с кодом темплейта, он загрузит собственный темплейт:
libmy_plugin.so
my_application.cpp:
// мой код
{
...
render("my_plugin_skin","some_view",some_content);
}
my_view.cpp <- my_view.tmpl
// этот код генерируется автоматически
namespace {
struct loader {
loader() { cppcms::views::pool::instance().add(my_skin_generator()); }
~loader() { cppcms::views::pool::instance().remove(my_skin_generator());}
} loader_instance;
}
Поэтому, как только вы загрузите разделяемый объект, выполнится loader::loader() и зарегистрирует view, а когда будете выгружать разделяемый объект - loader::~loader() разрегистрирует его.
Удостоверьтесь, что у каждого темплейта есть собственное имя skin'а.
Темплейты с собственным фреймворком плагинов
Если Вы реализовали собственный фреймворк плагинов, есть два метода, позволяющие плагинам определить их собственные темплейты для создаваемого ими контента, интегрируемого в страницу, генерируемую главным приложением.
Использование потоков
Основное приложение определяет макет всей страницы:
<% skin basic_skin %>
<% view basic_view uses content::basic_content %>
<html>
...
<body>
...
<!--- aux-часть-->
<% foreach p in plugings %>
<% item %><%= p.rendered_aux_html_content | raw %><% end %>
<% end %>
...
<!--- Main-часть-->
<% foreach p in plugings %>
<% item %>
<div id="plugin_<%=p.id %>" >
<%= p.rendered_main_html_content | raw %>
</div>
<% end %>
<% end %>
...
</body>
<% end view %>
...
<% end skin %>
Каждый плагин определяет темплейты для своего собственного содержимого:
<% skin plugin_a %> <% view main_html uses plugin_a_content::some_content_b %> <% template render() %> <!-- Указание render-функции --> какой-то конкретный html плагина делающий что-то, что ему нужно <% end template %> <% view aux_html uses plugin_a_content::some_content_a %> <% template render() %> <!-- Указание render-функции --> какой-то конкретный html плагина делающий что-то, что ему нужно <% end template %> <% end view %> <% end skin %>
Предположим, что наш плагин должен предоставлять следующее API:
class plugin {
public:
virtual std::string main_html(cppcms::applicatin *app) = 0;
virtual std::string aux_html(cppcms::application *app) = 0;
};
Содержимое каждого плагина рендерится примерно так:
// main_application.cpp
content::basic_content c;
// подготавливаем c
...
// рендерим плагины
for(int i=0;i<loaded_plugins.size();i++) {
c.plugins[i].rendered_main_html_content = loaded_plugins[i]->main_html(this);
c.plugins[i].rendered_aux_html_content = loaded_plugins[i]->aux_html(this);
}
render("basic_skin","basic_view",c);
На стороне плагина, это выглядит примерно так:
std::string main_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_b c;
...
std::ostringstream buf;
app->render("plugin_a","main_html",c,buf);
return buf.str();
}
std::string aux_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_a c;
...
std::ostringstream buf;
app->render("plugin_a","aux_html",c,buf);
return buf.str();
}
С помощью команды 'render'
Начиная с CppCMS 0.99.12 есть команда <% render ... %> См. Рендеринг других view.
Основное приложение определяет макет всей страницы:
<% skin basic_skin %>
<% view basic_view uses content::basic_content %>
<html>
...
<body>
...
<!--- aux-часть-->
<% foreach p in plugings %>
<% item %><% render p->name(), "aux_html" with p->aux_html_content() %><% end %>
<% end %>
...
<!--- Main-часть-->
<% foreach p in plugings %>
<% item %>
<div id="plugin_<%=p->id() %>" >
<% render p->name(), "main_html" with p->main_html_content() %>
</div>
<% end %>
<% end %>
...
</body>
<% end view %>
...
<% end skin %>
, где p - указатель на объект плагина.
Темплейт плагина будет реализовываться как и в предыдущем разделе. См. выше.
Предположим, что наш плагин должен предоставлять следующее API:
class plugin {
public:
virtual void prepare() = 0;
virtual cppcms::base_content &main_html_content() = 0;
virtual cppcms::base_content &aux_html_content() = 0;
virtual std::string name() = 0;
};
Содержимое каждого плагина рендерится примерно так:
basic_content {
...
// указатели на плагины
std::vector<plugin *> plugins;
}
main_application.cpp:
content::basic_content c;
// подготавливаем c
...
for(p in plugins)
p->prepare();
render("basic_skin","basic_view",c);
На стороне плагина, это выглядит примерно так:
class my_plugin : public plugin {
public:
virtual void prepare()
{
... установки main_ и aux_...
}
virtual std::string name() { return "plugin_a"; }
virtual cppcms::basic_content &main_html_content() { return main_; }
virtual cppcms::basic_content &aux_html_content() { return aux_; }
private:
plugin_a_content::some_content_b aux_;
plugin_a_content::some_content_a main_;
};
std::string main_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_b c;
...
std::ostringstream buf;
app->render("plugin_a","main_html",c,buf);
return buf.str();
}
std::string aux_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_a c;
...
std::ostringstream buf;
app->render("plugin_a","aux_html",c,buf);
return buf.str();
}
Вариации
Вариации на базе вышеуказанного, в зависимости от нужд Вашего приложения.
Определение плагина:
plugin {
void render_main(cppcms::application &);
void render_aux(cppcms::application &);
}
, где плагин выполняется как:
render_main(cppcms::application &app)
{
app.render("plugin_a","main_html",my_content);
}
и темплейт выглядит как:
<% foreach p in plugins %> <% item %> <% c++ p->render_main(content.app()); %> <% end item %> <% end foreach %>
Повторное использование skin'а
В этом разделе рассматривается возможность плагина по повторному использованию skin'а, осуществляющаяся ядром приложения.
Предположим, что Ваш плагин хочет расширить basic_skin::basic_view (т.е. наследоваться от них) и иметь контроль над всей страницей.
Это намного хитрее.
Вы не можете наследоваться от них, т.к. cppcms_tmpl_cc не генерирует заголовочные файлы. Вместо этого, сделайте следующее.
Вместо записи: <% skin basic_skin %>, используйте неименованный skin: <% skin %>
Затем скомпилируйте основной skin:
Main view cpp:
cppcms_tmpl_cc -s basic_skin basic_view.tmpl some_other_main_views.tmpl -o basic_skin.cpp g++ basic_skin.cpp -o libbasic_skin.so cppcms_tmpl_cc -s plugin_a basic_view.tmpl plugin_a.tmpl -o plugin_a.cpp g++ plugin_code.cpp plugin_a.cpp -o libplugin_a.so
Обратите внимание, что basic_view.tmpl присутствует дважды, первый - под skin'ом=namespace, а второй раз - под skin'ом=namespace plugin_a.
Это дублирует c++-код и требует от плагина действительно использовать код skin'а приложения. Это не идеально, но терпимо, по крайней мере пока cppcms_tmpl_cc не станет поддерживать генерацию файлов .h и .cpp.
