Pull to refresh

PocoCapsule: делаем «Hello world» проще

Reading time 7 min
Views 2.4K
image Статья рассказывает об опыте знакомства с 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:

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 и которые отняли у меня так много времени.
  1. После распаковки бинарников генератор прокси не запускается (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.


  2. При попытке поднять контекст ничего не происходит — получаем жизнерадостный 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-дескрипторы. К сожалению, я пошел первым путем и это только добавило проблем. Всем рекомендую идти по второму пути.
  3. PocoCapsule нативный xml-reader (libpocoxml.so) скомпилирован для Xerces-C++ версии 2.7, которая уже вышла из моды и нигде досатать ее не получится. Для решения этой проблемы качаем исходники pococapsule, устанавливаем xerces-c (dev) в систему, заходим в папочку pococapsule-cpp-1.1-src/src/xmlreader и выполняем make. После успешной компиляции забираем годный libpocoxml.so из папочки lib.
  4. 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.
  1. Создаем новый C++ проект. Подключаем заголовочные файлы (Project properties->Build->С++ Сompiler->Include directories: pococapsule_dir/include). Указываем линкеру где иcкать libpococapsule.so (Project properties->Build->Linker->Libraries: pococapsule_dir/lib/libpococapsule.so).
  2. Создадим класс 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.


  3. Создадим 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.


  4. Сгенерируем 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). Проверим, что все успешно билдится.
  5. Закодируем xml-контекст, чтобы избавиться от JAXP. Откроем консоль в директории проекта и выполним:

    export POCOCAPSULE_DIR=(директория PocoCapsule)
    $POCOCAPSULE_DIR/bin/pxencode context.xml


    * This source code was highlighted with Source Code Highlighter.


    На выходе получим context_poco.ctx.
  6. Отредактируем 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!»
    Вот и все :)


Спасибо за внимание.
Tags:
Hubs:
+3
Comments 3
Comments Comments 3

Articles