Funny code
Надо придумать специальное название для таких вот смешных кусков кода: Control.ModifierKeys == Keys.Control
.
Надо придумать специальное название для таких вот смешных кусков кода: Control.ModifierKeys == Keys.Control
.
Сервера веб служб возвращают ошибки в элементе 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 многие методы работают таким вот хитрым образом - полагаются на расположение вложенных структур в оперативной памяти.
Десериализацию структуры я опишу позже. Может быть.
Не так давно появилась 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")
Сгенерированные файлы содержат
<wsdl:types>
;Чтобы начать вызывать удаленные методы, необходимо создать канал. В 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: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition). Скачать можно, например, на torrents.ru.