Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Минимализм удаленного взаимодействия на C 11

Anna | 24.06.2014 | нет комментариев

Некоторое время назад мною был опубликован пост о создании собственной системы плагинов на 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 <iostream>
#include <string>

#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 <iostream>
#include "iface_ps.h" // Изложение Proxy/Stub интерфейса.
#include "class_ids.h" // Идентификаторы классов-реализаций.
#include "xml/pack.h" // Реализация (де)сериализации.
#include "http/http_remoting.h" // Реализация транспорта заказчика.
#include "class_factory.h"  // Фабрика классов.
int main()
{
  try
  {
    // Создание транспорта. В данном случае на основе libevent.
    // Реализовав интерфейс Remote::Http::Remoting дозволено предоставить личный транспорт.
    auto Remoting = std::make_shared<Remote::Http::Remoting>("127.0.0.1", 5555);
    auto Factory = Remote::ClassFactory::Create // Создание фабрики классов.
          <
            Remote::Pkg::Xml::OutputPack, // Тип для сериализации исходящих пакетов.
            IFace // Список поддерживаемых интерфейсов. В данном примере один интерфейс.
          >(Remoting);
    // Создание объекта с интерфейсом IFace и идентификатором реализации на стороне сервера FaceClsId.
    auto Obj = Factory->Create<IFace>(FaceClsId);
    // Вызов способов объекта на сервере.
    Obj->Mtd2(10);
  }
  catch (std::exception const &e)
  {
    std::cerr << e.what() << std::endl;
  }
  return 0;
}

При создании фабрики классов ей необходимо передать объект, реализующий транспорт и список интерфейсов, которые она поддерживает. По этому списку интерфейсов фабрика для сделанного на сервере объекта у себя определяет прокси-объект, которым пользователь будет пользоваться для вызова способов интерфейса.
Дозволено подметить из примера, что фабрика не является классом-образцом, а только ее способы создания самой фабрики и объектов являются шаблонными. Для чего? Если бы фабрика была образцом, то по каждому единицам трансляции необходимо было бы «таскать» за собой всю информацию о ее типах, с которыми она работает. А так дозволено воспользоваться forward declaration и передав указатель на фабрику в другой файл плана, в котором теснее не нужно знать и подключать все файлы с информацией о типе, изготавливающем сериализацию и десереализацию пакетов и файлов с интерфейсами, применение которых в той части плана не необходимо.

Примеры приведены, даны пояснения для чего сделано то либо иное решение, дозволено переходить к техническим аспектам реализации, а реализация еще раз повторюсь поделена на две большие части:

  • Создание 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 <std::uint32_t, typename>
  struct Method;
}

#define DECLARE_PROXY_METHOD(iface_, mtd_, id_) 
  namespace Methods 
  { 
    typedef decltype(&iface_::mtd_) mtd_##Type; 
    template <typename R, typename C, typename ... P> 
    struct Method<id_, R (C::*)(P ...)> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct Method<id_, R (C::*)(P ...) const> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) const 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    typedef Method<id_, mtd_##Type> mtd_##ProxyType; 
  }

Дозволено подметить, что макрос определяет две специализации: для константного и неконстантного способов, и только одна будет из них инстанцирована для определенного способа. С поддержкой этого макроса и каждого что теснее получено ранее дозволено собрать теснее готовый прокси-класс:

DECLARE_PROXY_METHOD(ISessionManager, OpenSession, 1)
DECLARE_PROXY_METHOD(ISessionManager, CloseSession, 2)
DECLARE_PROXY_METHOD(ISessionManager, IsValidSession, 3)

template <typename ... T>
class Proxy
  : public T ...
{
};

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 <typename T>
    struct Crc32TableWrap
    {      
      static constexpr std::uint32_t const Table[256] =
      {
        0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
        0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
        0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
        // И т.д. заполнение таблицы
      };
    };

    template <typename T>
    constexpr std::uint32_t const Crc32TableWrap<T>::Table[256];

    typedef Crc32TableWrap<void> Crc32Table;

    template<std::uint32_t const I>
    inline constexpr std::uint32_t Crc32Impl(char const *str)
    {
      return (Crc32Impl <I - 1>(str) >> 8) ^
        Crc32Table::Table[(Crc32Impl<I - 1>(str) ^ str[I - 1]) & 0x000000FF];
    }

    template<>
    inline constexpr std::uint32_t Crc32Impl<0>(char const *)
    {
      return 0xFFFFFFFF;
    }

  }

  template <std::uint32_t N>
  inline constexpr std::uint32_t Crc32(char const (&str)[N])
  {
    return (Private::Crc32Impl<sizeof(str)>(str) ^ 0xFFFFFFFF);
  }
}

Сейчас макрос определения прокси-объекта и его применение становятся чуть больше дружественными, так как необходимости раздачи идентификаторов способам возложена на компилятор.

Пример обновленного макроса и его применение

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;
};

typedef decltype(&ISessionManager::OpenSession) OpenSessionMtdType;
typedef decltype(&ISessionManager::CloseSession) CloseSessionMtdType;
typedef decltype(&ISessionManager::IsValidSession) IsValidSessionMtdType;

namespace Methods
{
  template <std::uint32_t, typename>
  struct Method;
}

#define DECLARE_PROXY_METHOD(iface_, mtd_) 
  namespace Methods 
  { 
    enum {mtd_##Id = Crc32(#mtd_)}; 
    typedef decltype(&iface_::mtd_) mtd_##Type; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...)> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) const 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    typedef Method<mtd_##Id, mtd_##Type> mtd_##ProxyType; 
  }

DECLARE_PROXY_METHOD(ISessionManager, OpenSession)
DECLARE_PROXY_METHOD(ISessionManager, CloseSession)
DECLARE_PROXY_METHOD(ISessionManager, IsValidSession)

template <typename ... T>
class Proxy
  : public T ...
{
};

typedef Proxy
          <
            Methods::OpenSessionProxyType,
            Methods::CloseSessionProxyType,
            Methods::IsValidSessionProxyType
          >
        SessionManagerProxy;

Невзирая на то, что в C 11 допустимо наследование от шаблонного параметра, тот, что является пакетом типов (образцы с переменным числом параметров), запись

typedef Proxy
          <
            Methods::OpenSessionProxyType,
            Methods::CloseSessionProxyType,
            Methods::IsValidSessionProxyType
          >
        SessionManagerProxy;

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

template <std::uint32_t>
struct TypeRegistry;

#define REGIDTER_TYPE(id_, type_) 
  template <> 
  struct TypeRegistry<id_> 
  { 
    typedef type_ Type; 
  };

Крайне простая реализация вновь основанная на частных специализациях. Добавив это в ранее рассмотренный макрос определения способа он приблизится еще на шаг к финальной цели.

Обновленный макрос и пример его применения

#define DECLARE_TYPE_REGISTRY(reg_name_) 
  namespace reg_name_ 
  { 
    template <std::uint32_t> 
    struct TypeRegistry; 
  }

#define REGIDTER_TYPE(id_, type_) 
  template <> 
  struct TypeRegistry<id_> 
  { 
    typedef type_ Type; 
  };

DECLARE_TYPE_REGISTRY(Methods)

namespace Methods
{
  template <std::uint32_t, typename>
  struct Method;
}

#define DECLARE_PROXY_METHOD(iface_, mtd_, id_) 
  namespace Methods 
  { 
    enum {mtd_##Id = Crc32(#mtd_)}; 
    typedef decltype(&iface_::mtd_) mtd_##Type; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...)> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual iface_ 
    { 
      virtual R mtd_ (P ... p) const 
      { 
        throw std::runtime_error("Not implemented."); 
      } 
    }; 
    typedef Method<mtd_##Id, mtd_##Type> mtd_##ProxyType; 
    REGIDTER_TYPE(id_, mtd_##ProxyType) 
  }

DECLARE_PROXY_METHOD(ISessionManager, OpenSession, 1)
DECLARE_PROXY_METHOD(ISessionManager, CloseSession, 2)
DECLARE_PROXY_METHOD(ISessionManager, IsValidSession, 3)

template <typename ... T>
class Proxy
  : public T ...
{
};

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 <unsigned N>
  struct Hierarchy
    : public Hierarchy<N - 1>
  {
  };

  template <>
  struct Hierarchy<0>
  {
  };
}

#define INIT_STATIC_COUNTER(counter_name_, max_count_) 
  namespace counter_name_ 
  { 
    typedef ::Private::Hierarchy<max_count_> CounterHierarchyType; 
    char (&GetCounterValue(void const *))[1]; 
  }

#define GET_NEXT_STATIC_COUNTER(counter_name_, value_name_) 
  namespace counter_name_ 
  { 
    enum { value_name_ = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0))) }; 
    char (&GetCounterValue(::Private::Hierarchy<value_name_> const *))[value_name_   1]; 
  }

Для того, Дабы опробовать работу такого счетчика дозволено написать тест:

INIT_STATIC_COUNTER(MyCounter, 100)

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;
}

В итоге на экране будут распечатаны значения от одного до 3. Это дозволено отправить компилятору с ключом -E для того Дабы развернуть все макросы.

Код позже разворачивания макросов

#include <iostream>
namespace Private
{

  template <unsigned N>
  struct Hierarchy
    : public Hierarchy<N - 1>
  {
  };

  template <>
  struct Hierarchy<0>
  {
  };
}
namespace MyCounter
{
  typedef ::Private::Hierarchy<100> CounterHierarchyType; char (&GetCounterValue(void const *))[1];
}

namespace MyCounter
{
  enum
  {
    Item1 = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
  };
  char (&GetCounterValue(::Private::Hierarchy<Item1> const *))[Item1   1];
}

namespace MyCounter
{
  enum
  {
    Item2 = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
  };
  char (&GetCounterValue(::Private::Hierarchy<Item2> const *))[Item2   1];
}

namespace MyCounter
{
  enum
  {
    Item3 = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
  };
  char (&GetCounterValue(::Private::Hierarchy<Item3> const *))[Item3   1];
}

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 <std::uint32_t, typename> 
        struct Method; 
      }

#define ADD_PROXY_METHOD(mtd_) 
  GET_NEXT_STATIC_COUNTER(MtdCounter, mtd_##Counter) 
  namespace Methods 
  { 
    enum {mtd_##Id = Crc32(#mtd_)}; 
    typedef decltype(&IFaceType::mtd_) mtd_##Type; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...)> 
      : public virtual IFaceType 
    { 
      virtual R mtd_ (P ... p) 
      { 
        throw std::runtime_error(#mtd_ " not implemented."); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual IFaceType 
    { 
      virtual R mtd_ (P ... p) const 
      { 
        throw std::runtime_error(#mtd_ " not implemented."); 
      } 
    }; 
    typedef Method<mtd_##Id, mtd_##Type> mtd_##ProxyType; 
    REGIDTER_TYPE(ProxiesReg, MtdCounter::mtd_##Counter, mtd_##ProxyType) 
  }

#define END_PROXY_MAP() 
      GET_NEXT_STATIC_COUNTER(MtdCounter, LastCounter) 
      template <unsigned I> 
      class ProxyItem 
        : public Methods::ProxiesReg::TypeRegistry<I>::Type 
        , public ProxyItem<I - 1> 
      { 
      }; 
      template <> 
      class ProxyItem<0> 
      { 
      }; 
    } 
    typedef Impl::ProxyItem<Impl::MtdCounter::LastCounter - 1> Proxy; 
  }

Вспомогательный код

namespace Private
{
  template <unsigned N>
  struct Hierarchy
    : public Hierarchy<N - 1>
  {
  };

  template <>
  struct Hierarchy<0>
  {
  };
}

#define INIT_STATIC_COUNTER(counter_name_, max_count_) 
  namespace counter_name_ 
  { 
    typedef ::Private::Hierarchy<max_count_> CounterHierarchyType; 
    char (&GetCounterValue(void const *))[1]; 
  }

#define GET_NEXT_STATIC_COUNTER(counter_name_, value_name_) 
  namespace counter_name_ 
  { 
    enum { value_name_ = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0))) }; 
    char (&GetCounterValue(::Private::Hierarchy<value_name_> const *))[value_name_   1]; 
  }

#define DECLARE_TYPE_REGISTRY(reg_name_) 
  namespace reg_name_ 
  { 
    template <std::uint32_t> 
    struct TypeRegistry; 
  }

#define REGIDTER_TYPE(reg_name_, id_, type_) 
  namespace reg_name_ 
  { 
    template <> 
    struct TypeRegistry<id_> 
    { 
      typedef type_ Type; 
    }; 
  }

Пользовательский код

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;
};

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 <unsigned N>
  struct Hierarchy
    : public Hierarchy<N - 1>
  {
  };

  template <>
  struct Hierarchy<0>
  {
  };
}

namespace ISessionManagerPS
{
  namespace Impl
  {
    typedef ISessionManager IFaceType;
    namespace MtdCounter
    {
      typedef ::Private::Hierarchy<100> CounterHierarchyType;
      char (&GetCounterValue(void const *))[1];
    }
    namespace Methods
    {
      namespace ProxiesReg
      {
        template <std::uint32_t>
        struct TypeRegistry;
      }
      template <std::uint32_t, typename>
      struct Method;
    }
    namespace MtdCounter
    {
      enum
      {
        OpenSessionCounter = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
      };
      char (&GetCounterValue(::Private::Hierarchy<OpenSessionCounter> const *))[OpenSessionCounter   1];
    }
    namespace Methods
    {
      enum
      {
        OpenSessionId = Crc32("OpenSession")
      };
      typedef decltype(&IFaceType::OpenSession) OpenSessionType;
      template <typename R, typename C, typename ... P>
      struct Method<OpenSessionId, R (C::*)(P ...)>
        : public virtual IFaceType
      {
        virtual R OpenSession (P ... p)
        {
          throw std::runtime_error("OpenSession" " not implemented.");
        }
      };
      template <typename R, typename C, typename ... P>
      struct Method<OpenSessionId, R (C::*)(P ...) const>
        : public virtual IFaceType
      {
        virtual R OpenSession (P ... p) const
        {
          throw std::runtime_error("OpenSession" " not implemented.");
        }
      };
      typedef Method<OpenSessionId, OpenSessionType> OpenSessionProxyType;
      namespace ProxiesReg
      {
        template <>
        struct TypeRegistry<MtdCounter::OpenSessionCounter>
        {
          typedef OpenSessionProxyType Type;
        };
      }
    }
    namespace MtdCounter
    {
      enum
      {
        CloseSessionCounter = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
      };
      char (&GetCounterValue(::Private::Hierarchy<CloseSessionCounter> const *))[CloseSessionCounter   1];
    }
    namespace Methods
    {
      enum
      {
        CloseSessionId = Crc32("CloseSession")
      };
      typedef decltype(&IFaceType::CloseSession) CloseSessionType;
      template <typename R, typename C, typename ... P>
      struct Method<CloseSessionId, R (C::*)(P ...)>
        : public virtual IFaceType
      {
        virtual R CloseSession (P ... p)
        {
          throw std::runtime_error("CloseSession" " not implemented.");
        }
      };
      template <typename R, typename C, typename ... P>
      struct Method<CloseSessionId, R (C::*)(P ...) const>
        : public virtual IFaceType
      {
        virtual R CloseSession (P ... p) const
        { 
          throw std::runtime_error("CloseSession" " not implemented.");
        }
      };
      typedef Method<CloseSessionId, CloseSessionType> CloseSessionProxyType;
      namespace ProxiesReg
      {
        template <>
        struct TypeRegistry<MtdCounter::CloseSessionCounter>
        {
          typedef CloseSessionProxyType Type;
        };
      }
    }
    namespace MtdCounter
    {
      enum
      {
        IsValidSessionCounter = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
      };
      char (&GetCounterValue(::Private::Hierarchy<IsValidSessionCounter> const *))[IsValidSessionCounter   1];
    }
    namespace Methods
    {
      enum
      {
        IsValidSessionId = Crc32("IsValidSession")
      };
      typedef decltype(&IFaceType::IsValidSession) IsValidSessionType;
      template <typename R, typename C, typename ... P>
      struct Method<IsValidSessionId, R (C::*)(P ...)>
        : public virtual IFaceType
      {
        virtual R IsValidSession (P ... p)
        {
          throw std::runtime_error("IsValidSession" " not implemented.");
        }
      };
      template <typename R, typename C, typename ... P>
      struct Method<IsValidSessionId, R (C::*)(P ...) const>
        : public virtual IFaceType
      {
        virtual R IsValidSession (P ... p) const
        {
          throw std::runtime_error("IsValidSession" " not implemented.");
        }
      };
      typedef Method<IsValidSessionId, IsValidSessionType> IsValidSessionProxyType;
      namespace ProxiesReg
      {
        template <>
        struct TypeRegistry<MtdCounter::IsValidSessionCounter>
        {
          typedef IsValidSessionProxyType Type;
        };
      }
    }
    namespace MtdCounter
    {
      enum
      {
        LastCounter = sizeof(GetCounterValue(static_cast<CounterHierarchyType const *>(0)))
      };
      char (&GetCounterValue(::Private::Hierarchy<LastCounter> const *))[LastCounter   1];
    }
    template <unsigned I>
    class ProxyItem
        : public Methods::ProxiesReg::TypeRegistry<I>::Type
        , public ProxyItem<I - 1>
    {
    };
    template <>
    class ProxyItem<0>
    {
    };
  }
  typedef Impl::ProxyItem<Impl::MtdCounter::LastCounter - 1> Proxy;
}

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 <typename T>
class ProxyMethodBase
{
public:
  template <typename R, typename ... P>
  R Execute(std::uint32_t mtdId, P ... params)
  {
    return dynamic_cast<T &>(*this).Execute<R, P ...>(mtdId, params ...);
  }
protected:
  virtual ~ProxyMethodBase()
  {
  }
};

Так как все реализации способов наследуют ProxyMethodBase виртуально, то в реализации ProxyMethodBase придется применять dynamic_cast, а не больше знакомый static_cast для CRTP. Виртуальное наследование необходимо для того, Дабы для всех способов определить одну базу и Дабы компилятор не испытывал затруднения при определении ветки наследования при приведении типа. Воображаемый деструктор в ProxyMethodBase необходим как минимум для того же, Дабы компилятор мог трудиться с dynamic_cast, в отвратном случае он скажет, что тип, к которому производится попытка приведения с поддержкой dynamic_cast не является полиморфным, а должен.

С учетом добавленного базового класса для всех реализаций способов интерфейса немножко меняются и макросы изложения прокси-класса.

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 <std::uint32_t, typename> 
        struct Method; 
      }

ADD_PROXY_METHOD

#define ADD_PROXY_METHOD(mtd_) 
  GET_NEXT_STATIC_COUNTER(MtdCounter, mtd_##Counter) 
  namespace Methods 
  { 
    enum {mtd_##Id = Crc32(#mtd_)}; 
    typedef decltype(&IFaceType::mtd_) mtd_##Type; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...)> 
      : public virtual IFaceType 
      , public virtual ProxyMethodBase<ProxyImpl> 
    { 
      virtual R mtd_ (P ... p) 
      { 
        return Execute<R, P ...>(mtd_##Id, p ...); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct Method<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual IFaceType 
      , public virtual ProxyMethodBase<ProxyImpl> 
    { 
      typedef Method<mtd_##Id, R (C::*)(P ...) const> ThisType; 
      virtual R mtd_ (P ... p) const 
      { 
        return const_cast<ThisType *>(this)->Execute<R, P ...>(mtd_##Id, p ...); 
} 
    }; 
    typedef Method<mtd_##Id, mtd_##Type> mtd_##ProxyType; 
    REGIDTER_TYPE(ProxiesReg, MtdCounter::mtd_##Counter, mtd_##ProxyType) 
  }

END_PROXY_MAP

#define END_PROXY_MAP() 
      GET_NEXT_STATIC_COUNTER(MtdCounter, LastCounter) 
      template <unsigned I> 
      class ProxyItem 
        : public Methods::ProxiesReg::TypeRegistry<I>::Type 
        , public ProxyItem<I - 1> 
      { 
      }; 
      template <> 
      class ProxyItem<0> 
      { 
      }; 
      class ProxyImpl 
        : public ProxyItem<MtdCounter::LastCounter - 1> 
      { 
      public: 
      private: 
        friend class ProxyMethodBase<ProxyImpl>; 
        template <typename R, typename ... P> 
        R Execute(std::uint32_t mtdId, P ... params) 
        { 
          throw std::runtime_error("Not implemented."); 
        } 
      }; 
    } 
    typedef Impl::ProxyImpl Proxy; 
  }

Пользовательский код же никак не меняется. В то же время в реализации прокси-класса возникла одна локализованная точка, в которой дозволено изготавливать все манипуляции с параметрами и работу с сервером. Если посмотреть на способ прокси-класса, куда в итоге приходит вызов способа интерфейса

template <typename R, typename ... P>
R Execute(std::uint32_t mtdId, P ... params)
{
  // ...
}

то тут теснее дозволено воспользоваться классической схемой перебора всех параметров вызванного способа для их сериализации, которая фактически во всех источниках, посвященных C 11 приводится на примере реализации типа неопасной функции printf.

template <typename S>
void Serialize(S &)
{
}

template <typename S, typename T, typename ... P>
void Serialize(S &stream, T prm, P ... params)
{
  stream << prm;
  Serialize(stream, params ...);
}

И добавив вызов Serialize в способ Execute дозволено получить сериализацию параметров. Остается только реализовать свой класс для сериализации. Это рутинная и неинтересная задача. Ее решение на основе xml и rapidxml [6] приведено в начальных кодах прилагаемых к этому посту.

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

Выше говорилось о Proxy/Stub, а пока реализована работа только с прокси. Реализация классов-заглушек (Stub) примерно аналогичны реализации прокси-классов. Различие заключается в том, что для прокси-класса необходимо при вызове способа интерфейса всю информацию упаковать и отправить, а полученный итог отдать вызывающей стороне, а для класса-заглушки необходимо исполнить все сурово напротив: распаковать полученный пакет, на его основе собрать параметры в определенный список доводов способа интерфейса и вызвать его, а полученный итог отправить вызывающей стороне. Для этого необходимо в существующие макросы добавить немножко изменений, связанных с вызовом способа с параметрами, извлеченными из пришедшего пакета. Все остальное остается таким же. Так же необходим и реестр, и счетчик, и сбор финального класса из его «кубиков». Реестра сейчас становится два: один для прокси-классов, 2-й для класса-заглушек. Дозволено и в один все разместить, а потом разбирать какой элемент чем является. Это приведет к больше трудному коду. Следственно добавлен 2-й реестр для объектов-заглушек.

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 <std::uint32_t, typename> 
        struct ProxyMethod; 
        DECLARE_TYPE_REGISTRY(StubsReg) 
        template <std::uint32_t, typename> 
        struct StubMethod; 
      }

PS_ADD_METHOD

#define PS_ADD_METHOD(mtd_) 
  GET_NEXT_STATIC_COUNTER(MtdCounter, mtd_##Counter) 
  namespace Methods 
  { 
    enum {mtd_##Id = Crc32(#mtd_)}; 
    typedef decltype(&IFaceType::mtd_) mtd_##Type; 
    template <typenameR, typename C, typename ... P> 
    struct ProxyMethod<mtd_##Id, R (C::*)(P ...)> 
      : public virtual IFaceType 
      , public virtual ProxyMethodBase<ProxyImpl> 
    { 
      virtual R mtd_ (P ... p) 
      { 
        return Execute<R, P ...>(mtd_##Id, p ...); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct ProxyMethod<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual IFaceType 
      , public virtual ProxyMethodBase<ProxyImpl> 
    { 
      typedef ProxyMethod<mtd_##Id, R (C::*)(P ...) const> ThisType; 
      virtual R mtd_ (P ... p) const 
      { 
        return const_cast<ThisType *>(this)->Execute<R, P ...>(mtd_##Id, p ...); 
      } 
    }; 
    typedef ProxyMethod<mtd_##Id, mtd_##Type> mtd_##ProxyType; 
    REGIDTER_TYPE(ProxiesReg, MtdCounter::mtd_##Counter, mtd_##ProxyType) 
    template <typename R, typename C, typename ... P> 
    struct StubMethod<mtd_##Id, R (C::*)(P ...)> 
      : public virtual StubMethodBase<StubImpl> 
    { 
      template <typename TPkg> 
      R Call(TPkg &pack) 
      { 
        return Call(mtd_##Id, pack, &IFaceType::mtd_); 
      } 
    }; 
    template <typename R, typename C, typename ... P> 
    struct StubMethod<mtd_##Id, R (C::*)(P ...) const> 
      : public virtual StubMethodBase<StubImpl> 
    { 
      template <typename TPkg> 
      R Call(TPkg &pack) 
      { 
        return Call(mtd_##Id, pack, &IFaceType::mtd_); 
      } 
    }; 
    typedef StubMethod<mtd_##Id, mtd_##Type> mtd_##StubType; 
    REGIDTER_TYPE(StubsReg, MtdCounter::mtd_##Counter, mtd_##StubType) 
  }

PS_END_MAP

#define PS_END_MAP() 
      GET_NEXT_STATIC_COUNTER(MtdCounter, LastCounter) 
      template <unsigned I> 
      class ProxyItem 
        : public Methods::ProxiesReg::TypeRegistry<I>::Type 
        , public ProxyItem<I - 1> 
      { 
      }; 
      template <> 
      class ProxyItem<0> 
      { 
      }; 
      class ProxyImpl 
        : public ProxyItem<MtdCounter::LastCounter - 1> 
      { 
      public: 
      private: 
        friend class ProxyMethodBase<ProxyImpl>; 
        template <typename R, typename ... P> 
        R Execute(std::uint32_t mtdId, P ... params) 
        { 
          Serialize(std::cout, params ...); 
          throw std::runtime_error("Not implemented."); 
        } 
      }; 
      template <unsigned I> 
      class StubItem 
        : public Methods::StubsReg::TypeRegistry<I>::Type 
        , public StubItem<I - 1> 
      { 
      }; 
      template <> 
      class StubItem<0> 
      { 
      }; 
      class StubImpl 
        : public StubItem<MtdCounter::LastCounter - 1> 
      { 
      public: 
      private: 
        friend class StubMethodBase<StubImpl>; 
        template <typename C, typename R, typename ... P> 
        R Call(std::uint32_t mtdId, R (C::*mtd)(P ...)) 
        { 
          throw std::runtime_error("Not implenented."); 
        } 
      }; 
    } 
    typedef Impl::ProxyImpl Proxy; 
    typedef Impl::StubImpl Stub; 
  }

Пользовательский код

ruct 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;
};

PS_BEGIN_MAP(ISessionManager)
  PS_ADD_METHOD(OpenSession)
  PS_ADD_METHOD(CloseSession)
  PS_ADD_METHOD(IsValidSession)
PS_END_MAP()

int main()
{
  try
  {
    ISessionManagerPS::Proxy Proxy;
    ISessionManagerPS::Stub Stub;
  }
  catch (std::exception const &e)
  {
    std::cerr << e.what() << std::endl;
  }
  return 0;
}

Немножко поменялись имена макросов и некоторых сущностей под ними и добавлен код по образованию классов-заглушек. Сейчас это полновесное изложение Proxy/Stub, которое дозволено применять при реализации собственного RPC на основе интерфейсов. В рамках же данного поста будет приведена некоторая инфраструктура и работа с транспортным ярусом, что даст предложенным идеям завершенность и вероятность их применения как готового продукта. А пока еще раз заострю внимание на некоторых различиях реализации классов-заглушек от прокси-классов. Если заглянуть в макрос PS_ADD_METHOD, то дозволено увидеть, что в него добавлены частные специализации для реализации классов, которые реализуют заглушки для всякого из способов. Различие же таких реализаций заглушек от прокси в том, что тип, реализующий один из способов прокси наследуется от интерфейса, а тип, реализующий способ заглушки такого наследования не имеет, так как оно ему не необходимо; при реализации заглушки необходимо получить адрес способа интерфейса, тот, что будет в последующем вызван. Основные отличия, взятые из макроса PS_ADD_METHOD приведены ниk!>#define PS_BEGIN_MAP(iface_) namespace iface_##_PS { DECLARE_RUNTIME_EXCEPTION(Proxy) DECLARE_RUNTIME_EXCEPTION(Stub) namespace PSImpl { template <typename> class Proxy; class Stub; typedef iface_ IFaceType; enum { IFaceId = ::Remote::Crc32(#iface_) }; namespace Methods { INIT_STATIC_COUNTER(PSCounter, PS_MAX_METHOD_COUNT) namespace MethodsProxy { typedef Proxy<void> ProxyClass; DECLARE_TYPE_REGISTRY(ProxyTypeItemsReg) template <typename TProxy, std::uint32_t Id, typename T> class ProxyMethodImpl; } namespace MethodsStub { DECLARE_TYPE_REGISTRY(StubTypeItemsReg) template <std::uint32_t Id, typename T> class StubMethodImpl; } }

 

PS_ADD_METHOD

#define PS_ADD_METHOD(mtd_) 
  namespace Methods 
  { 
    GET_NEXT_STATIC_COUNTER(PSCounter, mtd_##Counter) 
    typedef decltype(&IFaceType::mtd_) mtd_##Type; 
    enum { mtd_##Id = Remote::Crc32(#mtd_) }; 
    namespace MethodsProxy 
    { 
      template <typename TProxy, typename C, typename R, typename ... P> 
      class ProxyMethodImpl<TProxy, mtd_##Id, R (C::*)(P ... )> 
        : public virtual IFaceType 
        , public virtual ::Remote::Private::PoxyMethodIFace<TProxy> 
      { 
      private: 
        virtual R mtd_ (P ... p) 
        { 
          return this->template __ProxyExecute<R, P ...>(mtd_##Id, p ...); 
        } 
      }; 
      template <typename TProxy, typename C, typename R, typename ... P> 
      class ProxyMethodImpl<TProxy, mtd_##Id, R (C::*)(P ... ) const> 
        : public virtual IFaceType 
        , public virtual ::Remote::Private::PoxyMethodIFace<TProxy> 
      { 
      private: 
        typedef ProxyMethodImpl<TProxy, mtd_##Id, R (C::*)(P ... ) const> ThisType; 
        virtual R mtd_ (P ... p) const 
        { 
          return const_cast<ThisType *>(this)->template __ProxyExecute<R, P ...>(mtd_##Id, p ...); 
        } 
      }; 
      typedef ProxyMethodImpl<ProxyClass, mtd_##Id, mtd_##Type> mtd_##ProxyImpl; 
      REGISTRY_ADD_TYPE(ProxyTypeItemsReg, PSCounter::mtd_##Counter, mtd_##ProxyImpl) 
    } 
    namespace MethodsStub 
    { 
      template <typename C, typename R, typename ... P> 
      class StubMethodImpl<mtd_##Id, R (C::*)(P ... )> 
        : public virtual ::Remote::Private::StubMethodIFace<Stub> 
      { 
      public: 
        enum { MethodId = mtd_##Id }; 
        template <typename TPkg> 
        R __Call(TPkg &pkg) 
        { 
          return this->__StubCall(pkg, &IFaceType::mtd_); 
        } 
      }; 
      template <typename C, typename R, typename ... P> 
      class StubMethodImpl<mtd_##Id, R (C::*)(P ... ) const> 
        : public virtual ::Remote::Private::StubMethodIFace<Stub> 
      { 
      public: 
        enum { MethodId = mtd_##Id }; 
        template <typename TPkg> 
        R __Call(TPkg &pkg) 
        { 
          return this->__StubCall(pkg, &IFaceType::mtd_); 
        } 
      }; 
      typedef StubMethodImpl<mtd_##Id, mtd_##Type> mtd_##StubImpl; 
      REGISTRY_ADD_TYPE(StubTypeItemsReg, PSCounter::mtd_##Counter, mtd_##StubImpl) 
    } 
  }

 

PS_END_MAP

#define PS_END_MAP() 
      namespace Methods 
      { 
        GET_NEXT_STATIC_COUNTER(PSCounter, LastPSCounter) 
        namespace MethodsProxy 
        { 
          template <typename T, typename TPack> 
          struct ChangePackPrm; 
          template <typename TPack, std::uint32_t Id, typename TMtd, typename TNewPack> 
          struct ChangePackPrm<ProxyMethodImpl<TPack, Id, TMtd>, TNewPack> 
          { 
            typedef ProxyMethodImpl<Proxy<TNewPack>, Id, TMtd> Type; 
          }; 
          template <typename TPack, std::size_t const N> 
          class ProxyMethodsImpl 
            : public ProxyMethodsImpl<TPack, N - 1> 
            , public ChangePackPrm<typename ProxyTypeItemsReg<N>::Type, TPack>::Type 
          { 
          }; 
          template <typename TPack> 
          class ProxyMethodsImpl<TPack, 0> 
            : public virtual IFaceType 
          { 
          }; 
        } 
      } 
      template <typename TPack> 
      class Proxy 
        : public Methods::MethodsProxy::ProxyMethodsImpl<TPack, Methods::PSCounter::LastPSCounter - 1> 
      { 
      public: 
        Proxy(std::uint32_t instId, ::Remote::IRemotingPtr remoting) 
          : InstId(instId) 
          , Remoting(remoting) 
        { 
          if (!Remoting) 
            throw ProxyException("IRemoting pointer must not be null."); 
        } 
      private: 
        template <typename> 
        friend class ::Remote::Private::PoxyMethodIFace; 
        std::uint32_t InstId; 
        ::Remote::IRemotingPtr Remoting; 
        template <typename R, typename ... P> 
        R __ProxyExecuteImpl(std::uint32_t mtdId, P ... p) 
        { 
          TPack Pack(IFaceId, mtdId, InstId, 0/*thread*/, p ...); 
          auto Buf(std::move(Remoting->Execute(Pack.GetData(), Pack.GetSize()))); 
          auto const &BufRef = *Buf; 
          auto NewPack(std::move(TPack::CreateFromData(&BufRef[0], BufRef.size()))); 
          if (NewPack->GetIFaceId() != IFaceId) 
            throw ProxyException("Bad interface Id."); 
          if (NewPack->GetInstId() != InstId) 
            throw ProxyException("Bad instance Id."); 
          if (NewPack->GetMethodId() != mtdId) 
            throw ProxyException("Bad method Id."); 
          auto Exception = NewPack->template GetException<::Remote::RemotingException>(); 
          if (Exception != std::exception_ptr()) 
            std::rethrow_exception(Exception); 
          ::Remote::Private::UpdateOutputParams<decltype(*NewPack), P ...>(*NewPack, p ...); 
          LastPack = std::move(::Remote::Private::IDisposablePtr(new ::Remote::Private::Disposable<decltype(NewPack)>(NewPack))); 
          return ::Remote::Private::ResultExtractor<R>::Extract(*NewPack); 
        } 
      private: 
        ::Remote::Private::IDisposablePtr LastPack; 
      }; 
      namespace Methods 
      { 
        namespace MethodsStub 
        { 
          template <std::size_t const N> 
          class StubMethodsImpl 
            : public StubMethodsImpl<N - 1> 
            , public StubTypeItemsReg<N>::Type 
          { 
          public: 
            template <typename TPkg> 
            void __StubMethodCall(TPkg &pkg, std::uint32_t mtdId) 
            { 
              typedef typename StubTypeItemsReg<N>::Type StubMtdType; 
              if (mtdId == StubMtdType::MethodId) 
              { 
                typedef decltype(StubMtdType::__Call(pkg)) R; 
                ::Remote::Private::CallAndPackRes<R>::Call(this, &StubMtdType::__Call, pkg); 
                return; 
              } 
              StubMethodsImpl<N - 1>::__StubMethodCall(pkg, mtdId); 
            } 
          }; 
          template <> 
          class StubMethodsImpl<0> 
          { 
          public: 
            template <typename TPkg> 
            void __StubMethodCall(TPkg &, std::uint32_t) 
            { 
              throw StubException("Method not found."); 
            } 
          }; 
        } 
      } 
      class Stub 
        : public Methods::MethodsStub::StubMethodsImpl<Methods::PSCounter::LastPSCounter - 1> 
      { 
      public: 
        typedef std::shared_ptr<IFaceType> IFaceTypePtr; 
        Stub(std::uint32_t instId, IFaceTypePtr obj) 
          : InstId(instId) 
          , Obj(obj) 
        { 
          if (!Obj) 
            throw StubException("IFace pointer must not be null."); 
        } 
        virtual ~Stub() 
        { 
        } 
        template <typename TPack> 
        ::Remote::DataBufPtr Call(TPack &pack) 
        { 
          try 
          { 
            if (pack.GetIFaceId() != IFaceId) 
              throw StubException("Bad interface Id."); 
            if (pack.GetInstId() != InstId) 
              throw StubException("Bad instance Id."); 
            this->__StubMethodCall(pack, pack.GetMethodId()); 
            pack.PackParams(); 
          } 
          catch (std::exception const &e) 
          { 
            pack.GetPack().SetException(e); 
          } 
          ::Remote::DataBufPtr Ret(new ::Remote::DataBuf); 
          auto &NewPack = pack.GetPack(); 
          auto Size = NewPack.GetSize(); 
          if (Size) 
          { 
            void const *Data = NewPack.GetData(); 
            std::copy( 
                reinterpret_cast<char const *>(Data), 
                reinterpret_cast<char const *>(Data)   Size, 
                std::back_inserter(*Ret) 
              ); 
          } 
          return std::move(Ret); 
        } 
      private: 
        template <typename> 
        friend class ::Remote::Private::StubMethodIFace; 
        std::uint32_t InstId; 
        IFaceTypePtr Obj; 
        template <typename TPkg, typename R, typename C, typename ... P> 
        R __StubCallImpl(TPkg &pkg, R (C::*mtd)(P ...)) 
        { 
          return ::Remote::Private::CallStubMethod::Call(Obj.get(), mtd, pkg); 
        } 
        template <typename TPkg, typename R, typename C, typename ... P> 
        R __StubCallImpl(TPkg &pkg, R (C::*mtd)(P ...) const) 
        { 
          return ::Remote::Private::CallStubMethod::Call(Obj.get(), mtd, pkg); 
        } 
      }; 
    } 
    template <typename T> 
    using Proxy = PSImpl::Proxy<T>; 
    using Stub = PSImpl::Stub; 
  }

Результаты создания Proxy/Stub


Подведу выводы последовательности шагов создания Proxy/Stub:

  • Получить тип способа с поддержкой decltype.
  • Для всякого способа вычислить его идентификатор с поддержкой CRC32 от имени способа.
  • Сделать по реализации для всякого способа интерфейса, базируясь на специализациях некоторого образца, реализующего способ интерфейса.
  • Вести счетчик способов во время компиляции.
  • Добавлять полученные реализации прокси-классов и классов-заглушек в реестры прокси и заглушек.
  • В конце построения реализаций способов получить последнее значение счетчика способов.
  • Пройти по каждому реестру (по отдельности для прокси реализаций и реализаций заглушек) с поддержкой полученных значений счетчика способов и возвести иерархию классов на основе типов, расположенных в реестре. Всякая такая иерархия будет являться завершенной реализацией прокси-класса либо класса-заглушки.

Инфраструктура и транспорт


Под инфраструктурой будет подразумеваться определенный комплект сущностей, тот, что на стороне сервера будет отвечать за создание реальных объектов-реализаций и их заглушек, а так же руководить их временем жизни, а на стороне заказчика комплект сущностей для создания прокси-объектов, возвращаемых заказчику при запросе того либо другого объекта реализации с заданным интерфейсом и расположенным на сервере.

Под транспортом подразумевается что-то, что разрешает обмениваться пакетами данных между заказчиком и сервером. В данной реализации это сделано в виде надстройки над libevent (ее http-функционалом). Для этого взята примерно без изменений реализация http-сервера из [5] и дополнена клиентской частью отправки http-запросов.

Основными интерфейсами для заказчика и сервера, которые входят в инфраструктуру, являются интерфейс фабрики классов

namespace Remote
{  
  struct IClassFactory
  {
    virtual ~IClassFactory() {}
    virtual std::uint32_t CreateObject(std::uint32_t classId) = 0;
  };  
}

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

namespace Remote
{
  struct IObjectDeleter
  {
    virtual ~IObjectDeleter() {}
    virtual void DeleteObject(std::uint32_t instId) = 0;
  }; 
}

Реализации этих интерфейсов передаются заказчику с предопределенными идентификаторами их объектов. Заказчик, позже создания объекта, отвечающего за транспортный ярус, обращается к серверу за приобретением этих 2-х интерфейсов по предопределенным идентификаторам и с применением все тех же Proxy/Stub’ов, рассмотренных выше.

Proxy/Stub для IClassFactory и IObjectDeleter

namespace Remote
{
  PS_BEGIN_MAP(IClassFactory)
    PS_ADD_METHOD(CreateObject)
  PS_END_MAP()

  PS_BEGIN_MAP(IObjectDeleter)
    PS_ADD_METHOD(DeleteObject)
  PS_END_MAP()
}

PS_REGISTER_PSTYPES(Remote::IClassFactory)
PS_REGISTER_PSTYPES(Remote::IObjectDeleter)

Получив же экземпляры этих объектов, заказчик создает через прокси-объект фабрики классов надобные ему объекты. На стороне сервера создается настоящий объект, которому присваивается определенный неповторимый идентификатор экземпляра, а так же создается и связанный с ним объект-заглушка. Идентификатор возвращается заказчику. На стороне заказчика создается прокси-объект, которому передается полученный идентификатор. Заказчик использует мудрые указатели (в данном случае — это std::shared_ptr и в качестве deleter’а ему передан так же разумный указатель на IObjectDeleter, под которым находится его прокси-объект) и когда прокси-объект уничтожается, на сервер отправляется его идентификатор экземпляра через интерфейс IObjectDeleter. Сервер на своей стороне, на основании этого идентификатора, разрушает настоящий объект и заглушку с ним связанную. Так происходит управление временем жизни объектов между заказчиком и сервером. В остальном же все вызовы прокси-объекта обрабатываются сервером на основании его идентификатора экземпляра, полученного при его создании.

Сервер


На стороне сервера создается http-сервер на основе [5], в качестве его обработчика запросов, передается объект, разбирающий входящие пакеты и отдающий их администратору объектов, в работу которого входит создание и удаление объектов, а так же определение объекта-исполнителя пришедшего запроса.

RequestHandler

template <typename TPkg, typename ... TImpls>
class RequestHandler final
  : private NonCopyable
{
public:
  RequestHandler()
    : Mgr(std::make_shared<ObjectManagerType>())
    , Creator(STUB_CLASS(IClassFactory)(SysClassId::ClassFactory, Mgr))
    , Deleter(STUB_CLASS(IObjectDeleter)(SysClassId::ObjectDeleter, Mgr))
  {
  }
  DataBufPtr Proccess(void const *data, std::size_t bytes)
  {
    if (!data || !bytes)
      throw RequestHandlerException("Request data must not be empty.");
    TPkg Pack(data, bytes);
    auto InstId = Pack.GetInstId();
    switch (InstId)
    {
    case SysClassId::ClassFactory :
      return std::move(Creator.Call(Pack));
    case SysClassId::ObjectDeleter :
      return std::move(Deleter.Call(Pack));
    default :
      break;
    }
    return std::move(Mgr->Call(InstId, Pack));
  }

private:
  typedef Private::ObjectManager<TPkg, TImpls ...> ObjectManagerType;
  std::shared_ptr<ObjectManagerType> Mgr;
  STUB_CLASS(IClassFactory) Creator;
  STUB_CLASS(IObjectDeleter) Deleter;
};

Где STUB_CLASS — это каждого лишь маленький макрос формирующий имя класса-заглушки из имени интерфейса

STUB_CLASS

#define STUB_CLASS(iface_) iface_ ## _PS::Stub

Администратор объектов — это реализация IClassFactory и IObjectDeleter, которая является образцом, а параметрами его служат тип реализующий сериализацию/десериализацию и список типов, описывающих информацию о том какие объекты с какими интерфейсами и идентификаторами реализаций сервер может создавать и обрабатывать.

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

Если посмотреть на примеры из начала поста, то на них видно, как создается сервер и как ему передается каждая информация о поддерживаемых типах.

Заказчик


На стороне заказчика создается http-заказчик, реализующий интерфейс IRemoting. Через реализацию этого интерфейса заказчик отправляет запросы серверу. Разумный указатель на данный интерфейс передается фабрике классов при ее создании. Так же фабрика классов при создании принимает тип, реализующий сериализацию/десериализацию и список поддерживаемых интерфейсов, объекты которых предполагается создавать на стороне сервера. В то же время сама фабрика классов не является образцом, но и не имеет открытого конструктора, а только шаблонный статический способ ее создания. Это разрешает отлично инкапсулировать всю информацию, передаваемую при создании фабрики и отказаться от необходимости ее наличия в месте применения фабрики.

Фабрика классов на стороне заказчика

class ClassFactory final
  : private NonCopyable
{
public:
  template <typename TPkg, typename ... TIFaces>
  static std::shared_ptr<ClassFactory> Create(IRemotingPtr remoting)
  {
    // Детали реализации
  }

  template <typename TIFace>
  std::shared_ptr<TIFace> Create(std::uint32_t classId)
  {
    // Детали реализации
  }
private:
  std::shared_ptr<Private::IProxyObjectManager> Creator;

  ClassFactory()
  {
  }
};


Нестатический способ фабрики Create принимает как шаблонный параметр интерфейс, на основании которого производится поиск прокси-класса, и обыкновенный не шаблонный параметр идентификатор реализации, под которым на сервере зарегистрирована реализация интерфейса. Фабрика классов на основании этой информации и информации, переданной при ее создании, делает запрос на сервер на создание объекта, а полученный идентификатор экземпляра отдает обнаруженному прокси-объекту. Пользователю возвращается std::shared_ptr, под которым лежит прокси-объект с указанным интерфейсом и в качестве deleter’а прокси-объект с интерфейсом IObjectDeleter, посылающий запрос на сервер при уничтожении прокси-объекта на стороне заказчика для разрушения его на стороне сервера.

Связь интерфейсов и Proxy/Stub


Посмотрев на серверную и клиентскую части может появиться вопрос: как заказчик и сервер объединяют информацию об интерфейсах с информацией о Proxy/Stub’ах?

При изложении Proxy/Stub, допустимо, Вы подметили макрос PS_REGISTER_PSTYPES, о котором пока ничего не говорилось. Данный макрос регистрирует сделанные Proxy/Stub’ы в некотором глобальном реестре типов в момент компиляции (схожем на реестры, используемые при реализации способов интерфейсов, с тем различием, что в качестве идентификатора применяется не число, а интерфейс). А когда фабрика классов на стороне заказчика либо администратор объектов на стороне сервера ищет надобную ему реализацию прокси-класса либо класса-заглушки, то делается обращение к этому реестру по типу интерфейса. Код поиска прокси либо заглушки так же создается в момент компиляции.

Реестр Proxy/Stub’ов

namespace Remote
{
  namespace Private
  {
    template <typename>
    struct ProxyStubRegistry;
  }
}

#define PROXY_CLASS(iface_, tpack_) iface_ ## _PS::Proxy<tpack_>

#define STUB_CLASS(iface_) iface_ ## _PS::Stub

#define PS_REGISTER_PSTYPES(iface_) 
  namespace Remote 
  { 
    namespace Private 
    { 
      template <> 
      struct ProxyStubRegistry<iface_> 
      { 
        enum { Id = Crc32(#iface_) }; 
        template <typename I> 
        using Proxy = PROXY_CLASS(iface_, I); 
        typedef STUB_CLASS(iface_) Stub; 
      }; 
    } 
  }


Казалось бы для чего еще один макрос для регистрации сделанных прокси и заглушек, и отчего бы эту регистрацию не сделать в макросе PS_END_MAP? Дозволено и там, но тогда придется пожертвовать вероятностью раскладывать интерфейсы по пространствам имен, а так же раскладывать по одноименным пространствам имен и их Proxy/Stub. Из кода макросов причину дозволено осознать: невозможно, находясь в некотором пространстве имен, сделать частную специализацию образца в ином пространстве имен, никак не связанном с нынешним. Здесь пришлось сделать выбор между отказом от пространств имен для пользовательского кода либо добавить еще один макрос. Выбор был сделан в пользу добавления нового макроса. Дозволить отказаться от пространств имен я себе не могу…

Результаты работы инфраструктуры


Подводя результаты инфраструктуры дозволено выделить следующие шаги по организации заказчик-серверного взаимодействия:

  • Необходима реализация транспорта.
  • Реализация на стороне сервера и заказчика сущностей для создания и управления объектами.
  • Серверная сторона должна иметь информацию о поддерживаемых ею объектах.
  • Клиентская сторона должна иметь информацию о поддерживаемых интерфейсах.
  • Необходим реестр типов, в котором беречь всю информацию о привязке Proxy/Stub к определенному интерфейсу, на основании которой заказчик создает прокси-объекты, а сервер объекты-заглушки.

Примеры


Один из примеров был приведен в самом начале поста. Еще один в качестве примера будет разработан. Дозволено придумать реализацию с теснее приятелем интерфейсом ISessionManager и добавив новейший IDateSoutce. Способы интерфейса ISessionManager будут изготавливать эмуляцию работы с сессиями пользователя: выдавать идентификатор сессии на основе переданного имени пользователя и пароля, проверять полученный идентификатор на валидность и закрывать сессию. Способы интерфейса IDateSource будут получать нынешнюю дату. Один способ для приобретения даты в виде строки «yyyy.mm.dd hh:mm:ss», а 2-й возвращает дату через выходные параметры по частям. Так же все способы интерфейсов могут генерировать исключения, которые будут передаваться на сторону заказчика и там выбрасываться в виде исключений типа Remote::RemotingException.

Интерфейсы

// ifaces.h
namespace Test
{
  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;
  };
  struct IDateSource
  {
    virtual ~IDateSource() {}
    virtual void GetDate(std::uint16_t *year, std::uint16_t *month, std::uint16_t *day,
                         std::uint16_t *hour, std::uint16_t *min, std::uint16_t *sec) const = 0;
    virtual char const* GetDateAsString() const = 0;
  };
}

Изложение Proxy/Stub’ов

// ifaces_ps.h
namespace Test
{
  PS_BEGIN_MAP(ISessionManager)
    PS_ADD_METHOD(OpenSession)
    PS_ADD_METHOD(CloseSession)
    PS_ADD_METHOD(IsValidSession)
  PS_END_MAP()

  PS_BEGIN_MAP(IDateSource)
    PS_ADD_METHOD(GetDate)
    PS_ADD_METHOD(GetDateAsString)
  PS_END_MAP()
}
PS_REGISTER_PSTYPES(Test::ISessionManager)
PS_REGISTER_PSTYPES(Test::IDateSource)


Где-то необходимо еще определить константы, по которым заказчик и сервер будут понимать с какой реализацией интерфейса они обязаны трудиться. Это дозволено сделать прямо в коде в месте вызова в виде «магических цифр», а дозволено перенести такие константы отдельно, тяготясь сделать код больше самостоятельным от «магических чисел».

Константы классов-реализаций

// class_ids.h
namespace Test
{
  namespace ClassId
  {
    enum
    {
      SessionManager = Remote::Crc32("Test.SessionManager"),
      DateSource = Remote::Crc32("Test.DateSource")
    };
  }
}


Сами реализации интерфейсов ISessionManager и IDateSource приводить не буду, рутина; дозволено посмотреть в прилагаемых начальных кодах примеров.

Сервер

#include <iostream>
#include "session_manager.h"
#include "date_source.h"
#include "ifaces_ps.h"
#include "class_ids.h"
#include "xml/pack.h"
#include "http/server_creator.h"
int main()
{
  try
  {
    auto Srv = Remote::Http::CreateServer
      <
        Remote::Pkg::Xml::InputPack,
        Remote::ClassInfo<Test::ISessionManager, Test::SessionManager, Test::ClassId::SessionManager>,
        Remote::ClassInfo<Test::IDateSource, Test::DateSource, Test::ClassId::DateSource>
      >("127.0.0.1", 5555, 2);
    std::cin.get();
  }
  catch (std::exception const &e)
  {
    std::cerr << e.what() << std::endl;
  }
  return 0;
}

Заказчик

#include <iostream>
#include "ifaces_ps.h"
#include "class_ids.h"
#include "xml/pack.h"
#include "http/http_remoting.h"
#include "class_factory.h"
int main()
{
  try
  {
    auto Remoting = std::make_shared<Remote::Http::Remoting>("127.0.0.1", 5555);
    auto Factory = Remote::ClassFactory::Create
          <
            Remote::Pkg::Xml::OutputPack,
            Test::ISessionManager,
            Test::IDateSource
          >(Remoting);
    auto Mgr = Factory->Create<Test::ISessionManager>(Test::ClassId::SessionManager);
    auto SessionId = Mgr->OpenSession(UserName, Password);
    // TODO:
    Mgr->CloseSession(SessionId);
    auto Date = Factory->Create<Test::IDateSource>(Test::ClassId::DateSource);
    std::uint16_t Year = 0, Month = 0, Day = 0, Hour = 0, Min = 0, Sec = 0;
    Date->GetDate(&Year, &Month, &Day, &Hour, &Min, &Sec);
    auto const *DateAsString = Date->GetDateAsString();;
    // TODO:
  }
  catch (std::exception const &e)
  {
    std::cerr << e.what() << std::endl;
  }
  return 0;
}


Собираем и пробуем…

Заработало! (Снимок рабочего стола с сервером и заказчиком)

Применение


В качестве промежуточного вывода приведу малое изложение шагов по применению.

Каждый код предлагаемой библиотеки поставляется в виде включаемых файлов (за исключением примеров). Для включения в план не нужно собирать всевозможные библиотеки и позже с ними изготавливать линковку. Необходимо только добавить путь к включаемым файлам библиотеки, в которых предоставлено все для создания Proxy/Stub’ов и свой http-сервер на основе libevent. Для сборки плана с учетом предлагаемой библиотеки нужно присутствие libevent. Если же пользователь реализует свой транспорт, то такое условие снимается.

При создании заказчик-серверного приложения на основе предлагаемой библиотеки необходимо исполнить несколько шагов.

Всеобщие действия:

  • Определить интерфейсы
  • Описать Proxy/Stub’ы
  • При желании сделать файл с константами реализаций


На стороне сервера:

  • Реализовать определенные интерфейсы
  • Сделать сервер с привязкой к транспорту
  • При создании сервера указать всю информацию о (де)сериализаторе и передать список с информацией о поддерживаемых сервером объектах


На стороне заказчика:

  • Сделать транспорт
  • Сделать фабрику классов, передав ей сделанный транспорт и список поддерживаемых интерфейсов
  • Создавать (запрашивать с сервера) с поддержкой фабрики объекты за определенными ранее идентификаторами классов реализаций


Все исключения, выброшенные сервером в их способах, на клиентскую сторону приходят в виде Remote::RemotingException, тот, что порожден от std::exception. Это исключение дозволено понимать как «что-то случилось в способе объекта на стороне сервера» и его повод описана в его сообщении. Все же остальные исключения говорят о системных ошибках иошибках соединения.

Так же напомню, что сервер доступен пока существует его объект. Объект сервера не приостанавливает его создающий поток и следственно позже создания серверного объекта, поток, его сделавший, допустимо необходимо остановить иными средствами. Для своей работы серверный объект создает свои потоки обработки запросов.

При написании классов-реализаций интерфейсов стоит помнить, что вызов способа может протекать из различных потоков если сервер сделан не с исключительным потоком. число потоков указывается при создании сервера параметром threadCount. Следственно каждая синхронизация внутри классов-реализаций лежит на стороне их разработчика.

Заказчик же полагает применение в однопоточном режиме. Что в то же время не мешает создавать несколько заказчиков, расположенных в различных потоках. Условием является обращение к способам сделанного объекта через его интерфейс в рамках потока, в котором был сделан его прокси объект.

Ограничения


Межпроцессное взаимодействие и взаимодействие между различными рабочими станциями в сети неизменно больше сковано нежели взаимодействие частей системы в рамках одного процесса. Переход через рубеж процесса если говорить о программировании либо переход границы государства неизменно связаны с какой-то скованностью нежели находясь внутри той либо другой системы. Так если я решу открыть свой бизнес и обнаружу стартовый капитал для покупки двадцати килограмм iPhone’ов одной из последних моделей в США, то безусловно в рамках транспорта (авиаперевозчика) у меня не должно появиться задач с ввозом их в Россию и даже не придется оплачивать добавочный багаж. Все в разрешенных рамках. А вот таможенная служба столь грубые порывы крепко притупит безоговорочным желанием с законодательным подкреплением собрать таможенные сборы, так как не уговорить ее работников, что 20 кг iPhone я везу для личного применения и стоят они все совместно не больше разрешенной беспошлинной суммы :) Так и в рамках описываемой библиотеки есть ограничения.

Если же встроенный транспорт готов передать всякие данные с какими-то несущественными ограничениями по сопоставлению с (де)сериализатором, то (де)сериализатор как раз и является той самой таможней, которая беспошлинно разрешает применять всякое число параметров в способах интерфейсов, ограниченное только вероятностями компилятора и эталоном C 11. А на типы параметров введен таможенный сбор. Дозволено передавать в качестве параметров и возвращаемых значений только интегральные типы и типы с плавающей точкой. Параметры могут быть переданы как по значению, так и по ссылке и указателю и при необходимости с добавлением квалификатора const. Это «даром», т.е. встроено в предлагаемую реализацию, а если есть желание передавать, скажем, stl контейнеры либо иные типы, то придется внести «пошлину» в виде реализации собственного (де)сериализатора либо модификации присутствующего.

Так же значения, полученные по указателю либо ссылке не обязаны применяться за пределами способа в/из тот, что они переданы/получены; их значение должно быть скопировано. Мониторинг изменений значений по ссылке либо указателю между процессами дозволено было реализовать, но он был бы безосновательно дорог в вычислительных источниках. В то же время это не исключает того, что переданные параметры по ссылке либо указателю не могут быть изменены в способе, в тот, что они переданы. Их дозволено изменять так же как и в рамках процесса, измененные параметры в месте их приобретения будут отправлены вызывающей стороне. Тут полная сходство с работой в рамках одного процесса.

Поводы отказа от перегрузки способов интерфейсов были приведены при изложении реализации Proxy/Stub.

Исходники, сборка, тестирование


Библиотека для реализации заказчик-серверного взаимодействия, построенная поверх HTTP-протокола, спрятанного под libevent доступна для скачивания в виде zip-архива с сервера, на котором дозволено ее и протестировать. Поставляется она в виде включаемых файлов (помимо примеров).

При тестировании я пользовался компилятором gcc/g 4.8.1. С огромный долей вероятности все будет собираться и на больше ранних версиях, скажем 4.7.2 и может быть на 4.7; не проверял. На еще больше ранних версиях вероятность сборки близка к нулю. Так же не проверял, но gcc 4.6.x еще слабо поддерживают либо совсем не поддерживают некоторые вещи, используемые в реализации. Пробовал собирать на clang 3.5. Появились небольшие задачи при расчете CRC32 в момент компиляции от длинных строк, а длинной строкой данный компилятор посчитал «Remote.IClassFactory» и манипуляции с -ftemplate-depth и -fconstexpt-depth флагами не дали итога. Безусловно, эту задачу дозволено решить извернувшись с длинами идентификаторов либо, скажем, расчет их по частям строки и xor’ом полученных итогов, но пока от такого решения отказался в силу отсутствия нужды в нем и поддержке clang. При решении же задачи с длиной идентификатора, огромнее задач с clang 3.5 не найдено. Пробовать собирать с поддержкой MS Visual Studio 2013 не стал. Ранее пробовал собрать с поддержкой «студии 2013» свой план [1], и был поражен, что constexpr она не поддерживает. Убрав каждый с ним связанный код и подставив предварительно вычисленные идентификаторы она в одном из ключевых мест выдала сообщение о внутренней ошибке компилятора, позже чего все эксперименты с ней я решил отложить как минимум до выхода новой версии.

Как libevent, так и предлагаемая библиотека являются кроссплатформенными. При тестировании сборка была осуществлена на Ubuntu 12.10 32bit и Ubuntu 12.04 64bit. Под Windows с поддержкой MinGW пока не пробовал собирать. Допустимо все соберется с первого раза либо возникнут небольшие тонкости. Верю испробовать в скором грядущем.

Совместно с библиотекой поставляются примеры. Их дозволено испытать на локальной машине либо поменяв в заказчиках строку с адресом «127.0.0.1» на адрес сервера «t-boss.ru», на котором размещен тестовый экземпляр серверной части примеров, протестировать примеры заказчиков так сказать «в реальных условиях».

Завершение


Удалось ли достичь мне заявленного в наименовании минимализма в применении полученного продукта судить Вам. У всякого данный идеал может быть различным. Мне же удалось покрыть свое желание минимализировать указываемую информацию при изложении Proxy/Stub’ов для интерфейсов до того яруса, когда не нужно ничего указывать помимо имени способа, а так же мне удалось отказаться от необходимости наличия информации о транспорте, сериализации и тех же Proxy/Stub’ах в месте применения в клиентском коде объектов с заданным интерфейсом и реализацией логики на сервере.

Огромную поддержка в этом мне оказал эталон C 11. По сопоставлению с C 03 он дает огромнее вероятностей: decltype, auto, образцы с переменным числом параметров, лямбда функции, мудрые указатели, многопоточность и многое другое. Что разрешает при реализации кроссплатформенного кода отказаться от множества собственных оберток над системными функциями. Отказаться от разработки собственных разумных указателей и в большей степени иметь вероятность применения RAII [8], так как ранее одного std::auto_ptr было неудовлетворительно.

decltype дал вероятность получать типы переменных (в данном случае указателя на способ интерфейса). auto дает вероятность отвлекаться от указания какого-то типа и трудиться с полученным значением, базируясь на предположениях о том, что он должен содержать те либо иные способы и при этом не указывать очевидно данный тип и не протягивать информацию о нем в место применения.

С возникновением образцов с переменным числом параметров дозволено отказаться от ранее используемых и больше массивных списков типов в жанре Александреску. Избавиться от гор «копипастного кода», тот, что Зачастую возникал при разработке библиотек всеобщего применения с наличием образцов.

Библиотека работы с типами дает вероятность сократить код, избавившись от написания собственных пророческой типа std::remove_pointer, std::is_const и так дальше, а некоторые вещи, нынче реализованные в этой библиотеке ранее дозволено было сделать только компиляторозависимыми.

Достигнув же с поддержкой этих вероятностей некоторого для себя итога в минимализме и удобном, по моему суждению, взаимодействии заказчика с сервером, реализация же может показаться не столь минимальной либо примитивный, а может и не показаться…

Неизменно при разработке библиотек всеобщего применения усердствовал придерживаться мысли, что код, тот, что использует библиотеку как дозволено поменьше должен на ней зацикливаться и ей служить. Библиотека должна служить коду ее использующему. А вот реализация самой библиотеки может быть всякий трудности, насколько хватит квалификации ее авторов.

Убеждение может показаться спорным. Но приведу любимую мною параллель. Во многих планах дозволено обнаружить применение boost. Да, это неоднозначно воспринимаемый продукт IT-миром разработчиков C . Кто-то ее чурается, а кто-то обеими руками за ее применение. В целом же дозволено сказать, что применение boost в большинстве своем понимается позитивно, так как она освобождает от множества нюансов, скрывая их в своей реализации, подчас прибегаю как к зависимостям от компилятора, зависимостям операционно

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB