One of the main reasons many developers flocked to the Microsoft .NET Framework, and Java to a lesser degree, was the fact that it made it much easier to write software for the Internet. Whether you were writing an HTTP client or server application, the .NET Framework had you covered with classes for making HTTP requests and processing XML easily. You could even generate SOAP clients from WSDL documents and implement SOAP servers with ASP.NET. As the standards around Web services matured, Microsoft developed the Windows Communications Foundation (WCF), also built on the .NET Framework, to make it easier to use the increasingly complex Web standards for handling different transports, such as TCP and UDP, and provide more versatile security options.
C++ developers, however, were left wondering whether it was even practical to use C++ to write Web applications. Microsoft had provided a couple of temporary solutions in the form of ATL classes and a COM-based toolkit, but in the end these couldn’t keep up with the progress that the managed SOAP stacks had made and thus were largely abandoned.
Despite the seemingly single-minded focus on the .NET Framework, developers at Microsoft haven’t forgotten about the C++ developer. In fact, many of them are still and will continue to be passionate C++ developers. As a result, solutions that help C++ developers are a key part of Microsoft’s strategy for the Windows platform. Of course, many of the APIs targeted at C++ developers also end up underpinning many of the managed frameworks.
Although there are too many to enumerate here, a few are worth mentioning. The Windows HTTP Services (WinHTTP) API provides a powerful and flexible solution for writing HTTP clients. You can read more about WinHTTP in my August 2008 column. The HTTP Server (HTTP.sys) API provides the foundations for building high-performance HTTP servers without relying on a full-fledged Web server like Internet Information Services (IIS). In fact, IIS itself is based on this same API. And of course the XmlLite API provides a small and fast XML parser for native code. You can read more about XmlLite in my April 2007 feature article.
Given all of this, it is still quite a daunting task to write a SOAP client or server. Although SOAP started off “simple” (that’s what the “S” stands for), it didn’t stay that way for long. WinHTTP, HTTP.sys and XmlLite may get you a long way by handling the HTTP transport and parsing the XML, but there is still a ton of code to write to handle the communications layer: things like formatting and interpreting SOAP headers, not to mention supporting other transports like TCP or UDP. Even if you could somehow manage all of this, you’re still left with parsing SOAP envelopes instead of being able to treat logical SOAP operations as function calls.
Well, these headaches are a thing of the past. With the introduction of the Windows Web Services (WWS) API, C++ developers no longer have to think of themselves as second-class citizens in the world of Web Services. WWS is designed from the ground up to be a completely native-code implementation of SOAP, including support for many of the WS-* protocols. WWS is, strictly speaking, exposed through a C API, making interoperability with other languages and runtimes very straightforward, but it is the C++ developer who will likely benefit the most. In fact, with a little help from C++, it can be a real joy to use—as you shall see in this article.
Architecture and Principles
WWS embodies everything that .NET Framework-based libraries are not. It is designed for native code. It is designed to introduce a minimal number of dependencies. It is designed to use as little memory as possible. And it is designed to be fast. Really fast. The team responsible for developing WWS runs performance tests on each new build, comparing it with WCF and RPC. RPC is used as a sort of baseline since nothing could be faster, but it does provide a reliable way of tracking speed regressions. It is, however, illuminating when you compare WCF and WWS.
Figure 1 shows a comparison of the working set for a client using WCF and WWS respectively. That’s a pretty dramatic margin, but perhaps not that surprising when you think that the .NET Framework is involved.
Figure 2, however, should be surprising if you, like many others, consider WCF to be state of the art. It shows the throughput in operations per second for a server using WCF and WWS respectively. WWS is more than twice as fast! Don’t get me wrong: There is nothing wrong with WCF or the .NET Framework, but when you need something small and fast, it’s hard to beat C++ and native code. But you know that already!
Figure 1
Comparing Client Working Set (Lower Is Better)
The WWS runtime is packaged in WebServices.dll, which is included with Windows 7 and Windows Server 2008 R2. It is also available as a system update for Windows XP and later. Functions exported from WebServices.dll represent the WWS API and you can gain access to them by linking to WebServices.lib and including the WebServices.h header file from the Windows SDK. So far, so good. But what does the API look like? Well, unlike COM-style APIs like that of XmlLite or Direct2D, this C API requires you to imagine a set of logical layers and objects that live behind the scenes and are just waiting to break out. Let’s first take a look at it in terms of layers.
Figure 3 illustrates the layers of functionality exposed by the WWS API, with each layer building upon the one beneath it. Each layer is represented by a number of functions and structures and provides a set of abstractions. As you can guess, the application can make use of any of the layers, but for the most part you will want to stick with the service model that provides the simplest programming model and hides many of the details for you. The transports layer is just a reminder that underneath it all there will be some network protocol. WWS will use WinHTTP, HTTP.sys, or Winsock, depending on the selected transport and whether it is used to implement a client or server.
Figure 2
Comparing Server Throughput (Higher Is Better)
Figure 3
Layered API
As its name suggests, the service model layer entirely abstracts the SOAP message exchanges and models the logical Web service operations as function calls. It does not, however, stand alone but relies on the use of a command-line tool called Wsutil.exe from the Windows SDK. Given a WSDL file, this tool will generate a header file as well as a C source file with most of the code necessary to both connect to a Web service of the given description and to implement such a Web service, taking care to ensure the channel binding is properly configured and messages are properly formatted. This is by far the simplest approach and provides a programming model that is very much like what you would expect from traditional RPC.
The channel layer, on the other hand, exposes the messages sent and received on a particular transport, but still optionally shields you from having to actually format the messages yourself. The benefit here is that you are shielded from the particular transport and encoding that is used. The channel layer is where you control binding information and where you can secure your communication whether for authentication or privacy.
The XML layer exposes you to the formatting of messages and serialization of data. You have full access to the content of messages but are shielded from the particular encoding whether you’re communicating with text, binary or MTOM. You might be surprised to hear that WWS has its own XML parser. Why doesn’t it just use XmlLite? Although XmlLite is certainly lightweight and very fast, it isn’t quite a perfect match for a number of reasons. The most obvious reason is that WWS needs to support different encodings while XmlLite supports only text. SOAP messages are also typically encoded using UTF-8, while XmlLite exposes all properties with Unicode strings and this introduces unnecessary cost when copying values. WWS also has very strict memory consumption goals (there is actually an API for this, as we’ll see later on) that cannot be met with XmlLite. In the end, the WWS team was able to implement a custom parser specifically for SOAP that is considerably faster than XmlLite. Keep in mind that the WWS parser is not meant to replace XmlLite. As a general purpose XML parser, it is hard to beat, but the WWS XML layer provides developers with very specific features aimed at efficiently serializing data into and out of SOAP message using the subset of XML required by SOAP.
Apart from the functions and data structures that logically belong to these three layers, the WWS API provides a number of facilities that are common to all layers, including error handling, asynchronous completion, cancellation, memory management and more. Because I have limited space and want to help you get started quickly, I’m going to limit the rest of this article to the use of the service model for building a Web service client and server. In a future article, I’ll dig more deeply into the other parts of WWS.
Getting Started
To get started, I’ll use the minimal Web service definition from
Figure 4. This WSDL document defines the types, messages, operations, endpoints and channel bindings of the service. The first thing to do is run it through Wsutil.exe as follows:
Wsutil.exe Calculator.wsdl
This will produce a header file called Calculator.wsdl.h and a C source file called Calculator.wsdl.c.
Figure 4
Web Service Definition
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="ttp://www.w3.org/2001/XMLSchema"
xmlns:tns="http://calculator.example.org/"
targetNamespace="http://calculator.example.org/">
<wsdl:types>
<xsd:schema elementFormDefault="qualified"
targetNamespace="http://calculator.example.org/">
<xsd:element name="AddRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="first" type="xsd:double" />
<xsd:element name="second" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="AddResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="result" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="AddRequestMessage">
<wsdl:part name="parameters" element="tns:AddRequest" />
</wsdl:message>
<wsdl:message name="AddResponseMessage">
<wsdl:part name="parameters" element="tns:AddResponse" />
</wsdl:message>
<wsdl:portType name="CalculatorPort">
<wsdl:operation name="Add">
<wsdl:input message="tns:AddRequestMessage" />
<wsdl:output message="tns:AddResponseMessage" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CalculatorBinding" type="tns:CalculatorPort">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Add">
<soap:operation soapAction=
"http://calculator.example.org/Add" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CalculatorService">
<wsdl:port name="CalculatorPort" binding="tns:CalculatorBinding">
<soap:address location="http://localhost:81/calculator"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Before we take a look at what was generated, we need to get some groundwork done, regardless of whether you’re implementing the client or the server. The first thing you’ll need is a way to express error information. The WWS API exposes rich error information both for its own functions as well as SOAP faults via an error object. Being a C API, this object is represented by an opaque handle and a set of functions. In this case, WS_ERROR* represents a handle to an error object and WsCreateError is the function that creates it. The object is freed by calling the WsFreeError function. The error object can store a number of strings with different levels of information about an error. To retrieve these, you need to first determine how many strings are present. This is done by calling the WsGetErrorProperty function, giving it the handle to the error object and specifying the WS_ERROR_PROPERTY_STRING_COUNT constant. Armed with this information, you call the WsGetErrorString function, giving it the handle to the error object as well as the zero-based index of the string to retrieve. You can also use the API functions to populate your own error objects. Naturally, a little C++ will go a long way to making this simpler and more reliable.
Figure 5 provides a simple error object wrapper. You can easily enumerate the strings in an error object, as shown in
Figure 6.
Figure 5
Error Object Wrapper
class WsError
{
WS_ERROR* m_h;
public:
WsError() m_h(0)
{}
~WsError()
{
if (0 != m_h)
}
HRESULT Create(const WS_ERROR_PROPERTY* properties,
ULONG propertyCount)
{
return WsCreateError(properties, propertyCount, &m_h);
}
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, void* buffer,
ULONG bufferSize)
{
return WsGetErrorProperty(m_h, id, buffer, bufferSize);
}
template <typename T>
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, out T* buffer)
{
return GetProperty(id, buffer, sizeof(T));
}
HRESULT GetString(ULONG index, WS_STRING* string)
{
return WsGetErrorString(m_h, index, string);
}
operator WS_ERROR*() const
{
return m_h;
}
};
Figure 6
Enumerate the Strings in an Error Object
WsError error;
HR(error.Create(0, 0));
// Something goes wrong . . .
ULONG stringCount = 0;
HR(error.GetProperty(WS_ERROR_PROPERTY_STRING_COUNT,&stringCount));
for (ULONG i = 0; i < stringCount; ++i)
{
WS_STRING string;
HR(error.GetString(i, &string));
wprintf(L"Error %d: %.*s\n", i, string.length, string.chars);
}
The next thing we’ll need is a heap object. The heap object provides precise control over memory allocation when producing or consuming messages and when needing to allocate various other API structures. It also simplifies the programming model since there is no need for you to know precisely how much memory is required for any particular function to succeed. Many older functions in the Windows SDK will, for example, either require you to guess how much storage is required or to first call a function in a particular way to determine how much memory you should allocate for the function to succeed. The use of the WWS heap object removes all this extra coding and provides a nice way to control the memory usage of the API. This also comes in handy from a security perspective where you may want to specify expected limits on how much the API may allocate. WS_HEAP* represents a handle to a heap object and WsCreateHeap is the function that creates it. The object is freed by calling the WsFreeHeap function. Once it’s created, you can allocate memory from the heap using the WsAlloc function, but for this article we’ll just pass the handle to the heap object to other API functions for them to use as necessary.
Figure 7 provides a simple heap object wrapper. Given this, you can create a heap object limited to 250 bytes as follows:
WsHeap heap;
HR(heap.Create(250, // max size
0, // trim size
0, // properties
0, // property count
error));
Notice how I’m passing the error object to the heap object’s Create method. Should anything go wrong while creating the heap object, I’ll be able to interrogate the error object to find out the cause.
Figure 7
Heap Object Wrapper
class WsHeap
{
WS_HEAP* m_h;
public:
WsHeap() : m_h(0)
{}
~WsHeap()
{
if (0 != m_h) WsFreeHeap(m_h);
}
HRESULT Create(SIZE_T maxSize, SIZE_T trimSize,
const WS_HEAP_PROPERTY* properties,
ULONG propertyCount,
in_opt WS_ERROR* error)
{
return WsCreateHeap(maxSize, trimSize, properties, propertyCount,
&m_h, error);
}
operator WS_HEAP*() const
{
return m_h;
}
};
The Client
The client-side of the service model centers on the service proxy object. The generated source code includes a function called CalculatorBinding_CreateServiceProxy. The name is derived from that of the endpoint or binding name defined in the WSDL document. This function creates a service proxy object and returns a WS_SERVICE_PROXY* representing an opaque handle to that object. The object is freed by calling the WsFreeServiceProxy function. Once created, your application can open the service endpoint using the WsOpenServiceProxy function and then make calls via the service proxy to the Web service. What exactly WsOpenServiceProxy does is dependent on the transport being used. You must also take care to close the service proxy prior to freeing the object using the WsCloseServiceProxy function. Naturally, all of this housekeeping can be nicely wrapped up in a simple class provided in
Figure 8. Given this, you can create and open the service proxy, as shown in
Figure 9.
Figure 8
Service Proxy Wrapper
class WsServiceProxy
{
WS_SERVICE_PROXY* m_h;
public:
WsServiceProxy() : m_h(0)
{}
~WsServiceProxy()
{
if (0 != m_h)
{
Close(0, 0); // error
WsFreeServiceProxy(m_h);
}
}
HRESULT Open(const WS_ENDPOINT_ADDRESS* address,
const WS_ASYNC_CONTEXT* asyncContext,
WS_ERROR* error)
{
return WsOpenServiceProxy(m_h, address, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext,
WS_ERROR* error)
{
return WsCloseServiceProxy(m_h, asyncContext, error);
}
WS_SERVICE_PROXY** operator&()
{
return &m_h;
}
operator WS_SERVICE_PROXY*() const
{
return m_h;
}
};
Figure 9
Create and Open Service Proxy
WsServiceProxy serviceProxy;
HR(CalculatorBinding_CreateServiceProxy(0, // template value
0, // properties
0, // property count
&serviceProxy,
error));
WS_ENDPOINT_ADDRESS address =
{
{
static_cast<ULONG>(wcslen(url)),
const_cast<PWSTR>(url)
}
};
HR(serviceProxy.Open(&address,
0, // async context
error));
The WS_ENDPOINT_ADDRESS structure is used to address the messages being sent. You will typically use this structure when programming at the channel layer. In this case, we populate only the URL portion and the service proxy takes care of the rest.
At this point, we can use another generated function namely CalculatorBinding_Add, which unsurprisingly represents the Web service’s Add operation. It really doesn’t get much easier than this:
const double first = 1.23;
const double second = 2.34;
double result = 0.0;
HR(CalculatorBinding_Add(serviceProxy,
first,
second,
&result,
heap,
0, // properties
0, // property count
0, // async context
error));
ASSERT(3.57 == result);
Once you are done interacting with the Web service you just need to close the service proxy’s communication channel:
HR(serviceProxy.Close(0, // async context
error));
The Server
While the client-side programming model centers on the service proxy, the server instead creates and manages a service host, which provides the necessary runtime to listen on the various endpoints based on the provided channel information. Again, because we’re using the service model, most of the details are abstracted away, and we’re left only to create the service endpoint and host. WWS will do the rest.
The first step is to create the service endpoint. The service model takes care of this in the form of another generated function, namely CalculatorBinding_CreateServiceEndpoint, as
Figure 10 shows. Creating the endpoint requires specifying the address on which the endpoint is going to listen. This is provided by the WS_STRING structure, which is a length-prefixed Unicode string and is not required to be null terminated. Because the endpoint is responsible for fulfilling requests, you need to provide a table of function pointers mapping to the operations exposed by the service. The generated CalculatorBindingFunctionTable structure is used for this purpose. Finally, the endpoint itself is represented by the WS_SERVICE_ENDPOINT structure and is allocated in the provided heap.
Figure 10
CalculatorBinding_CreateServiceEndpoint
class WsServiceHost
{
WS_SERVICE_HOST* m_h;
public:
WsServiceHost() : m_h(0)
{}
~WsServiceHost()
{
if (0 != m_h)
{
Close(0, 0);
WsFreeServiceHost(m_h);
}
}
HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints,
const USHORT endpointCount,
const WS_SERVICE_PROPERTY* properties,
ULONG propertyCount, WS_ERROR* error)
{
return WsCreateServiceHost(endpoints, endpointCount, properties,
propertyCount, &m_h, error);
}
HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsOpenServiceHost(m_h, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsCloseServiceHost(m_h, asyncContext, error);
}
operator WS_SERVICE_HOST*() const
{
return m_h;
}
};
WsServiceProxy serviceProxy;
const WS_STRING address =
{
static_cast<ULONG>(wcslen(url)),
const_cast<PWSTR>(url)
};
CalculatorBindingFunctionTable functions =
{
AddCallback
};
WS_SERVICE_ENDPOINT* endpoint = 0;
HR(CalculatorBinding_CreateServiceEndpoint(0, // template value
&address,
&functions,
0, // authz callback
0, // properties
0, // property count
heap,
&endpoint,
error));
The next step is to create the service host. WS_SERVICE_HOST* represents a handle to a service host object and WsCreateServiceHost is the function that creates it. The object is freed by calling the WsFreeServiceHost function. The WsCreateServiceHost function creates the service host object given a list of endpoints. At this point, you can start the listeners on all the endpoints using the WsOpenServiceHost function. Stop the communication using the WsCloseServiceHost function. As with the service proxy, be sure to close the service host prior to freeing it. Again, all of this housekeeping can be nicely wrapped up in a simple class provided in
Figure 11. Given this, you can start the Web service as follows:
const WS_SERVICE_ENDPOINT* endpoints[] = { endpoint };
WsServiceHost serviceHost;
HR(serviceHost.Create(endpoints,
_countof(endpoints),
0, // properties
0, // property count
error));
HR(serviceHost.Open(0, // async context
error));
In this case, there is only a single endpoint but you can see how easy it would be to add additional endpoints to the service host. When it is time to stop the service, you just need to close the service host’s communication channels.
HR(serviceHost.Close(0, // async context
error));
Actually implementing the Web service operation is about the easiest part:
HRESULT CALLBACK AddCallback(__in const WS_OPERATION_CONTEXT*,
__in double first,
__in double second,
__out double* result,
__in_opt const WS_ASYNC_CONTEXT* /*asyncContext*/,
__in_opt WS_ERROR* /*error*/)
{
*result = first + second;
return S_OK;
}
The signature for the AddCallback function is also provided by the generated source code, should you have any doubts about how to specify it.
And that’s all I have space for this month, but you should now have a good idea of the features and benefits that the Windows Web Services API has to offer. As you can see, C++ developers finally have a modern SOAP stack right out of the box. It offers the best possible performance and memory usage and is a pleasure to use with a little help from C++.
Figure 11
Service Host Wrapper
class WsServiceHost
{
WS_SERVICE_HOST* m_h;
public:
WsServiceHost() : m_h(0)
{}
~WsServiceHost()
{
if (0 != m_h)
{
Close(0, 0);
WsFreeServiceHost(m_h);
}
}
HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints,
const USHORT endpointCount,
const WS_SERVICE_PROPERTY* properties,
ULONG propertyCount, WS_ERROR* error)
{
return WsCreateServiceHost(endpoints, endpointCount, properties,
propertyCount, &m_h, error);
}
HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsOpenServiceHost(m_h, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsCloseServiceHost(m_h, asyncContext, error);
}
operator WS_SERVICE_HOST*() const
{
return m_h;
}
};