Статья рассказывает об опыте знакомства с IoC-контейнером PocoCapsule (C++), возникших трудностях и способах их преодоления. Помимо прочего статья включает небольшой пример для быстрого старта с PocoCapsule (упрощенный проект «Hello World» с официального сайта).
По моему скромному мнению технология IoC является важнейшей составляющей любого мало-мальски значимого проекта, посему правильное использование IoC-контейнера является критически важным. Поэтому, после знакомства с PocoCapsule, я решил написать эту статью. Не только ради того, чтобы поделиться своим опытом, но и для того, чтобы убедиться, что я все правильно сделал и все правильно понял, поскольку, как известно, лучший способ понять что-либо — попытаться объяснить это другому человеку.
Для того, чтобы запустить «Hello world»-приложение c использованием PocoCapsule, мне потребовалось порядка 6 часов. Причем, периодически хотелось послать все ко всем чертям, т.к. руки опускаются, когда видишь сообщение «failure: null», хотя делаешь все по учебнику. При том, что аналогичный старт в Spring IoC занимает от силы минут 30-60.
PocoCapsule это IoC-контейнер, который работает со стандартными C++ объектами (POCO) и делает это он достаточно хитро. PocoCapsule не может, прочитав xml-файл конфигурации, напрямую создать требуемые POCO-объекты, т.к. в C++ отсутствуем механизм рефлексии.
Например, есть класс Foo:
и xml-конфигурация контейнера:
Прочитав xml-конфигурацию, PocoCapsule должен создать объект класса Foo. Но как это сделать? Ведь PocoCapsule не зависит от ваших исходников и не может знать что это за класс такой Foo и как с ним обращаться, поэтому, естественно, он не может напрямую создать объект этого класса.
Для решения этой проблемы PocoCapsule генерирует для каждого вашего класса прокси-методы — методы, которые занимаются непосредственно работой с вашими классами (создание, удаление и пр.). Так, для вызова конструктора Foo будет сгенерирован метод:
Для удаления соответственно:
Затем также генерируются методы регистрации этих «poco_proxy» в контейнере. В конце концов эти прокси-методы обеъдиняются в общем исходном файле, который подключается к проекту и является связующим звеном между вашими классами и PocoCapsule.
Проблемы с оригинальным «Hello word». (Окружение Ubuntu 10.04 LTS)
Опишу по порядку проблемы, с которыми я столкунлся при знакомстве с PocoCapsule и которые отняли у меня так много времени.
Помимо прочего, «Hello World»-проект по учебнику подразумевает динамическую линковку. Я считаю, что можно сделать гораздо проще для нужд знакомства-то.
Простейший «Hello World»
Для разработки я использую Ubuntu 10.04 LTS и Netbeans 6.8, а так же бинарники PocoCapsule 1.0.
Спасибо за внимание.
По моему скромному мнению технология IoC является важнейшей составляющей любого мало-мальски значимого проекта, посему правильное использование IoC-контейнера является критически важным. Поэтому, после знакомства с PocoCapsule, я решил написать эту статью. Не только ради того, чтобы поделиться своим опытом, но и для того, чтобы убедиться, что я все правильно сделал и все правильно понял, поскольку, как известно, лучший способ понять что-либо — попытаться объяснить это другому человеку.
Для того, чтобы запустить «Hello world»-приложение c использованием PocoCapsule, мне потребовалось порядка 6 часов. Причем, периодически хотелось послать все ко всем чертям, т.к. руки опускаются, когда видишь сообщение «failure: null», хотя делаешь все по учебнику. При том, что аналогичный старт в Spring IoC занимает от силы минут 30-60.
PocoCapsule это IoC-контейнер, который работает со стандартными C++ объектами (POCO) и делает это он достаточно хитро. PocoCapsule не может, прочитав xml-файл конфигурации, напрямую создать требуемые POCO-объекты, т.к. в C++ отсутствуем механизм рефлексии.
Например, есть класс Foo:
class Foo {
public:
Foo() {};
~Foo() {};
}
* This source code was highlighted with Source Code Highlighter.
и xml-конфигурация контейнера:
<poco-application-context>
<bean class="Foo"
destroy-method="delete"
lazy-init="false">
</bean>
</poco-application-context>
* This source code was highlighted with Source Code Highlighter.
Прочитав xml-конфигурацию, PocoCapsule должен создать объект класса Foo. Но как это сделать? Ведь PocoCapsule не зависит от ваших исходников и не может знать что это за класс такой Foo и как с ним обращаться, поэтому, естественно, он не может напрямую создать объект этого класса.
Для решения этой проблемы PocoCapsule генерирует для каждого вашего класса прокси-методы — методы, которые занимаются непосредственно работой с вашими классами (создание, удаление и пр.). Так, для вызова конструктора Foo будет сгенерирован метод:
// ---------------------------------------
// Proxies of constructors and factories
// ---------------------------------------
//
// new Foo()
//
static void* _poco_proxy_0(void* _poco_this, void* _poco_params[])
{
Foo* _poco_var_retv;
_poco_var_retv = new Foo();
return (void*)_poco_var_retv;
}
* This source code was highlighted with Source Code Highlighter.
Для удаления соответственно:
// -------------------------------------
// Proxies of dup and destroy methods
// -------------------------------------
//
// delete(Foo*)
//
static void* _poco_proxy_1(void* _poco_this, void* _poco_params[])
{
int _poco_i = 0;
Foo* _poco_var_0 = (Foo*)(_poco_params[_poco_i++]);
delete(
_poco_var_0);
return (void*)0UL;
}
* This source code was highlighted with Source Code Highlighter.
Затем также генерируются методы регистрации этих «poco_proxy» в контейнере. В конце концов эти прокси-методы обеъдиняются в общем исходном файле, который подключается к проекту и является связующим звеном между вашими классами и PocoCapsule.
Проблемы с оригинальным «Hello word». (Окружение Ubuntu 10.04 LTS)
Опишу по порядку проблемы, с которыми я столкунлся при знакомстве с PocoCapsule и которые отняли у меня так много времени.
- После распаковки бинарников генератор прокси не запускается (bin/pxgenproxy). Имеем следующее:
caiiiycuk@caiiiycuk-laptop:~/pococapsule-cpp/bin$ ./pxgenproxy ./pxgenproxy: undefined symbol: JNI_CreateJavaVM
При этом у меня установлено и JRE и JDK (pocoapsule использует Java для генерации прокси и не только).
Для решения этой проблемы необходимо установить переменную окружения POCOCAPSULE_DIR=(папка с распакованным PocoCapsule). Очень старнно то, что данное требование нигде в документации не описанно. Предполагается, что должно работать из коробки, но у меня не пошло.
Правильный алгоритм:
caiiiycuk@caiiiycuk-laptop:~/pococapsule-cpp/bin$ export POCOCAPSULE_DIR=/home/caiiiycuk/pococapsule-cpp/
caiiiycuk@caiiiycuk-laptop:~/pococapsule-cpp/bin$ ./pxgenproxy
------------------------------------------------------------
Pocomatic Software, Dynamic Proxy Generator, version 1.0
------------------------------------------------------------
Usage:
./pxgenproxy (xml-file|option)*
options:
-help : print this page
-s=[name] : suffix and extend name of proxy file,
default value is '_reflx.cc'
-H=[std-header-file] : use the specified standard header file
-h=[usr-header-file] : use the specified user defined header file
-r=[gather|scatte] : recursively step into imported resources and gather or scatte
proxy generation result into one or multiple individual proxy file(s)
caiiiycuk@caiiiycuk-laptop:~/pococapsule-cpp/bin$
* This source code was highlighted with Source Code Highlighter.
- При попытке поднять контекст ничего не происходит — получаем жизнерадостный null вместо контекста. Сделав все по учебнику и запустив скомпилированный main.C, мы получаем «failure: null», поскольку cxtx == null:
/****************************************************************************/
/* */
/* Copyright 2006, by Pocomatic Software, LLC. All Rights Reserved. */
/* */
/****************************************************************************/
#include "pocoapp.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
POCO_AppEnv* env = POCO_AppContext::initDefaultAppEnv(argc, argv);
POCO_AppContext* ctxt = POCO_AppContext::create("setup.xml", "file");
if( ctxt == NULL || !ctxt->initSingletons() ) {
printf("failure: %s\n", env->get_message());
return -1;
}
ctxt->terminate();
ctxt->destroy();
return 0;
}
* This source code was highlighted with Source Code Highlighter.
А причина все в том же «POCOCAPSULE_DIR»: если перед запуском объявить эту перменную окружения, все начинает работать. Это связнано с тем, что для парсинга xml-файлов PocoCapsule использует по умолчанию JAXP. Sad but true :( Как вы понимаете, таскать с собой яву для парсинга xml-файлов не очень приятно… Благо есть два способа от нее отбиться: использовать нативыный xml-reader на базе Xerces-C++ или использовать encoded xml-дескрипторы. К сожалению, я пошел первым путем и это только добавило проблем. Всем рекомендую идти по второму пути.
- PocoCapsule нативный xml-reader (libpocoxml.so) скомпилирован для Xerces-C++ версии 2.7, которая уже вышла из моды и нигде досатать ее не получится. Для решения этой проблемы качаем исходники pococapsule, устанавливаем xerces-c (dev) в систему, заходим в папочку pococapsule-cpp-1.1-src/src/xmlreader и выполняем make. После успешной компиляции забираем годный libpocoxml.so из папочки lib.
- PocoCapsule игнорирует наше желание использовать Xerces-C++ (libpocoxml.so) и по-прежнему требует JAXP (libpocoxsl.so). Учебник нам говорит о том, что если мы подключим libpocoxml.so к нашему проекту, то PocoCapsule автоматически будет его использлвать вместо libpocoxsl.so. Да это так — на моем «Hello word»-проекте сроботало. Но в другом (более сложном) проекте, в котором подключены еще и другие библиотеки, подключение libpocoxml.so не дает эффекта — проект упорно требует libpocoxsl.so. После безуспешных танцев с бубном, я тупо перименовал libpocoxml.so в libpocoxsl.so и все заработало.
Помимо прочего, «Hello World»-проект по учебнику подразумевает динамическую линковку. Я считаю, что можно сделать гораздо проще для нужд знакомства-то.
Простейший «Hello World»
Для разработки я использую Ubuntu 10.04 LTS и Netbeans 6.8, а так же бинарники PocoCapsule 1.0.
- Создаем новый C++ проект. Подключаем заголовочные файлы (Project properties->Build->С++ Сompiler->Include directories: pococapsule_dir/include). Указываем линкеру где иcкать libpococapsule.so (Project properties->Build->Linker->Libraries: pococapsule_dir/lib/libpococapsule.so).
- Создадим класс Foo (Foo.h):
#ifndef _FOO_H
#define _FOO_H
#include <iostream>
class Foo {
public:
Foo() {
std::cout << "Hello world!" << std::endl;
}
};
#endif /* _FOO_H */
* This source code was highlighted with Source Code Highlighter.
- Создадим xml-контекст (context.xml):
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE poco-application-context
SYSTEM "http://www.pocomatic.com/poco-application-context.dtd">
<poco-application-context>
<bean class="Foo"
destroy-method="delete"
lazy-init="false">
</bean>
</poco-application-context>
* This source code was highlighted with Source Code Highlighter.
- Сгенерируем proxy-методы для нашего класса Foo. Откроем консоль в директории проекта и выполним:
export POCOCAPSULE_DIR=(директория PocoCapsule)
$POCOCAPSULE_DIR/bin/pxgenproxy -h=Foo.h context.xml
* This source code was highlighted with Source Code Highlighter.
После успешной генерации повяится файл context_reflx.cc, добавим этот файл в проект, чтобы NetBeans его билдил (Sources Files->Add Exsisting Item). Проверим, что все успешно билдится.
- Закодируем xml-контекст, чтобы избавиться от JAXP. Откроем консоль в директории проекта и выполним:
export POCOCAPSULE_DIR=(директория PocoCapsule)
$POCOCAPSULE_DIR/bin/pxencode context.xml
* This source code was highlighted with Source Code Highlighter.
На выходе получим context_poco.ctx.
- Отредактируем main.cpp:
#include "pocoapp.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
POCO_AppEnv* env = POCO_AppContext::initDefaultAppEnv(argc, argv);
POCO_AppContext* ctxt = POCO_AppContext::create("context_poco.ctx", "file");
if( ctxt == NULL || !ctxt->initSingletons() ) {
printf("failure: %s\n", env->get_message());
return -1;
}
ctxt->terminate();
ctxt->destroy();
return 0;
}
* This source code was highlighted with Source Code Highlighter.
Запустив увидем «Hello World!»
Вот и все :)
Спасибо за внимание.