Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / Win32
Article

A first look at the Windows Web Services API

Rate me:
Please Sign up or sign in to vote.
4.96/5 (34 votes)
27 Jul 2009CPOL8 min read 187.5K   72   41
The article shows how to interop between a WCF service and a WWS client, and also how to rewrite the WCF service in WWS retaining compatibility with existing WCF clients.

Image 1

Introduction

The Windows Web Services API is a native implementation of SOAP and can be used to interop transparently with existing WCF services and clients, in addition to offering the ability to completely achieve a client-server implementation in pure native code. I have been longing to play with it ever since I heard Nikola Dudar talk about it at the MVP Summit earlier this year. It’s natively included with Windows 7, but can also be installed and used from older OSes such as XP, Vista, 2003 and 2008. You can write pure native clients using WWS that can connect to an existing managed WCF service, and also write a WWS native service that can be consumed by a WCF client. It’s so compatible that you can replace either a WCF client or a WCF service with a WWS equivalent without the other party being aware of it. In this article, I'll talk about a simple WCF service and its WCF client, and then show how to use WWS to write a native client that can consume the WCF service. I'll then show how the WCF service itself can be replaced transparently with an equivalent WWS service, and how both the WCF and WWS clients can connect to this WWS service without any changes in code.

Note : The examples were written on a 64 bit Windows 7 RC machine running VS 2010 beta 1.

The example WCF service

The first thing to do is to create a very simple WCF service. For our example I'll use a string reversing service that exposes a single method that accepts a string and returns the reversed string. Here’s the service interface :

C#
[ServiceContract]
interface IStringService
{
    [OperationContract]
    string Reverse(string s);
}

class MyStringService : IStringService
{
    public string Reverse(string s)
    {
        return new string(s.Reverse().ToArray());
    }
}

Here’s the code that shows how the service is created and run.

C#
WSHttpBinding binding = new WSHttpBinding();
binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
binding.Security.Mode = SecurityMode.None;

Uri baseAddress = new Uri("http://localhost:8000/StringService");

using (ServiceHost serviceHost = 
  new ServiceHost(typeof(MyStringService), baseAddress))
{
    // Check to see if it already has a ServiceMetadataBehavior
    ServiceMetadataBehavior smb = 
      serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
    if (smb == null)
        smb = new ServiceMetadataBehavior();

    smb.HttpGetEnabled = true;
    smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy12;
    serviceHost.Description.Behaviors.Add(smb);

    // Add MEX endpoint
    serviceHost.AddServiceEndpoint(
      ServiceMetadataBehavior.MexContractName,
      MetadataExchangeBindings.CreateMexHttpBinding(),
      "mex"
    );

    serviceHost.AddServiceEndpoint(
      typeof(IStringService), binding, baseAddress);
    serviceHost.Open();

    Console.WriteLine("The service is running. Press any key to stop.");
    Console.ReadKey(); 

I wanted to avoid creating a config file and so everything's done in code, including adding the MEX endpoint. There's not much to explain there, I've used a standard WSHttpBinding and have disabled security (to keep the example simple).

Note : WWS supports several security modes compatible with WCF - so this is not an issue, but the default WCF security mode (Message) is not supported! So make sure that you don't trip on that one.

Simple WCF client in C#

Here's a simple C# console client that uses WCF to connect to the above WCF service and invoke the Reverse method.

C#
[ServiceContract]
interface IStringService
{
    [OperationContract]
    string Reverse(string s);
}

class Program
{
    static void Main(string[] args)
    {
        WSHttpBinding binding = new WSHttpBinding();
        binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
        binding.Security.Mode = SecurityMode.None;

        Uri baseAddress = new Uri("http://localhost:8000/StringService");
        EndpointAddress address = new EndpointAddress(baseAddress);
        ChannelFactory<IStringService> channelFactory =
          new ChannelFactory<IStringService>(binding, address);
        IStringService channel = channelFactory.CreateChannel();

        Console.WriteLine(channel.Reverse("This is a test string."));
        

        ((IChannel)channel).Close();
        Console.ReadKey();
    }
}

It doesn't do much but I wanted to show it here first because later on I'll show how this same client can consume the WWS service that'll replace the above WCF service. At this point we have a WCF service and a WCF client that consumes it. Next, let's write a simple WWS client (in native code) that'll connect to this WCF service.

Writing the native WWS client

Prior to creating the project, you need to have the Windows 7 RC SDK installed and configured. (Note : On older OSes, you'll also need to download and install the WWS RC API)

You'll also need to appropriately point your C++ include and library search directories. In VS 2010 this is done on a per-project level. For information on how to do this in VS 2010, see this blog entry I wrote : Setting VC++ directories in VS 2010.

Now there are two steps to do before we start writing the WWS native client :

Use svcutil to generate WSDL from the WCF service

Run this command : svcutil /t:metadata http://localhost:8000/StringService

You will get the following files generated for you :

  1. schemas.microsoft.com.2003.10.Serialization.xsd
  2. tempuri.org.wsdl
  3. tempuri.org.xsd

Here's a snip from the wsdl file that shows our service description :

XML
<wsdl:message name="IStringService_Reverse_InputMessage">
  <wsdl:part name="parameters" element="tns:Reverse" />
</wsdl:message>
<wsdl:message name="IStringService_Reverse_OutputMessage">
  <wsdl:part name="parameters" element="tns:ReverseResponse" />
</wsdl:message>
<wsdl:portType name="IStringService">
  <wsdl:operation name="Reverse">
    <wsdl:input wsaw:Action="http://tempuri.org/IStringService/Reverse" 
      message="tns:IStringService_Reverse_InputMessage" />
    <wsdl:output 
      wsaw:Action="http://tempuri.org/IStringService/ReverseResponse" 
      message="tns:IStringService_Reverse_OutputMessage" />
  </wsdl:operation>
</wsdl:portType>
<wsdl:binding name="WSHttpBinding_IStringService" 
  type="tns:IStringService">
  <wsp:PolicyReference URI="#WSHttpBinding_IStringService_policy" />
  <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="Reverse">
    <soap12:operation 
      soapAction="http://tempuri.org/IStringService/Reverse" 
        style="document" />
    <wsdl:input>
      <soap12:body use="literal" />
    </wsdl:input>
    <wsdl:output>
      <soap12:body use="literal" />
    </wsdl:output>
  </wsdl:operation>
</wsdl:binding>
<wsdl:service name="MyStringService">
  <wsdl:port name="WSHttpBinding_IStringService" 
    binding="tns:WSHttpBinding_IStringService">
    <soap12:address location="http://localhost:8000/StringService" />
    <wsa10:EndpointReference>
      <wsa10:Address>http://localhost:8000/StringService</wsa10:Address>
    </wsa10:EndpointReference>
  </wsdl:port>
</wsdl:service>

Next step is to generate the proxy files.

Use the Windows WebServices compiler tool (wsutil) to generate proxies (c/h files).

Run this command : wsutil *.xsd *.wsdl

You'll see a c/h file pair generated for each file that's passed to wsutil - and since we have 3 files, it'll give us 6 files back. Once the files are generated, open the native C++ console project you created and add all the generated c/h files (and remember to disable precompiled headers for the C files). For error free compilation, put your

#include
-s in this order :

C++
#include "stdafx.h"
. . .
#include "WebServices.h"
#include "schemas.microsoft.com.2003.10.Serialization.xsd.h"
#include "tempuri.org.xsd.h"
#include "tempuri.org.wsdl.h"

And remember to include WebServices.lib in your additional linker modules. The first thing is to do now is to declare some variables and also specify the service URL :

C++
HRESULT hr = ERROR_SUCCESS;
WS_ERROR* error = NULL;
WS_HEAP* heap = NULL;
WS_SERVICE_PROXY* proxy = NULL;

WS_ENDPOINT_ADDRESS address = {};
WS_STRING url= WS_STRING_VALUE(L"http://localhost:8000/StringService");
address.url = url;

WS_STRING is a simple struct that has a WCHAR* (that will point to the string) and a ULONG that will represent the length. I’ve used the WS_STRING_VALUE macro to initialize the string there.

The WWS APIs provide rich error info through the WS_ERROR structure and so we create a WS_ERROR struct using the WsCreateError API call (using default arguments).

C++
hr = WsCreateError(NULL,  0,  &error);
if (FAILED(hr))
{
  //...
}

We also need to create a WS_HEAP object which represents an opaque heap structure (error handling not shown) :

C++
hr = WsCreateHeap(2048, 512, NULL, 0, &heap, error); 

The next step is to create the service proxy :

C++
WS_HTTP_BINDING_TEMPLATE templ = {};
hr = WSHttpBinding_IStringService_CreateServiceProxy(&templ, NULL, 0, &proxy, error);

WSHttpBinding_IStringService_CreateServiceProxy is a proxy function that was generated by wsutil. Internally it calls the WWS API WsCreateServiceProxyFromTemplate but it saves us the hassle of correctly and properly filling up the various arguments. Here's the generated code :

C++
HRESULT WSHttpBinding_IStringService_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),
        &tempuri_org_wsdl.policies.WSHttpBinding_IStringService,
        sizeof(tempuri_org_wsdl.policies.WSHttpBinding_IStringService),
        _serviceProxy,
        error);
}

As you can see, the proxy function saved us the hassle of specifying all those properties. Next, we open the service proxy (connects us to the service endpoint) :

C++
hr = WsOpenServiceProxy(proxy, &address, NULL, error);

At this point we are ready to make calls into the service for which we again use the proxy functions that are generated by wsutil.

C++
WCHAR* result;	

hr = WSHttpBinding_IStringService_Reverse(
        proxy, L"Nishant Sivakumar", &result,
        heap, NULL, 0, NULL, error);

if (FAILED(hr))
{
  // ...
}

wprintf(L"%s\n", result);

WSHttpBinding_IStringService_Reverse is generated for us and is a pretty simple function to use, and it internally wraps the call to the WsCall WWS API function, including correctly wrapping up all the arguments and the return value. I am not showing the actual proxy function here but it'll be in tempuri.org.wsdl.c.

Well, that’s pretty much it. Once you are done, just call all the close/free APIs :

C++
if (proxy)
{
  WsCloseServiceProxy(proxy, NULL, NULL);
  WsFreeServiceProxy(proxy);
}

if (heap)
{
  WsFreeHeap(heap);
}

if (error)
{
  WsFreeError(error);
}

I was absolutely thrilled when I ran the console app for the first time and it successfully connected to the WCF service. The native client is about twice as long (number of lines of code) as the equivalent managed WCF client but that’s a small price to pay for the ability to consume a WCF service in pure native code. In the next section I will write about how the WCF service itself can be replaced with an identical WWS service (both WCF and WWS clients will continue to work the same).

Rewriting the WCF service in WWS

Once we rewrite the service using WWS, existing clients (whether WWS, WCF, or other) would continue to behave the same. I am going to use the same c/h files that wsutil generated from the wsdl file. There is a function signature generated for us to match the service contract methods. In our case there’s just one - WSHttpBinding_IStringService_ReverseCallback.
C++
typedef HRESULT (CALLBACK* WSHttpBinding_IStringService_ReverseCallback) (
    __in const WS_OPERATION_CONTEXT* _context,
    __in_opt __nullterminated WCHAR* s, 
    __out_opt __deref __nullterminated WCHAR** ReverseResult, 
    __in_opt const WS_ASYNC_CONTEXT* _asyncContext,
    __in_opt WS_ERROR* _error);

So the first thing is to add a method that matches this signature, and this will reverse a string just like the WCF service (except we write it in C or C++).

C++
HRESULT CALLBACK Reverse(
	__in const WS_OPERATION_CONTEXT* context,
	__in WCHAR* s,
	__out  WCHAR** reverse,
	__in_opt const WS_ASYNC_CONTEXT* asyncContext,
	__in_opt WS_ERROR* error)
{
	WS_HEAP* heap = NULL;

	HRESULT hr = WsGetOperationContextProperty(
		context,
		WS_OPERATION_CONTEXT_PROPERTY_HEAP,
		&heap,
		sizeof(heap),
		error);

	if (FAILED(hr))
	{
		return hr;
	}

	hr = WsAlloc(
           heap,
           sizeof(WCHAR) * (wcslen(s) + 1),
           (void**)reverse,
           error);

	if (FAILED(hr))
	{
		return hr;
	}

	wcscpy(*reverse, s);
	wcsrev(*reverse);

	return ERROR_SUCCESS;
}

I first use WsGetOperationContextProperty to get the heap and then use WsAlloc to allocate memory for the reversed string on this heap. We do not ever allocate memory using standard memory allocation mechanisms unless it’s memory we will have the option to delete/free when we are done using it. Instead we use the WWS heap which frees us from worrying about memory leaks - the memory will be released when the heap is reset or freed.

Now let’s get to creating the service. I will not show the error handling code (to save space) but every

HRESULT
return value must be checked for success before proceeding further. The first thing is to create the error and heap objects just as we did when writing the WWS client.

C++
WS_ERROR* error = NULL;
HRESULT hr = WsCreateError( NULL, 0, &error);
if (FAILED(hr))
{
 // ...
}

WS_HEAP* heap = NULL;
hr = WsCreateHeap( 100000, 0, NULL, 0, &heap, error);
if (FAILED(hr))
{
 // ...
}

The next step is to create a service endpoint.

C++
WSHttpBinding_IStringServiceFunctionTable functions = { Reverse };

WS_STRING url = WS_STRING_VALUE(L"http://localhost:8000/StringService");

WS_HTTP_BINDING_TEMPLATE templateValue = {};

WS_SERVICE_ENDPOINT* serviceEndpoint;
hr = WSHttpBinding_IStringService_CreateServiceEndpoint(&templateValue,
    &url, &functions, NULL, NULL, 0,
    heap, &serviceEndpoint, error);
if (FAILED(hr))
{
  // ...
}

Notice how we use the generated WSHttpBinding_IStringServiceFunctionTable to specify the list of functions (just one in our case). Now I use the proxy WSHttpBinding_IStringService_CreateServiceEndpoint to create the end point (and it internally calls WsCreateServiceEndpointFromTemplate). I have used default values for other arguments, but there is a lot of custom configuration that can be done. We’ll now create the service host:

C++
WS_SERVICE_HOST* host;
const WS_SERVICE_ENDPOINT* serviceEndpoints[1];
serviceEndpoints[0]= serviceEndpoint;
hr = WsCreateServiceHost( serviceEndpoints, 1,
    NULL, 0,  &host, error);
if (FAILED(hr))
{
}

We only have one endpoint, but the service host can host multiple endpoints. While I have called

WsCreateServiceHost 
with default arguments (basically passing NULL) it’s possible to set various service properties at this point. The last step is to open the service host.

C++
hr = WsOpenServiceHost(host, NULL, error);

The above code will open the service and start listening on each of the endpoints (just one in our example). For the example console app we'll use a _getch() so the app won’t exit.

C++
wprintf(L"Press any key to stop the service...\n");
_getch();

Once the app’s done, close and free the service host :

C++
WsCloseServiceHost(host, NULL, error);
WsFreeServiceHost(host);

And also free the heap/error objects :

C++
if (heap)
{
  WsFreeHeap(heap);
}

if (error)
{
  WsFreeError(error);
}

Run the service, and then run the WWS client which will be able connect to it and invoke the reverse function successfully. You can also run the simple WCF C# client and it’ll connect to this and execute the string reversal method. So now we can connect either of the two clients to either of the two services - sweet!

Conclusion

I do agree that all these proxies, having to create/free structures, handling HRESULTs etc. may seem a tad foreign if you are coming from a pure C# or VB.NET world. But if you are not put off by C++, and keeping your service or client code native is important to you, then WWS sure seems to be a great way to do it.

History

  • Article 1st draft : 7/25/2009
  • Article published : 7/27/2009

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
GeneralRe: Two questions Pin
tmaroff5-Aug-09 3:30
tmaroff5-Aug-09 3:30 
GeneralRe: Two questions Pin
rockonedge1-Dec-09 22:00
rockonedge1-Dec-09 22:00 
GeneralVery good intro! Pin
Ernest Laurentin31-Jul-09 5:27
Ernest Laurentin31-Jul-09 5:27 
GeneralRe: Very good intro! Pin
Nish Nishant31-Jul-09 14:15
sitebuilderNish Nishant31-Jul-09 14:15 
GeneralRuntime support Pin
sreeju.koothaduth29-Jul-09 20:41
sreeju.koothaduth29-Jul-09 20:41 
GeneralRe: Runtime support Pin
Nish Nishant30-Jul-09 6:07
sitebuilderNish Nishant30-Jul-09 6:07 
GeneralTimeout Pin
ShinjiR29-Jul-09 6:02
ShinjiR29-Jul-09 6:02 
GeneralRe: Timeout Pin
Nish Nishant29-Jul-09 11:54
sitebuilderNish Nishant29-Jul-09 11:54 
ShinjiR wrote:
I have followed the steps but I'm facing an error: "The timeout for all the call expired, the call has been abandoned as a result." error code = 0x803d0006


What's your OS? And can you show me the point in your code where it fails? Is it when you call WsOpenServiceProxy?


GeneralRe: Timeout Pin
ShinjiR4-Aug-09 11:07
ShinjiR4-Aug-09 11:07 
GeneralRe: Timeout Pin
Nish Nishant5-Aug-09 2:06
sitebuilderNish Nishant5-Aug-09 2:06 
GeneralOT - WCF and DIME Pin
Marc Clifton28-Jul-09 10:42
mvaMarc Clifton28-Jul-09 10:42 
GeneralRe: OT - WCF and DIME Pin
Nish Nishant28-Jul-09 11:52
sitebuilderNish Nishant28-Jul-09 11:52 
GeneralRe: OT - WCF and DIME Pin
Pete O'Hanlon28-Jul-09 21:41
subeditorPete O'Hanlon28-Jul-09 21:41 
GeneralFantastic stuff Mr S. Pin
Pete O'Hanlon28-Jul-09 9:23
subeditorPete O'Hanlon28-Jul-09 9:23 
GeneralRe: Fantastic stuff Mr S. Pin
Nish Nishant28-Jul-09 11:52
sitebuilderNish Nishant28-Jul-09 11:52 
GeneralA very nice introduction Pin
Nemanja Trifunovic28-Jul-09 3:15
Nemanja Trifunovic28-Jul-09 3:15 
GeneralRe: A very nice introduction Pin
Nish Nishant28-Jul-09 11:53
sitebuilderNish Nishant28-Jul-09 11:53 
GeneralGreat stuff Nish Pin
Sacha Barber27-Jul-09 23:56
Sacha Barber27-Jul-09 23:56 
GeneralRe: Great stuff Nish Pin
Nish Nishant28-Jul-09 2:22
sitebuilderNish Nishant28-Jul-09 2:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.