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

Funny code

Надо придумать специальное название для таких вот смешных кусков кода: Control.ModifierKeys == Keys.Control.

ASP.NET MVC 1.0 released

Тихонько вышел ASP.NET MVC 1.0. Download ASP.NET MVC 1.0

WWS - How to read SOAP Fault

Сервера веб служб возвращают ошибки в элементе SOAP Fault. Здесь я объясню, как прочитать эту информацию.

Как узнать, что произошел сбой? Проверить код возврата на равенство WS_E_ENDPOINT_FAULT_RECEIVED. Дальше начинается магия.

WS_FAULT *pFault;
hr = WsGetFaultErrorProperty(pError, WS_FAULT_ERROR_PROPERTY_FAULT, &pFault, sizeof(pFault));

Структура определена следующим образом:

//  Faults structure
//
//   A Fault is a value carried in the body of a message which conveys a
//  processing failure.  Faults are modeled using the WS_FAULT structure.
//  See Faults for more information.
//
struct _WS_FAULT {
    WS_FAULT_CODE* code;
    WS_FAULT_REASON* reasons;
    ULONG reasonCount;
    WS_STRING actor;
    WS_STRING node;
    WS_XML_BUFFER* detail;
};

Наибольший интерес вызывает информация, заложенная в элементе <detail/>. WCF определяет специальные сущности с названием Fault Contract. Мы хотим их получить в удобном виде. Текущая версия WWS не генерирует код для Fault Contract-ов. Можно прочитать голый XML или вручную написать эти структуры вместе с метаданными, которые их описывают.

WS_XML_WRITER *pWriter;
hr = WsCreateWriter(NULL, 0, &pWriter, pError);

WS_XML_WRITER_TEXT_ENCODING textEncoding;
textEncoding.charSet = WS_CHARSET_UTF8;
textEncoding.encoding.encodingType = WS_XML_WRITER_ENCODING_TYPE_TEXT;

LPVOID pBuf;
ULONG nBufSize;
hr = WsWriteXmlBufferToBytes(pWriter, pFault->detail, &textEncoding.encoding, NULL, 0, _pWsHeap, &pBuf, &nBufSize, _pWsError);

WsFreeWriter(pWriter);

Этот код запишет XML в буффер pBuf (там будет строка в кодировке UTF-8). Кстати, метод WsWriteXmlBufferToBytes хочет в 3-м параметре __in_opt const WS_XML_WRITER_ENCODING* encoding, а мы ему передаем указатель на член структуры WS_XML_WRITER_TEXT_ENCODING. Учитывая значение WS_XML_WRITER_ENCODING_TYPE_TEXT в поле encodingType, WWS предполагает, что дальше в памяти содержится WS_CHARSET. В WWS многие методы работают таким вот хитрым образом - полагаются на расположение вложенных структур в оперативной памяти.

Десериализацию структуры я опишу позже. Может быть.

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-контракты, а как прочитать обобщенную информацию об ошибке - непонятно.

Framework Design Guidelines

Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition) В сети уже с месяц как появился скан книжки Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition). Скачать можно, например, на torrents.ru.

Copyright 2007-2011 Chabster