Не так давно появилась 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>
;
- метаданные, необходимые WWS для взаимодействия со службой;
- вспомогательные методы, упрощающие взаимодействие со службой.
Чтобы начать вызывать удаленные методы, необходимо создать канал. В 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-контракты, а как прочитать обобщенную информацию об ошибке - непонятно.