Архитектура плагинов
Введение
У 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.