пятница, 9 октября 2009 г.

[comp.prog.flame] Более конструктивные мысли после двух суток выкуривания gSOAP

Данную заметку можно рассматривать как логическое продолжение предыдущей. Лыжи таки поехали. Местами я был идиотом, местами пришлось отлаживать gSOAP 2.7.14 для того, чтобы самому найти уже известный баг (который нашли за неделю до меня). Но в итоге все заработало.

Довольно своеобразная штука этот gSOAP. С одной стороны, очень удобно: берешь WSDL, натравливаешь на него wsdl2h, затем soapcpp2 и вуаля – в твоем распоряжении набор классов для обращения к удаленной стороне (ну или для реализации собственного сервиса).

Но с другой стороны… Видно, что gSOAP вырос из C-шной реализации. И уши этого наследия торчат из всех щелей. Взять, например, управление памятью. gSOAP хранит указатели на все динамически созданные в процессе десериализации SOAP-запроса объекты. И, если пользователь не взял их под собственную ответственность, автоматически уничтожает их. Но все это хорошо только для ответов сервера (если говорить о клиенте). А ведь запрос к серверу так же нужно подготовить. И вот тут-то начинается форменное корявство. Например, gSOAP генерирует структуры типа вот такой:

class SOAP_CMAC PPGw__Sms : public PPGw__Request
{
public:
   int dataCodingScheme;
   std::string *fromNumber;
   xsd__base64Binary *header;
   PPGw__PremiumInfo *premiumInfo;
   std::string *protocolIdentifier;
   std::string *reportLevel;
   std::string *toNumber;
   LONG64 validityPeriod;
public:
   ...
            PPGw__Sms() :
         dataCodingScheme(0),
         fromNumber(NULL),
         header(NULL),
         premiumInfo(NULL),
         protocolIdentifier(NULL),
         reportLevel(NULL),
         toNumber(NULL),
         validityPeriod(0) { this->soap_default(NULL); }
   virtual ~PPGw__Sms() { }
};

Можно видеть, что конструктор инициализирует элементы нулями, а деструктор ничего не делает. В это и весь фокус. Либо атрибуту PPGw__Sms назначается значение, время жизни которого определяет программист. Либо же ему назначается значение, время жизни которого будет определять gSOAP. В первом случае (когда мы отвечаем за объекты) код подготовки запроса будет иметь вид:

void
do_send_sms( PPGwBindingProxy & proxy )
   {
      std::cout << "...sending sms..." << std::endl;

      std::string baID = "***";
      std::string text_sms_selector = "TextSms";
      std::string text_of_message = "Just a Test!";
      std::string to_number = "+420***";
      std::string msg_id = "c0465d63-38f0-4f31-93b3-0dc80a063baf";

      PPGw__TextSms text_sms;
      text_sms.text = &text_of_message;
      text_sms.dataCodingScheme = 0;
      text_sms.toNumber = &to_number;
      text_sms.baID = &baID;
      text_sms.msgID = &msg_id;

      PPGw__MessageContainer mc;
      mc.selector = &text_sms_selector;
      mc.textSms = &text_sms;

      PPGw__send request;
      request.mc = &mc;

      PPGw__sendResponse response;

      int error_code = proxy.send( &request, &response );
      if( error_code )
      ...

А во втором случае (чтобы память почистил за нас gSOAP), вот такой:

void
do_send_sms( PPGwBindingProxy & proxy )
   {
      std::cout << "...sending sms..." << std::endl;

      PPGw__TextSms * text_sms = soap_new_PPGw__TextSms( &proxy, -1 );
      (text_sms->text = soap_new_std__string( &proxy, -1 ))->assign( "Just a Test!" );
      text_sms->dataCodingScheme = 0;
      (text_sms->toNumber = soap_new_std__string( &proxy, -1 ))->assign( "+420***" );
      (text_sms->baID = soap_new_std__string( &proxy, -1 ))->assign( "***" );
      (text_sms->msgID = soap_new_std__string( &proxy, -1 ))->assign(
            "c0465d63-38f0-4f31-93b3-0dc80a063baf" );

      PPGw__MessageContainer * mc = soap_new_PPGw__MessageContainer( &proxy, -1 );
      (mc->selector = soap_new_std__string( &proxy, -1 ))->assign( "TextSMS" );
      mc->textSms = text_sms;

      PPGw__send * request = soap_new_PPGw__send( &proxy, -1 );
      request->mc = mc;

      PPGw__sendResponse response;

      int error_code = proxy.send( request, &response );
      if( error_code )

      ...

Как по мне, так и первый, так и второй варианты – это страх и ужас, особенно второй.

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

А кроссплатформенных альтернатив gSOAP-у под C++, в общем-то, и нет. Была какая-то реализация SOAP-а от Apache (Apache Axis C++) – но там не видно обновлений с 2005-го года. Еще у разработчиков библиотеки POCO есть какой-то собственный POCO Remoting. Но он сильно дороже gSOAP-а, не умеет (как я понимаю) транслировать WSDL в C++, да и вообще его способности поддерживать именно SOAP слегка под вопросом (поскольку там для SOAP-а есть только некий SoapLite).

В связи с этим появляется соблазн взять и склепать собственный wsdl2cpp, который бы из WSDL генерировал заточенные под POCO или Qt нормальные C++ные классы. И продавать бы такой инструмент эдак по $200 за версию + upgrade на следующую версию за $75. Ей Богу, была бы такая альтернатива gSOAP-у – выбрал бы сейчас ее не задумываясь.

Отправить комментарий