Создание простого компонента сервиса на C++, часть 1

Обзор API C++ для архитектуры компонентов сервиса (SCA)

Comments

Серия контента:

Этот контент является частью # из серии # статей: Создание простого компонента сервиса на C++, часть 1

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Создание простого компонента сервиса на C++, часть 1

Следите за выходом новых статей этой серии.

Создание и подключение простого компонента сервиса на C++

О проекте Tuscany

Apache Tuscany - это проект, проходящий этап созревания в фонде Apache Software Foundation. Одна из задач проекта - это разработка рабочего цикла на C++, воплощающего следующие спецификации архитектуры компонентов сервиса (Service Component Architecture, SCA) (см. раздел Ресурсы , где можно найти дополнительную информацию):

  1. SCA Assembly Model (Модель сборки архитектуры компонентов сервиса);
  2. SCA C++ Client and Implementation (Клиент и реализация SCA C++).

В рамках этой статьи мы разработаем и разместим компонент сервиса на C++ для рабочего цикла C++ Apache Tuscany C++.

Введение

Рабочий цикл архитектуры компонентов сервиса (SCA) на C++ Tuscany позволяет создавать компоненты SCA при помощи стандартного кода C++ и размещать их в областях памяти, где они могут быть обнаружены и загружены рабочим циклом SCA. Чтобы добиться такой динамической загрузки компонентов, для рабочего цикла требуется несколько файлов описаний, и эти объекты, наряду с заголовочными файлами, используются для генерации заместителей и упаковщиков, которые позволяют выполнить вызов компонентов из других компонентов или клиентского кода как из локального объекта C++.

Мы создадим сначала один простой компонент SCA, затем еще один такой компонент, и соединим их друг с другом.

В качестве среды разработки мы воспользуемся Microsoft Visual studio, но можно использовать также компилятор командной строки и текстовый редактор. Вы увидите, как задать параметры для проекта studio и процесс разработки приложения.

ПРИМЕЧАНИЕ: Tuscany SCA зависит от проектов Tuscany SDO и Apache Axis2/C. Перед началом работы библиотеки SCA/SDO Tuscany libraries и библиотеки Apache Axis должны находиться в каталогах, указанных в переменной PATH. Дополнительную информацию можно найти в инструкции по загрузке для проекта.

Рабочий цикл Tuscany SCA C++ должен быть обеспечен информацией о том, где размещены модули и компоненты. Корень размещения идентифицируется переменной окружения TUSCANY_SCACPP_SYSTEM_ROOT. Сейчас мы зададим эту переменную, чтобы можно было запустить тестовую программу из Visual Studio. Если вы пользуетесь командной строкой, то не нужно задавать переменную до начала рабочего цикла.

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

Воспользуйтесь панелью управления, чтобы задать следующее значение: TUSCANY_SCACPP_SYSTEM_ROOT=c:\mybasicsample.

В Control Panel (Панели управления) откройте окно System (Система), затем перейдите на вкладку Advanced (Дополнительно) и нажмите кнопку Environment Variables (Переменные среды). Нажмите кнопку New (Создать) и задайте для Variable name (Имя переменной) значение TUSCANY_SCACPP_SYSTEM_ROOT, а для Variable value (Значение переменной) значение c:\mybasicsample. Затем нажмите кнопку OK, чтобы создать переменную окружения.

Создайте каталог с именем mybasicsample, а в нем два вложенных каталога с именами modules и subsystems.

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

Краткое резюме по спецификации SCA (вы, конечно, прочитали спецификацию?) напомнит нам, что система SCA состоит из двух или более подсистем. Каждая подсистема содержит список moduleComponents. Каждый moduleComponent в действительности реализуется каким-либо модулем. В мире C++ существует набор файлов описаний XML, которые используются в процессе компиляции для генерации заместителей и упаковщиков сервисов и в процессе рабочего цикла для поиска предлагаемых сервисов. Имеет смысл изучить информацию об этих файлах до того, как мы приступим к разработке. Файл, который описывает подсистему, должен иметь имя sca.subsystem и располагаться в своем подкаталоге каталога subsystems корневого каталога. Файл sca.subsystem описывает, какие компоненты moduleComponents включены в подсистему. Компонент moduleComponent может рассматриваться просто как часть подсистемы, moduleComponent имеет имя, а также указывает на модуль, который реализует поведение компонента moduleComponent:

Листинг 1. moduleComponent
<subsystem xmlns="http://www.osoa.org/xmlns/sca/0.9" name="MyServiceSubsystem">
        <moduleComponent name="MyModuleComponent" module="FloatConverter" />
</subsystem>

Листинг 1 сообщает рабочему циклу архитектуры компонентов сервиса SCA, что moduleComponent "MyModuleComponent" реализуется модулем с именем "FloatConverter", поэтому теперь мы должны создать этот модуль.

Файл sca.subsystem - это чистый объект рабочего цикла, он не обслуживает ни одной задачи в процессе компиляции. Другие файлы (файлы componentType и файлы sca.module) описывают модуль "FloatConverter", чтобы его можно было найти во время рабочего цикла. Эти файлы помогают также генератору кода создавать упаковщики и заместители для сервисов. Более подробно мы познакомимся с этими файлами в процессе разработки, который описан ниже.

А пока давайте вернемся к началу. Мы хотим разместить класс C++ как сервис и поместить этот сервис в модуль с именем "FloatConverter". Следующие этапы покажут, как это сделать.

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

ПРИМЕЧАНИЕ: До того, как вы приступите к процессу разработки, необходимо иметь или загрузить код SCA/SDO и встроить его, или загрузить двоичную версию, чтобы в дальнейшем можно было сообщить вашему проекту, где найти рабочий цикл SCA. Теперь зададим две переменных окружения TUSCANY_SCACPP и TUSCANY_SDOCPP, которые указывают каталоги проектов SCA и SDO, в которых находятся каталоги bin, lib; в них можно включать каталоги.

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

В заголовочном файле с именем "Example.h" находится класс:

Листинг 2. Пример класса
class Example  
{
public:
    // we will get a float from a string
    virtual float toFloat(const char* input) = 0;

    // we will convert a float to a string
    virtual char* toString(float value) = 0;
};

Нам нужно сообщить генератору кода, какой тип компонента будет демонстрировать это абстрактное поведение, поэтому мы создаем файл componentType. Файл componentType file связывает имя реализующего класса с абстрактным поведением в соответствии с соглашением об используемых названиях. Файлу присваивается имя по классу реализации, причем в имя включается ссылка на абстрактный класс, поэтому в нашем случае файл будет назван "ExampleImpl.componentType":

Листинг 3. файл componentType
<?xml version="1.0" encoding="ASCII"?>
<componentType xmlns="http://www.osoa.org/xmlns/sca/0.9"
               xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<service name="ExampleService">
		<interface.cpp header="Example.h">
		</interface.cpp>
	</service>
</componentType>

Листинг 3 сообщает генератору кода, что компонент с именем "ExampleService" будет демонстрировать поведение, которое описывается в файле заголовка "Example.h". Он также сообщает рабочему циклу, что сервис, реализованный под заголовком "ExampleImpl.h" - это ExampleService.

Итак, в Visual Studio мы создадим проект win32 dll и вставим в него файл заголовка, приведенный выше. Мы назвали проект "TheExampleProject", поэтому предполагается, что созданная dll по умолчанию получит имя "TheExampleProject.dll". Теперь создайте реализацию сервиса, которая, естественно, вызывает файлы ExampleImpl.cpp и ExampleImpl.h:

Листинг 4. Реализация сервиса
#include "Example.h"

class ExampleImpl : public Example
{
public:
    ExampleImpl();
    virtual ~ExampleImpl();

    // Example interface
    virtual float toFloat(const char* input);

    virtual char* toString(float value);

};


#include "ExampleImpl.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

ExampleImpl::ExampleImpl()
{
}
    
ExampleImpl::~ExampleImpl()
{
}

// Example interface

float ExampleImpl::toFloat(const char* input)
{
    if (input != 0)
    {
        float f = (float)atof(input);
        return f;
    }
    return (float)0;
}

char* ExampleImpl::toString(float value)
{
    char * r = new char[100];
    sprintf(r,"The float is : %5.5f", value);
    return r;
}

К этому моменту DLL будет скомпилирована и скомпонована , поэтому можно написать тестовую программу, чтобы использовать API Example и протестировать его. Пропустите данный шаг, поскольку это, на самом деле, стандартный материал.

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

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

  1. По переменной окружения TUSCANY_SCACPP_DEFAULT_MODULE=<subsystemname>/<modulecomponentname>, в нашем примере это будет: TUSCANY_SCACPP_DEFAULT_MODULE=MyServiceSubsystem/MyModuleComponent;
  2. Код клиента может определить модуль по умолчанию при помощи класса TuscanyRuntimе. Именно этот способ мы используем в нашем примере.

Теперь рабочему циклу остается только найти сервис в модуле и имя скомпонованной DLL. Это осуществляется чтением файла "sca.module", как показано в листинге 5.

Листинг 5. Чтение файла sca.module
<?xml version="1.0" encoding="ASCII"?>
<module xmlns="http://www.osoa.org/xmlns/sca/0.9" 
	xmlns:v="http://www.osoa.org/xmlns/sca/values/0.9" 
	name="FloatConverter">
	<component name="ExampleService">

		<implementation.cpp dll="TheExampleProject.dll" header="ExampleImpl.h">
		</implementation.cpp>
        	<properties> 
		</properties>
		<references>
		<references>
	</component>
</module>

Теперь мы знаем, что модуль FloatConverter содержит компонент, который называется "ExampleService". ExampleService реализован в библиотеке TheExampleProject.dll, а его методы описаны в файле заголовка "ExampleImpl.h".

Теперь мы должны добавить файлы sca.module и sca.subsystem в наш проект, просто чтобы следить за ними.

У нашего рабочего цикла SCA теперь есть вся информация, которая необходима для нахождения сервиса и вызова метода. Однако мы не можем написать клиент, который напрямую вызывает сервис, иначе мы получим зависимость в процессе компиляции в нашей библиотеке dll, поэтому нам для вызова необходим заместитель, а библиотеке нужен упаковщик для упаковки сервиса. Они будут сгенерированы инструментом scagen. Инструмент scagen использует файл заголовка, описанный в файле sca.module, и создает необходимый код. Вам остается только запустить инструмент, сообщить ему, где находится файл и куда записать вывод. Предположим, что файл sca.module и исходник нашего проекта находятся в каталоге c:\mybasicsample\Example. В каталоге c:\mybasicsample введите > scagen -dir Example -output Example

ПРИМЕЧАНИЕ: Конечно, для этого вам нужно иметь доступ к инструменту scagen, и вы должны знать, что scagen - это java-приложение. Если вы загрузили двоичный вариант, то scagen уже находится в каталоге "bin", в противном случае вам нужно создать эту утилиту. Для создания scagen вам нужно только установить комплект java JDK (1.4.2 или более поздние версии) и apache ant. Перейдите в каталог tools/scagen в проекте sca и введите "ant". В каталоге bin будет создана утилита scagen.

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

Листинг 6. Утилита scagen
<headername>_<servicename>_Proxy.cpp/.h and
<headername>_servicename>_Wrapper.cpp/.h

Добавьте эти новые файлы в проект visual studio.

Теперь ваш проект зависит от sca, и не будет создан до тех пор, пока вы не сообщите ему, где находятся заголовки и библиотеки sca. Добавьте к пути $(TUSCANY_SCACPP)/include и $(TUSCANY_SDO)/include для включения файлов заголовков (project,settings, C++,preprocessor, additional include directories). Кроме того, добавьте файлы tuscany_sdo.lib и tuscany_sca.lib в библиотеки (link/input/Object/library modules). Добавьте $(TUSCANY_SCACPP)\lib,$(TUSCANY_SDOCPP)\lib в качестве дополнительного пути к библиотекам.

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

Клиентская программа будет создаваться в рамках нового консольного исполняемого проекта, поэтому мы можем перейти к созданию такого проекта. Клиент зависит от рабочего цикла sca, но определенно не должен зависеть от проекта dll. Однако клиенту нужно знать, какие методы предлагает сервис, поэтому в завершение мы используем абстрактный базовый класс, который определили вначале. В проект клиента необходимо добавить заголовок Example.h. Проект клиента зависит от рабочего цикла SCA, поэтому мы должны добавить все библиотеки и заголовки, как мы только что делали для проекта dll.

Теперь создайте файл cpp:

Листинг 7. Файл cpp клиента
#include "..\MyServiceProject\Example.h"
#include "osoa/sca/sca.h"
#include <iostream>
#include <stdlib.h>
#include <tuscany/sca/core/TuscanyRuntime.h>

using namespace osoa::sca;
using namespace std;

int main(int argc, char* argv[])
{
	
    if (argc != 2)
    {
        cout << "MyClient.exe: Would you cast me adrift without my float?" << endl;
    	return 0;
    }
    
    // Set the default moduleComponent
    TuscanyRuntime runtime("MyServiceSubsystem/MyModuleComponent");

    try	{
        runtime.start(); // bootstrap the Tuscany Runtime

        // Get the current module context - (which is the default)
        ModuleContext myContext = ModuleContext::getCurrent();
        // Get an example service
	Example *theService = (Example*) myContext.locateService("ExampleService");
	if (theService == 0) {
		cout << "MyClient.exe: Unable to find MyFloatService" << endl;
	}
	else {
             try { 
                 float result = theService->toFloat(argv[1]);
                 cout << "The float returned is "  << result << endl;
                 char *str = theService->toString(result + 1);
                 cout << "The string came back as " << str << endl;
  	     }
	     catch (char* x) {
	         cout << "MyClient.exe: exception caught: " << x << endl;
	     }
	}
    }
    catch (ServiceRuntimeException& ex) {
        cout << "MyClient.exe: runtime exception caught: " << ex << endl;
    }
    runtime.stop(); // stop the Tuscany Runtime
    return 0;
}

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

В каталог TUSCANY_SCACPP_SYSTEM_ROOT/modules/Examplе:

  • Файл заголовка * Example.h;
  • Файл заголовка * ExampleImpl.h;
  • * TheExampleProject.dll;
  • * ExampleImpl.componentType;.
  • * sca.module.

В каталог TUSCANY_SCACPP_SYSTEM_ROOT/subsystems/Example:

  • Файл * sca.subsystem.

Теперь, когда в переменной path размещены файлы tuscany_sca.dll , tuscany_sdo.dll и axis2 dlls и задана переменная окружения TUSCANY_SCACPP_SYSTEM_ROOT, приложение сможет выполняться.

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

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

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

Сначала нужно создать второй сервис. Вам не нужно объяснять, как это делать. Мы просто создадим простой сервис, который возвращает строку:

Листинг 8. Пример простого сервиса
class StringThing  
{
public:

    // we will get a string
    virtual char* getString() = 0;
};


#include "StringThing.h"

class StringThingImpl : public StringThing
{
public:
    StringThingImpl();
    virtual ~StringThingImpl();

    // interface

    virtual char* getString();

};

#include "StringThingImpl.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

StringThingImpl::StringThingImpl()
{
}
    
StringThingImpl::~StringThingImpl()
{
}

// interface

char* StringThingImpl::getString()
{
    char * r = new char[100];
    sprintf(r,"The string from stringthing");
    return r;
}

Теперь, перед повторным использованием инструмента scagen, нужно создать новый файл componentType для нового компонента и добавить новый компонент в файл sca.module. В завершение нам также нужно сообщить системе, что первый componentType будет связан со вторым. Это делается посредством изменения файла ExampleImpl.componentType.

Вот новый файл componentType (StringThingImpl.componentType):

Листинг 9. StringThingImpl.componentType
<?xml version="1.0" encoding="ASCII"?>
<componentType xmlns="http://www.osoa.org/xmlns/sca/0.9"
               xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<service name="StringService">
		<interface.cpp header="StringThing.h">
		</interface.cpp>
	</service>
</componentType>

А это файл sca.module с изменениями:

Листинг 10. Файл sca.module с изменениями
>
<?xml version="1.0" encoding="ASCII"?>
<module xmlns="http://www.osoa.org/xmlns/sca/0.9" 
	xmlns:v="http://www.osoa.org/xmlns/sca/values/0.9" 
	name="FloatConverter">
	<component name="ExampleComponent">
		<implementation.cpp dll="TheExampleProject.dll" header="ExampleImpl.h">
		</implementation.cpp>
        <properties> 
		</properties>
		<references>
			<stringService>StringThing/StringService</stringService>
		<references>
	</component>
	<component name="StringThing">
		<implementation.cpp dll="TheExampleProject.dll" header="StringThingImpl.h">
		</implementation.cpp>
        <properties> 
		</properties>
		<references>
		<references>
	</component>
</module>

Файл ExampleImpl.componentType с изменениями:

Листинг 11. Файл ExampleImpl.componentType с изменениями
<?xml version="1.0" encoding="ASCII"?>
<componentType xmlns="http://www.osoa.org/xmlns/sca/0.9"
               xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<service name="ExampleService">
		<interface.cpp header="Example.h">
		</interface.cpp>
	</service>

	<reference name="stringService">
		<interface.cpp header="StringThing.h">
		</interface.cpp>
	</reference>

</componentType>

Обратите внимание на то, что мы использовали имя, начинающееся со строчной буквы "stringService" для ссылки на сервис с именем, начинающимся с заглавной буквы "StringService". При повторном запуске scagen вы заметите, что были созданы два дополнительных файла. Это заголовок заменителя и файл cpp для ссылки на "stringService" при вызове из ExampleImpl. Вам нужно добавить эти два новых файла в проект.

И, наконец, вот новый код сервиса Example, который разрешает новый сервис, использующий компонент componentContext:

Листинг 12. Пример сервиса
char* ExampleImpl::toString(float value)
{
    char * r = new char[100];
    // now make a service call to stringthing...
    try {
        ComponentContext myContext = ComponentContext::getCurrent();
        StringThing* stringService = (StringThing*)myContext.getService("stringService");

        if (stringService == 0)
        {
            cout << "unable to find string thing service" << endl;
        }
        else
        {
            char* chars = stringService->getString();
            if (chars != 0)
            {
                sprintf(r,"%s and the float is %5.5f",chars,value);
                delete chars;
                return r;
            }
        }
    }
    catch (ServiceRuntimeException& e)
    {
        cout << "Errror from service: " << e << endl;
        // .. just carry on
    }

    sprintf(r,"The float is : %5.5f", value);
    return r;
}

Напоминаем, что у нас есть новый сервис, поэтому нам нужно разместить новые файлы рабочего цикла:

  • StringThingImpl.h;
  • StringThing.h;
  • StringThingImpl.componentType.

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

Заключение

Вот и готова наша первая скомпонованная и работающая подсистема SCA. Рисунок 1 поможет запомнить путь, который используется для разрешения имени сервиса. Надеемся, что этот пример с решением дал вам ощущение эффективности архитектуры компонентов сервиса. В качестве упражнения можно добавить к компонентам некоторые свойства или попытаться упаковать StringThing в другую dll.

В следующей статье мы рассмотрим входящие и исходящие Web-сервисы и отношение к axis2.

Рисунок 1. Компоновка сервиса
layout
layout

Ресурсы для скачивания


Похожие темы

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=SOA и web-сервисы
ArticleID=174917
ArticleTitle=Создание простого компонента сервиса на C++, часть 1: Обзор API C++ для архитектуры компонентов сервиса (SCA)
publish-date=09082006