Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Beginning WWS

Не так давно появилась beta версия Windows Web Services для ОС семейства Windows XP и Vista. WWS должен войти в состав новой операционки Windows 7, но хитрые менеджеры поняли, что неплохо иметь этот функционал и на более ранних версиях ОС Windows. К тому же, уверен, WWS API будет готов гораздо раньше, чем Windows 7. Да, кстати, WWS API - это библиотека для создания веб служб на языках C/C++. Наконец то! Мне захотелось попробовать WWS API в деле, чем я и занялся на днях.

Некоторые подробности можно узнать на персональном блоге product manager'а WWS - Nikola Dudar. Там же есть ссылка на не очень полезное видео о WWS.

Для поиграться нужно скачать и поставить саму библиотеку WWS, а также Windows 7 SDK Beta, который содержит заголовочные файлы и утилиты, необходимые для программирования Windows Web Services.

WWS позволяет создавать как клиенты веб сервисов, так и сами сервисы. На первых порах я разбирался лишь с клиенткой частью, но механизм в целом одинаков. Оба сценария предполагают наличиеwsdl файла для генерации кода. Как не трудно догадаться, применен механизм WSDL First, что понятно - разобрать С++ код сложновато, а интроспекции нет.

Для примера я решил взять службу погоды - http://www.webservicex.com/globalweather.asmx. wsdl файл, как обычно, можно получить по следующему адресу: http://www.webservicex.com/globalweather.asmx?wsdl. Для генерации необходимых файлов необходимо выполнить аж одну команду:

"c:\Program Files\Microsoft SDKs\Windows\v7.0\Bin\Wsutil.exe" WeatherService.wsdl

В результате чего должны появиться файлы WeatherService.wsdl.h и WeatherService.wsdl.c. Их необходимо добавить в проект, после чего отключить использование Precompiled Header у WeatherService.wsdl.c. А в stdafx.h не помешает добавить следующее:

#include <WebServices.h>
#pragma comment(lib, "WebServices.lib")

Сгенерированные файлы содержат

  1. структуры данных, соответствующие схеме, заложенной в секции <wsdl:types>;
  2. метаданные, необходимые WWS для взаимодействия со службой;
  3. вспомогательные методы, упрощающие взаимодействие со службой.

Чтобы начать вызывать удаленные методы, необходимо создать канал. В Microsoft решили не отходить далеко от терминологии WCF и использовать известные всем понятия - channel, endpoint и т.д. В сгенерированном коде есть для этого специальный метод:

HRESULT GlobalWeatherSoap_CreateServiceProxy(
    __in_opt WS_HTTP_BINDING_TEMPLATE* templateValue,
    __in_ecount_opt(proxyPropertyCount) const WS_PROXY_PROPERTY* proxyProperties,
    __in const ULONG proxyPropertyCount,
    __deref_out_opt WS_SERVICE_PROXY** _serviceProxy,
    __in_opt WS_ERROR* error)
{
    return WsCreateServiceProxyFromTemplate(
        WS_CHANNEL_TYPE_REQUEST,
        proxyProperties,
        proxyPropertyCount,
        WS_HTTP_BINDING_TEMPLATE_TYPE,
        templateValue,
        templateValue == NULL ? 0 : sizeof(WS_HTTP_BINDING_TEMPLATE),
        &WeatherService_wsdl.policies.GlobalWeatherSoap,
        sizeof(WeatherService_wsdl.policies.GlobalWeatherSoap),
        _serviceProxy,
        error);
}

Как видно из кода, эта функция - просто обертка над WsCreateServiceProxyFromTemplate, которая и выполняет всю работу по созданию канала. Генератор кода сообразил, что используется WS HTTP привязка, и сгенерировал соответствующий код. WWS поддерживает много привязок, о чем свидетельствует следующее перечисление:

//  Policy Support enum
//
//   An enumeration of the different security binding combinations that
//  are supported in current product.
//
typedef enum
{
    WS_HTTP_BINDING_TEMPLATE_TYPE                                         = 0,
    WS_HTTP_SSL_BINDING_TEMPLATE_TYPE                                     = 1,
    WS_HTTP_HEADER_AUTH_BINDING_TEMPLATE_TYPE                             = 2,
    WS_HTTP_SSL_HEADER_AUTH_BINDING_TEMPLATE_TYPE                         = 3,
    WS_HTTP_SSL_USERNAME_BINDING_TEMPLATE_TYPE                            = 4,
    WS_HTTP_SSL_KERBEROS_APREQ_BINDING_TEMPLATE_TYPE                      = 5,
    WS_TCP_BINDING_TEMPLATE_TYPE                                          = 6,
    WS_TCP_SSPI_BINDING_TEMPLATE_TYPE                                     = 7,
    WS_TCP_SSPI_USERNAME_BINDING_TEMPLATE_TYPE                            = 8,
    WS_TCP_SSPI_KERBEROS_APREQ_BINDING_TEMPLATE_TYPE                      = 9,
    WS_HTTP_SSL_USERNAME_SECURITY_CONTEXT_BINDING_TEMPLATE_TYPE           = 10,
    WS_HTTP_SSL_KERBEROS_APREQ_SECURITY_CONTEXT_BINDING_TEMPLATE_TYPE     = 11,
    WS_TCP_SSPI_USERNAME_SECURITY_CONTEXT_BINDING_TEMPLATE_TYPE           = 12,
    WS_TCP_SSPI_KERBEROS_APREQ_SECURITY_CONTEXT_BINDING_TEMPLATE_TYPE     = 13,
} WS_BINDING_TEMPLATE_TYPE;

Кстати, нам понадобятся объекты WS_ERROR и WS_HEAP. WS_ERROR используется для чтения ошибок на клиенте и записи их на сервере. А WS_HEAP - для выделения памяти под различные объекты, которые возникают в процессе работы с каналом:

BOOL GlobalWeatherClient::OpenConnection() {
    _hr = WsCreateError(NULL, 0, &_pWsError);
    if (FAILED(_hr)) {
        return(FALSE);
    }

    _hr = WsCreateHeap(10000000, 0, NULL, 0, &_pWsHeap, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }

Далее необходимо заполнить структуру WS_HTTP_BINDING_TEMPLATE:

    WS_HTTP_BINDING_TEMPLATE templateValue = { };
    ULONG maxMessageSize = 2147483647;
    ULONG timeout = 1000 * 60 * 2;

    WS_CHANNEL_PROPERTY channelProperty[5];

    channelProperty[0].id = WS_CHANNEL_PROPERTY_MAX_BUFFERED_MESSAGE_SIZE;
    channelProperty[0].value = &maxMessageSize;
    channelProperty[0].valueSize = sizeof(maxMessageSize);

    channelProperty[1].id = WS_CHANNEL_PROPERTY_CONNECT_TIMEOUT;
    channelProperty[1].value = &timeout;
    channelProperty[1].valueSize = sizeof(timeout);

    channelProperty[2].id = WS_CHANNEL_PROPERTY_SEND_TIMEOUT;
    channelProperty[2].value = &timeout;
    channelProperty[2].valueSize = sizeof(timeout);

    channelProperty[3].id = WS_CHANNEL_PROPERTY_RECEIVE_RESPONSE_TIMEOUT;
    channelProperty[3].value = &timeout;
    channelProperty[3].valueSize = sizeof(timeout);

    channelProperty[4].id = WS_CHANNEL_PROPERTY_RECEIVE_TIMEOUT;
    channelProperty[4].value = &timeout;
    channelProperty[4].valueSize = sizeof(timeout);

    WS_CHANNEL_PROPERTIES channelProperties;
    channelProperties.properties = channelProperty;
    channelProperties.propertyCount = _countof(channelProperty);

    templateValue.channelProperties = channelProperties;

Из шаблона создать канал:

    _hr = GlobalWeatherSoap_CreateServiceProxy(&templateValue, NULL, 0, &_pWsServiceProxy, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }

В конце концов, открыть канал, задав адрес сервиса:

    WS_ENDPOINT_ADDRESS address = { };
    WS_STRING url = WS_STRING_VALUE(L"http://www.webservicex.com/globalweather.asmx");
    address.url = url;

    _hr = WsOpenServiceProxy(_pWsServiceProxy, &address, NULL, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }
    return(TRUE);
}

Для каждой операции генерируется помошничек:

// operation: GlobalWeatherSoap_GetWeather
HRESULT WINAPI GlobalWeatherSoap_GetWeather(
    __in WS_SERVICE_PROXY* _serviceProxy,
    __in __nullterminated WCHAR* CityName,
    __in __nullterminated WCHAR* CountryName,
    __out __deref __nullterminated WCHAR** GetWeatherResult,
    __in WS_HEAP* _heap,
    __in_ecount_opt(_callPropertyCount) const WS_CALL_PROPERTY* _callProperties,
    __in const ULONG _callPropertyCount,
    __in_opt const WS_ASYNC_CONTEXT* _asyncContext,
    __in_opt WS_ERROR* _error)
{
    void* _argList[3];
    _argList[0] = &CityName;
    _argList[1] = &CountryName;
    _argList[2] = &GetWeatherResult;
    return WsCall(_serviceProxy,
        (WS_OPERATION_DESCRIPTION*)&WeatherService_wsdlLocalDefinitions.contracts.GlobalWeatherSoap.GlobalWeatherSoap_GetWeather.GlobalWeatherSoap_GetWeather,
        (void **)&_argList,
        _heap,
        _callProperties,
        _callPropertyCount,
        _asyncContext,
        _error);
}

Вызвать операцию проще простого:

std::wstring GlobalWeatherClient::GetWeather(LPWSTR pszCity, LPWSTR pszCountry) {
    LPWSTR result;
    _hr = GlobalWeatherSoap_GetWeather(_pWsServiceProxy, pszCity, pszCountry, &result, _pWsHeap, NULL, 0, NULL, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        return(wstring());
    }
    return(result);
}

По завершению работы канал необходимо закрыть, а все остальные объекты освободить:

void GlobalWeatherClient::CloseConnection() {
    if (_pWsServiceProxy) {
        WsCloseServiceProxy(_pWsServiceProxy, NULL,  _pWsError);
        WsFreeServiceProxy(_pWsServiceProxy);
        _pWsServiceProxy = NULL;
    }
    if (_pWsHeap) {
        WsFreeHeap(_pWsHeap);
        _pWsHeap = NULL;
    }

    if (_pWsError) {
        WsFreeError(_pWsError);
        _pWsError = NULL;
    }
}

Довольно интересно автора решили проблему выделения и освобождения памяти с помощью концепции куча или просто HEAP. Память из кучи выделяется функцией WsAlloc, но функции для ее освобождения нет! Вместо этого куча просто очищается одним махом вызовом WsResetHeap. Куча, кстати, не обязательно одна. Это порождает интересные сценарии использования. Например, для метода с массивным объемом данных можно использовать отдельную кучу.

Генератор создает чистоплотный C-код, который каждый любитель ООП, уверен, захочет обернуть в удобный класс, как здесь:

#pragma once
#include "GlobalWeather/WeatherService.wsdl.h"

class GlobalWeatherClient
{

public:
    GlobalWeatherClient();
    ~GlobalWeatherClient();

public:
    BOOL OpenConnection();
    void CloseConnection();

public:
    wstring GetCitiesByCountry(LPWSTR pszCountry);
    wstring GetWeather(LPWSTR pszCity, LPWSTR pszCountry);

protected:
    void PrintError(HRESULT errorCode, WS_ERROR *error);

private:
    HRESULT _hr;
    WS_ERROR *_pWsError;
    WS_HEAP *_pWsHeap;
    WS_SERVICE_PROXY *_pWsServiceProxy;

};

#include "stdafx.h"
#include "GlobalWeatherClient.h"

GlobalWeatherClient::GlobalWeatherClient() {
    _hr = S_OK;
    _pWsError = NULL;
    _pWsHeap = NULL;
    _pWsServiceProxy = NULL;
}

GlobalWeatherClient::~GlobalWeatherClient() {
    CloseConnection();
}

BOOL GlobalWeatherClient::OpenConnection() {
    _hr = WsCreateError(NULL, 0, &_pWsError);
    if (FAILED(_hr)) {
        return(FALSE);
    }

    _hr = WsCreateHeap(10000000, 0, NULL, 0, &_pWsHeap, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }

    WS_HTTP_BINDING_TEMPLATE templateValue = { };
    ULONG maxMessageSize = 2147483647;
    ULONG timeout = 1000 * 60 * 2;

    WS_CHANNEL_PROPERTY channelProperty[5];

    channelProperty[0].id = WS_CHANNEL_PROPERTY_MAX_BUFFERED_MESSAGE_SIZE;
    channelProperty[0].value = &maxMessageSize;
    channelProperty[0].valueSize = sizeof(maxMessageSize);

    channelProperty[1].id = WS_CHANNEL_PROPERTY_CONNECT_TIMEOUT;
    channelProperty[1].value = &timeout;
    channelProperty[1].valueSize = sizeof(timeout);

    channelProperty[2].id = WS_CHANNEL_PROPERTY_SEND_TIMEOUT;
    channelProperty[2].value = &timeout;
    channelProperty[2].valueSize = sizeof(timeout);

    channelProperty[3].id = WS_CHANNEL_PROPERTY_RECEIVE_RESPONSE_TIMEOUT;
    channelProperty[3].value = &timeout;
    channelProperty[3].valueSize = sizeof(timeout);

    channelProperty[4].id = WS_CHANNEL_PROPERTY_RECEIVE_TIMEOUT;
    channelProperty[4].value = &timeout;
    channelProperty[4].valueSize = sizeof(timeout);

    WS_CHANNEL_PROPERTIES channelProperties;
    channelProperties.properties = channelProperty;
    channelProperties.propertyCount = _countof(channelProperty);

    templateValue.channelProperties = channelProperties;

    _hr = GlobalWeatherSoap_CreateServiceProxy(&templateValue, NULL, 0, &_pWsServiceProxy, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }

    WS_ENDPOINT_ADDRESS address = { };
    WS_STRING url = WS_STRING_VALUE(L"http://www.webservicex.com/globalweather.asmx");
    address.url = url;

    _hr = WsOpenServiceProxy(_pWsServiceProxy, &address, NULL, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        CloseConnection();
        return(FALSE);
    }
    return(TRUE);
}

void GlobalWeatherClient::CloseConnection() {
    if (_pWsServiceProxy) {
        WsCloseServiceProxy(_pWsServiceProxy, NULL,  _pWsError);
        WsFreeServiceProxy(_pWsServiceProxy);
        _pWsServiceProxy = NULL;
    }
    if (_pWsHeap) {
        WsFreeHeap(_pWsHeap);
        _pWsHeap = NULL;
    }

    if (_pWsError) {
        WsFreeError(_pWsError);
        _pWsError = NULL;
    }
}

wstring GlobalWeatherClient::GetCitiesByCountry(LPWSTR pszCountry) {
    LPWSTR result;
    _hr = GlobalWeatherSoap_GetCitiesByCountry(_pWsServiceProxy, pszCountry, &result, _pWsHeap, NULL, 0, NULL, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        return(wstring());
    }
    return(result);
}

std::wstring GlobalWeatherClient::GetWeather(LPWSTR pszCity, LPWSTR pszCountry) {
    LPWSTR result;
    _hr = GlobalWeatherSoap_GetWeather(_pWsServiceProxy, pszCity, pszCountry, &result, _pWsHeap, NULL, 0, NULL, _pWsError);
    if (FAILED(_hr)) {
        PrintError(_hr, _pWsError);
        return(wstring());
    }
    return(result);
}

void GlobalWeatherClient::PrintError(HRESULT errorCode, WS_ERROR *pError) {
    cout << "Failure errorCode=0x" << hex << errorCode << endl;

    HRESULT hr = S_OK;
    if (pError) {
        ULONG errorCount;
        hr = WsGetErrorProperty(pError, WS_ERROR_PROPERTY_STRING_COUNT, &errorCount, sizeof(errorCount));
        if (SUCCEEDED(hr)) {
              for (ULONG i = 0; i < errorCount; i++) {
                    WS_STRING errorString;
                    hr = WsGetErrorString(pError, i, &errorString);
                    if (SUCCEEDED(hr))
                        wcout << wstring(errorString.chars, errorString.chars + errorString.length) << endl;
              }
          }
     }
    if (FAILED(hr))
         cout << "Failed to get additional error information, hr=0x" << hex << hr << endl;
}

Кстати, примеры использования WWS можно найти в каталоге C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\Web\WWSAPI.

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

2 коммент.:

FX Poster комментирует...

У тебя WSS встречается в половине текста вместо WWS. Поправь. :) А за пост - спасибо!

Анонимный комментирует...

I have to thank you for the efforts you have put in penning this site.

I'm hoping to view the same high-grade blog posts from you later on as well. In truth, your creative writing abilities has encouraged me to get my very own site now ;)

My blog; online backup solution
My web-site :: cheap online backup

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

Copyright 2007-2011 Chabster