Минимализм удаленного взаимодействия на C++11
Некоторое время назад мной был опубликован пост о создании собственной системы плагинов на C++11 [1]. Где было рассказано о плагинах в рамках одного процесса. Есть желание дать возможность плагинам не зависеть от процесса и компьютера, на котором они исполняются. Сделать межпроцессное и удаленное взаимодействие. Для этого надо решить пару задач: создать наборы Proxy/Stub и предоставить транспорт для обмена данными между клиентом и сервером.Во многих библиотеках, предназначенных для организации удаленного взаимодействия предлагается некоторый скрипт или утилита для генерации Proxy/Stub из некоторого описания интерфейса. Так, например для libevent [2] при использовании ее части, связанной с RPC, есть скрипт event_rpcgen.py, который из С-подобного описания структур генерирует код для Proxy/Stub, а транспортом уже служит другая часть libevent. Так же gSOAP [3] предоставляет утилиту генерации кода из C++-подобного описания структур данных или из WSDL-описания и имеет свой встроенный транспорт, который можно использовать. gSOAP хороший и интересный продукт и в нем применение утилиты автогенерации кода оправдано, т. к. из C++-подобного описания можно сгенерировать WSDL-описание, которое уже может быть использовано при работе с Web-сервисами из других языков программирования.
Можно найти еще несколько примеров библиотек для построения клиент-серверного взаимодействия. Многие из них будут предлагать использовать те или иные механизмы генерации Proxy/Stub и свой встроенный транспорт.
Как можно взять за основу любой известный транспорт и отказаться от утилит генерации кода Proxy/Stub, возложив эту задачу на компилятор и воспользоваться преимуществами C++11 для создания объектного интерфейса удаленного взаимодействия с минимальными трудозатратами его использования? О принципах и реализации в виде законченного проекта, который может быть использован как библиотека при разработке своего клиент-серверного приложения изложены ниже.Пост получился не из разряда самых коротких. Его можно читать с любого интересующего Вас раздела. В зависимости от того, что является первостепенным интересом: технические детали реализации или возможность использования и примеры.
Разделы:
Введение Как уже было отмечено выше, появление поста было вызвано желанием «расселить» плагины одного процесса по разным и по возможности по разным компьютерам. Но пост не о доделках системы плагинов, а скорее о ее побочном полноценном и независимом от нее продукте, который в последствии и ляжет в ее основу реализации удаленного взаимодействия плагинов. А пока это всего лишь немного упрощенная реализация, позволяющая строить клиент-серверные приложения. В качестве интерфейса для такого взаимодействия выбран интерфейс в стиле C++ (структура с чисто виртуальными методами). Как из такого интерфейса получить набор Proxy/Stub’ов я уже ранее писал [4]. Это был пост с реализацией на C++03 и был своего рода идеально сферическим конем из абсолютно черной материи, т.е. не имел завершенной практической реализации, которую можно было бы взять и, что называется, «прямо из коробки» попробовать использовать. В этом же посте будет дана полная реализация с преимуществами, которые были получены благодаря стандарту C++11, а так же все можно будет протестировать «из коробки» на реальном тестовом сервере.Реализация В основу реализации легли материалы уже опубликованные мною ранее: пост о реализации Proxy/Stub’ов [4] и пост о создании своего http-сервера на базе libevent [5].Что использовать в качестве транспорта не важно, и при желании его можно легко заменить, реализовав пару простых интерфейсов. Мною был выбран http встроенный функционал libevent, так как он мне показался простым и не затратным в использовании и уже неплохо себя зарекомендовавшим.
Для формирования пакетов данных, которыми обмениваются клиент и сервер была выбрана простая библиотека rapidxml [6], которая меня привлекла тем, что она поставляется в виде набора только включаемых файлов, что для меня дает возможность более простого ее внедрения и распространения исходного кода на ее основе, а так же она показывает хорошие результаты производительности, что при работе с xml так же играет не последнюю роль. Так же как и транспорт предлагаемая реализация удаленного взаимодействия может легко переключиться на иной формат/протокол сериализации и десериализации данных при желании пользователя. Для этого нужно реализовать два класса с заданным интерфейсом.
Вся реализация построена с использованием шаблонов и немного макросов. Мда… Говорят, что макросы — это зло, а шаблоны — это сложно и непонятно. Получается сложное и непонятное зло. Так ли это? Нет. Макросы в небольшом их количестве полезны, т. к. иногда не все возможно выразить только средствами языка, а для достижения результата нужно прибегнуть к препроцессору. В свою же очередь шаблоны дают обобщение и сокращение кода. Получается сдвиг от сложно-непонятного зла в сторону полезного сокращения и обобщения кода.
Будучи не раз обруганным многоэтажным матом компилятора его длинных сообщений об ошибках в шаблонном коде да еще и под макросами, мне удалось достичь минимализма, примеры которого я приведу перед тем как погрузиться в детали реализации, чтобы было видно к какому результату будет стремление в описании реализации.
Пример интерфейса struct IFace { virtual ~IFace () {} virtual void Mtd1() = 0; virtual void Mtd2(int i) = 0; virtual void Mtd3(int i) const = 0; virtual int const* Mtd4() const = 0; virtual int const& Mtd5(int i, long *l1, long const *l2, double &d, long const &l3) const = 0; virtual char const* Mtd6() const = 0; virtual wchar_t const* Mtd7() = 0; virtual void Mtd8(char const *s1, wchar_t const *s2) = 0; }; Тестовый интерфейс, который содержит методы как с параметрами, так и без. Параметры передаются по значению, ссылке или указателю. Так же методы могут ничего не возвращать или какое-то значение, переданное по ссылке, указателю или по значению (простите за тавтологию). В общем, материал как раз для того, чтобы в полном объеме протестировать то минимальное описание Proxy/Stub, которое приведено ниже. И которое не требует никакого указания информации о параметрах и возвращаемых значениях, а только интерфейс и имена методов.Пример описания Proxy/Stub PS_BEGIN_MAP (IFace) PS_ADD_METHOD (Mtd1) PS_ADD_METHOD (Mtd2) PS_ADD_METHOD (Mtd3) PS_ADD_METHOD (Mtd4) PS_ADD_METHOD (Mtd5) PS_ADD_METHOD (Mtd6) PS_ADD_METHOD (Mtd7) PS_ADD_METHOD (Mtd8) PS_END_MAP () PS_REGISTER_PSTYPES (IFace) То к чему у меня было большое желание прийти — это отказаться от указания информации о параметрах и возвращаемых значениях при описании Proxy/Stub, задавая только имена методов. За это пришлось немного заплатить небольшим ограничением: отказом от использования перегрузки методов. И это ограничение можно обойти, добавив небольшой макрос, который уже не будет столь лаконичен. В рамках этого поста этого макроса не будет. При соответствующей мотивации это можно сделать очень быстро.Тяга к такому минимализму обусловлена тем, что приходилось иметь дело с разными библиотеками и framework’ами, которые заставляли прилагать много усилий для описания каждого метода и если не было автогенерации, то при внесении изменений в метод, приходилось после пинка компилятора вспоминать, что забыл поправить Proxy/Stub, отправляться в соответствующий файл и править. А когда и была автогенерация, то она иногда сильно изобиловала тонкостями описания ее входных данных, на основании которых она работала. Полностью от этого все же не удалось избавиться, так как средствами C++ можно всю информацию о методах класса легко получить, а вот стандартных средств перечислить методы нет. Поэтому единственное, что придется делать при изменении интерфейса — это добавлять и удалять методы в описании Proxy/Stub при их добавлении и удалении в интерфейсе, при этом поддерживать строгий порядок следования Proxy/Stub описания методов последовательности методов самого интерфейса нет необходимости. Этого не было в ранее опубликованном посте [4], а теперь благодаря средствам C++11 такую независимость удалось получить.
Пример сервера
#include
#include «face.h» // Класс-реализация интерфейса IFace.
#include «iface_ps.h» // Описание Proxy/Stub интерфейса.
#include «xml/pack.h» // Реализация (де)сериализации
#include «http/server_creator.h» // Функция создания сервера.
#include «class_ids.h» // Идентификаторы классов-реализаций.
int main ()
{
try
{
// Создание сервера.
auto Srv = Remote: Http: CreateServer
<
Remote::Pkg::Xml::InputPack, // Тип десериализатора входящих пакетов
Remote::ClassInfo // Описание реализации интерфейса.
// Описаний Remote::ClassInfo может быть несколько.
// На каждую реализацию здесь передается Remote::ClassInfo.
<
IFace, // Реализуемый интерфейс
Face, // Реализация интерфейса
FaceClsId // Идентификатор реализации
>
>(»127.0.0.1» /*IP*/, 5555/*Port*/, 2/*ThreadCount*/); // Сетевой интерфейс, на котором работает сервер.
std: cin.get (); // «Завешиваем» основной поток. Сервер работает пока существует его объект.
}
catch (std: exception const &e)
{
std: cerr << e.what() << std::endl;
}
return 0;
}
В примере показано, что при создании сервера нужно указать тип, который будет заниматься сборкой и разбором пакетов данных и указать список классов, реализации которых сервер будет поставлять его клиентам. Так как для одного и того же интерфейса может быть несколько разных реализаций, то они маркируются идентификатором. При создании объекта клиент передает серверу идентификатор реализации. Классы-реализации могут наследовать множество всего, а для того, чтобы при запросе клиента сервер мог создать нужный объект-заглушку, указывается и интерфейс, который реализует класс. По этому интерфейсу производится поиск нужного типа заглушки. Сервер может поставлять множество реализаций для множества интерфейсов и все они должны быть перечислены при создании сервера. Функцию CreateServer можно переписать для своего вида транспорта, а все необходимое для этого уже есть. Нужен только транспорт при желании его заменить. Вся работа по созданию объектов и объектов-заглушек уже реализована и не нуждается в замене.Пример клиента
#include
Создание Proxy/Stub’ов Инфраструктура и транспорт Proxy/Stubs При возникновении необходимости в разделении объекта и его использующего кода между клиентом и сервером, в этот момент появляется необходимость в прокси-объектах (Proxy) на стороне клиента и объектах-заглушках (Stub) на стороне сервера. Первые при вызове метода на стороне клиента создают запрос и отправляют его на сервер, а вторые на стороне сервера разбирают этот запрос и вызывают соответствующий метод реального объекта.Как с помощью C++ можно получить всю информацию о методе на основе указателя на метод в момент компиляции. А так же как с помощью полученной информации и преимуществ C++11 создавать наборы Proxy/Stub так же в момент компиляции будет рассказано в этом разделе. Это можно сделать на основе шаблонов и немного прибегнув к средствам препроцессора.Предположим есть такой интерфейс:
struct ISessionManager { virtual ~ISessionManager () {} virtual std: uint32_t OpenSession (char const *userName, char const *password) = 0; virtual void CloseSession (std: uint32_t sessionId) = 0; virtual bool IsValidSession (std: uint32_t sessionId) const = 0; }; предназначенный для работы с сессиями пользователей. OpenSession — открывает сессию для заданного пользователя и в качестве результата возвращает идентификатор открытой сессии. CloseSession закрывает сессию по переданному идентификатору. IsValidSession — проверяет идентификатор сессии на валидность.При создании Proxy/Stub для каждого из методов надо получить его тип. Тип указателя на метод. В C++03 стандартными средствами этого сделать нельзя. Приходилось прибегать к некоторым компиляторозависимым решениям [4]. Так для gcc можно было воспользоваться его расширением typeof, а для MS Visual Studio сделать (более ранних версий, чем 2010) некоторый хак на шаблонах, который мог компилироваться только ее компилятором, т. к. подход выходит за рамки стандарта. Это все и многое другое можно подсмотреть, например, в boost. С появлением C++11 это стало возможно в рамках стандарта. Получать типы переменных и выражений можно с помощью decltype.
typedef decltype (&ISessionManager: OpenSession) OpenSessionMtdType; typedef decltype (&ISessionManager: CloseSession) CloseSessionMtdType; typedef decltype (&ISessionManager: IsValidSession) IsValidSessionMtdType; В этом и кроется ограничение, которое не дает при рассматриваемой простоте использования макросов создания Proxy/Stub использовать перегрузку методов, так как при передаче в decltype указателя на метод нет средств указать компилятору какой из перегруженных методов нужно использовать.Получив тип метода, можно с помощью еще одного средства C++11, шаблонов с переменным числом параметров сделать реализации для каждого из методов и на их основе построить или прокси-объект или объект-заглушку, получая при этом всю информацию о методе: о возвращаемом значении, передаваемых параметрах и его cv-квалификаторе (в данном случае интересен только const квалификатор для небольшого упрощения). А так как нужно в эту реализацию еще и имя метода подставить, то придется немного прибегнуть к препроцессору. На основании этого можно получить такой макрос для реализации метода:
namespace Methods
{
template
#define DECLARE_PROXY_METHOD (iface_, mtd_, id_) \
namespace Methods \
{ \
typedef decltype (&iface_:: mtd_) mtd_##Type; \
template
template
typedef Proxy
<
Methods::OpenSessionProxyType,
Methods::CloseSessionProxyType,
Methods::IsValidSessionProxyType
>
SessionManagerProxy;
Сам макрос принимает три параметра: интерфейс, метод и идентификатор метода. Если с интерфейсом и методом все просто, то идентификатор откуда-то надо взять. В [4] предлагалось сделать некоторый счетчик в момент компиляции и его использовать как идентификатор метода. Это накладывает некоторые ограничения: последовательность методов важна. Если собран сервер с одной последовательностью методов, а клиент с другой, то идентификторы методов не будут совпадать. И при обмене пакетами клиент и сервер не смогут «договориться» о том какой метод использовать для присланного идентификатора. Отсюда необходимость пересборки обеих частей при изменении порядка методов или при их удалении и добавлении в описание Proxy/Stub. С помощью C++11 такое ограничение можно устранить, вычислив CRC32 имени метода в момент компиляции, а constexpr позволяет это легко сделать в момент компиляции. Счетчик так же пригодится для других целей. А пока пара слов о генерации CRC32 в момент компиляции. Вариант создания CRC32 в момент компиляции уже приводился ранее в [1]. Здесь еще раз приведу его.Создание CRC32 кода от строки в момент компиляции
namespace Remote
{
namespace Private
{
template
typedef decltype (&ISessionManager: OpenSession) OpenSessionMtdType; typedef decltype (&ISessionManager: CloseSession) CloseSessionMtdType; typedef decltype (&ISessionManager: IsValidSession) IsValidSessionMtdType;
namespace Methods
{
template
#define DECLARE_PROXY_METHOD (iface_, mtd_) \
namespace Methods \
{ \
enum {mtd_##Id = Crc32(#mtd_)}; \
typedef decltype (&iface_:: mtd_) mtd_##Type; \
template
DECLARE_PROXY_METHOD (ISessionManager, OpenSession) DECLARE_PROXY_METHOD (ISessionManager, CloseSession) DECLARE_PROXY_METHOD (ISessionManager, IsValidSession)
template
typedef Proxy
<
Methods::OpenSessionProxyType,
Methods::CloseSessionProxyType,
Methods::IsValidSessionProxyType
>
SessionManagerProxy;
Несмотря на то, что в C++11 возможно наследование от шаблонного параметра, который является пакетом типов (шаблоны с переменным числом параметров), запись
typedef Proxy
<
Methods::OpenSessionProxyType,
Methods::CloseSessionProxyType,
Methods::IsValidSessionProxyType
>
SessionManagerProxy;
как-то еще очень слабо тянет на минимализм. Чтобы избавиться от конкретных имен типов в определении прокси-класса нужно создать некоторый реестр типов, пронумеровав каждый в нем содержащийся тип с помощью некоторого счетчика. На основе этого реестра и счетчика позднее построить иерархию наследования классов-реализаций методов интерфейса. Создать реестр можно с помощью небольшой связки шаблонов и макросов.
template
#define REGIDTER_TYPE (id_, type_) \
template <> \
struct TypeRegistry
#define REGIDTER_TYPE (id_, type_) \
template <> \
struct TypeRegistry
DECLARE_TYPE_REGISTRY (Methods)
namespace Methods
{
template
#define DECLARE_PROXY_METHOD (iface_, mtd_, id_) \
namespace Methods \
{ \
enum {mtd_##Id = Crc32(#mtd_)}; \
typedef decltype (&iface_:: mtd_) mtd_##Type; \
template
DECLARE_PROXY_METHOD (ISessionManager, OpenSession, 1) DECLARE_PROXY_METHOD (ISessionManager, CloseSession, 2) DECLARE_PROXY_METHOD (ISessionManager, IsValidSession, 3)
template
typedef Proxy < typename Methods::TypeRegistry<1>:: Type, typename Methods: TypeRegistry<2>:: Type, typename Methods: TypeRegistry<3>:: Type > SessionManagerProxy; Посмотрев на конечный код построения прокси-класса typedef Proxy < typename Methods::TypeRegistry<1>:: Type, typename Methods: TypeRegistry<2>:: Type, typename Methods: TypeRegistry<3>:: Type > SessionManagerProxy; можно заметить, что в нем пропали какие-то специфичные имена при определении прокси-класса, но опять появился какой-то идентификатор. На данный момент это не идентификатор, это счетчик под которым находится определенная запись в реестре типов. Построив счетчик в момент компиляции можно отказаться от явного задания каких-то «цифр» и построить завершающий вариант упрощенных макросов для определения прокси-класса из информации о его методах.Как сделать счетчик в момент компиляции уже было написано в [4]. Бегло повторю. Для построения счетчика нужно:
Сгенерировать некоторую иерархию типов.
Объявить функцию принимающую void * и возвращающую массив char размеров в один элемент.
На каждом шаге для каждой новой константы счетчика с помощью sizeof получать размер массива объявленной (но не определенной) функции и объявлять новую функцию с одним из типов иерархии, которая при спуске по иерархии возвращает массив все большей и большей длины. Реализация функций не требуется, т. к. они никогда не вызываются, а используются в момент компиляции под sizeof для вычисления размера возвращаемого значения.
Описания алгоритма звучит более непонятным, чем он реализуется… Реализация проста:
namespace Private
{
template
#define INIT_STATIC_COUNTER (counter_name_, max_count_) \
namespace counter_name_ \
{ \
typedef: Private: Hierarchy
#define GET_NEXT_STATIC_COUNTER (counter_name_, value_name_) \
namespace counter_name_ \
{ \
enum { value_name_ = sizeof (GetCounterValue (static_cast
GET_NEXT_STATIC_COUNTER (MyCounter, Item1) GET_NEXT_STATIC_COUNTER (MyCounter, Item2) GET_NEXT_STATIC_COUNTER (MyCounter, Item3)
int main ()
{
std: cout << MyCounter::Item1 << std::endl;
std::cout << MyCounter::Item2 << std::endl;
std::cout << MyCounter::Item3 << std::endl;
return 0;
}
В результате на экране будут распечатаны значения от одного до трех. Это можно отправить компилятору с ключом -E для того чтобы развернуть все макросы.Код после разворачивания макросов
#include
namespace MyCounter
{
enum
{
Item1 = sizeof (GetCounterValue (static_cast
namespace MyCounter
{
enum
{
Item2 = sizeof (GetCounterValue (static_cast
namespace MyCounter
{
enum
{
Item3 = sizeof (GetCounterValue (static_cast
int main () { std: cout << MyCounter::Item1 << std::endl; std::cout << MyCounter::Item2 << std::endl; std::cout << MyCounter::Item3 << std::endl; return 0; } Как и говорилось все просто. В начале немного макросы пугают, но тут они как раз на пользу идут. Без них трудно будет реализовать счетчик.Все составляющие для генерации конечного прокси-класса есть. Прокси-класс создается из его составляющих кубиков, каждый из которых содержит реализацию для одного из методов интерфейса. Можно написать конечный набор макросов для любого интерфейса и проверить на уже ранее разбираемом ISessionManager.
Макросы для описания прокси-класса
#define BEGIN_PROXY_MAP (iface_) \
namespace iface_##PS \
{ \
namespace Impl \
{ \
typedef iface_ IFaceType; \
INIT_STATIC_COUNTER (MtdCounter, 100) \
namespace Methods \
{ \
DECLARE_TYPE_REGISTRY (ProxiesReg) \
template
#define END_PROXY_MAP () \
GET_NEXT_STATIC_COUNTER (MtdCounter, LastCounter) \
template
#define INIT_STATIC_COUNTER (counter_name_, max_count_) \
namespace counter_name_ \
{ \
typedef: Private: Hierarchy
#define GET_NEXT_STATIC_COUNTER (counter_name_, value_name_) \
namespace counter_name_ \
{ \
enum { value_name_ = sizeof (GetCounterValue (static_cast
#define DECLARE_TYPE_REGISTRY (reg_name_) \
namespace reg_name_ \
{ \
template
#define REGIDTER_TYPE (reg_name_, id_, type_) \
namespace reg_name_ \
{ \
template <> \
struct TypeRegistry
BEGIN_PROXY_MAP (ISessionManager) ADD_PROXY_METHOD (OpenSession) ADD_PROXY_METHOD (CloseSession) ADD_PROXY_METHOD (IsValidSession) END_PROXY_MAP ()
int main ()
{
try
{
ISessionManagerPS: Proxy Proxy;
Proxy.OpenSession («user»,»111»);
}
catch (std: exception const &e)
{
std: cerr << e.what() << std::endl;
}
return 0;
}
Как можно видеть из приведенного примера получился простой интерфейс для описания прокси-класса, внешне мало отличающийся от того, что было приведено в примерах в начале поста. Этот код уже можно скомпилировать и попробовать вызвать методы прокси-объекта для интерфейса ISessionManager и в ответ получить исключение с сообщением «OpenSession not implemented.». На данный момент реализация каждого метода просто кидает исключение о том что метод пока ничего не делает. Немногим позднее эти реализации будут заполнены более осмысленным кодом. А пока можно попробовать пример, приведенный выше, отправить компилятору с ключом -E и посмотреть во что развернулись все макросы. Кода после раскрытия всех макросов получилось не так и мало для восприятия человеком как вспомогательного материала при чтении поста. Да и по большому счету он и не рассчитан на человека, препроцессор сделал свою грязную работу, заменив частично утилиту автогенерации, теперь очередь за компилятором продолжать работу над полученными типами, продолжая заменять утилиту автогенерации кода: инстанцировать, рассчитывать полученный счетчик и т. д. Можно по диагонали просмотреть код и легко понять его структуру, что получилось после разворачивания всех макросов.Код примера после разворачивания макросов
namespace Private
{
template
namespace ISessionManagerPS
{
namespace Impl
{
typedef ISessionManager IFaceType;
namespace MtdCounter
{
typedef: Private: Hierarchy<100> CounterHierarchyType;
char (&GetCounterValue (void const *))[1];
}
namespace Methods
{
namespace ProxiesReg
{
template
int main () { try { ISessionManagerPS: Proxy Proxy; Proxy.OpenSession («user»,»111»); } catch (std: exception const &e) { std: cerr << e.what() << std::endl; } return 0; } Реализовав все методы интерфейса в прокси-классе, эти реализации нужно заполнить кодом, который бы занимался упаковкой всех переданных параметров, отправкой полученного пакета данных на сервер и полученный ответ разбирал бы, находя в нем результат выполнения метода (если таковой имеется), а так же если в ходе выполнения метода было выброшено исключение, то получить информацию о нем из пакета с ответом, так как исключения так же передаются от сервера клиенту.Такой код можно разместить в каждой из реализаций методов. Однако это было бы не самым красивым и оптимальным решением. Желательно этот код расположить в одном и том же месте. Для решения этой задачи хорошо подойдет такой шаблон проектирования, как CRTP [7].
Все «кубики» (классы-реализации методов) можно наследовать от класса, который будет иметь метод для выполнения манипуляций с пакетом данных. А так как вся необходимая информация находится в самом прокси-классе (в самом низу иерархии наследования), то туда и должны перенаправляться все вызовы. Зачем? Логичнее всего было бы в этом прокси-классе разместить информацию об идентификаторе экземпляра объекта на стороне сервера, а так же указатель на интерфейс, через который осуществляется взаимодействие (транспорт), а не вставлять однотипный код с обработкой этой информации по всем классам-реализациям методов интерфейса.
В данном случае CRTP очень хорошо подходит и имеет преимущество над обычными интерфейсами с наборами чисто виртуальных методов. Сделать шаблонной виртуальную функцию нельзя, а сделать метод класса в CRTP шаблонным ничего не мешает. А так как в данном случае количество параметров для каждого вызова такого метода для работы с пакетами данных разное в силу того что разные методы интерфейса могут иметь разное количество параметров, то использование CRTP оказывается очень полезным.
Базовым классом для всех классов-реализаций методов будет:
template
BEGIN_PROXY_MAP
#define BEGIN_PROXY_MAP (iface_) \
namespace iface_##PS \
{ \
namespace Impl \
{ \
typedef iface_ IFaceType; \
INIT_STATIC_COUNTER (MtdCounter, 100) \
class ProxyImpl; \
namespace Methods \
{ \
DECLARE_TYPE_REGISTRY (ProxiesReg) \
template
template
Выше говорилось о Proxy/Stub, а пока реализована работа только с прокси. Реализация классов-заглушек (Stub) почти аналогичны реализации прокси-классов. Отличие заключается в том, что для прокси-класса нужно при вызове метода интерфейса всю информацию упаковать и отправить, а полученный результат отдать вызывающей стороне, а для класса-заглушки нужно выполнить все строго наоборот: распаковать полученный пакет, на его основе собрать параметры в некоторый список аргументов метода интерфейса и вызвать его, а полученный результат отправить вызывающей стороне. Для этого нужно в существующие макросы добавить немного изменений, связанных с вызовом метода с параметрами, извлеченными из пришедшего пакета. Все остальное остается таким же. Так же нужен и реестр, и счетчик, и сбор конечного класса из его «кубиков». Реестра теперь становится два: один для прокси-классов, второй для класса-заглушек. Можно и в один все поместить, а потом разбирать какой элемент чем является. Это приведет к более сложному коду. Поэтому добавлен второй реестр для объектов-заглушек.
PS_BEGIN_MAP
#define PS_BEGIN_MAP (iface_) \
namespace iface_##PS \
{ \
namespace Impl \
{ \
typedef iface_ IFaceType; \
INIT_STATIC_COUNTER (MtdCounter, 100) \
class ProxyImpl; \
class StubImpl; \
namespace Methods \
{ \
DECLARE_TYPE_REGISTRY (ProxiesReg) \
template