Last year I described the general structure of WSDAPI's WSDXML sections and provided some hints for creating and consuming these structures. I've received a few requests since then to provide more concrete examples of how to build these structures in code.
Before we get started, I do recommend you re-read my original post on WSDXML. The relationship between WSDXML_ELEMENT, WSDXML_TYPE, and WSDXML_NODE is an important part of understanding how everything fits together.
Simple example: element with text
The first example will demonstrate how to build a single element with text, but without any attributes or children. If we imagine the "ex" prefix is bound to the http://www.example.org namespace, here's the element we will want to build:
<ex:TestElement>text</ex:TestElement>
Creating this structure is straightforward. It involves two steps:
- Extract an XML name ("ex:TestElement") from the XML context
- Build a WSDXML_ELEMENT structure from that XML name
Here's what it looks like in code.
// All error handling is omitted from this example
HRESULT hr = S_OK;
IWSDXMLContext *pContext = NULL;
WSDXML_NAME *pName = NULL;
WSDXML_ELEMENT *pElement = NULL;
hr = WSDXMLCreateContext( &pContext );
hr = pContext->AddNamespace(
L"http://www.example.org",
L"ex",
NULL );
hr = pContext->AddNameToNamespace(
L"http://www.example.org",
L"TestElement",
&pName );
hr = WSDXMLBuildAnyForSingleElement(
pName, // pName is copied here, and must be freed separately
L"text",
&pElement );
// pElement is now complete, and contains: <ex:TestElement>text</ex:TestElement>
// cleanup:
// WSDFreeLinkedMemory( pElement );
// WSDFreeLinkedMemory( pName );
// pContext->Release();
Note that you can omit the "text" parameter to WSDXMLBuildAnyForSingleElement to generate empty elements. Supplying NULL here would result in <ex:TestElement />
Advanced example: chaining elements
We'll build on the last example to show how you can create chains of structures. Here's the element we want to build this time:
<ex:ParentElement>
<ex:ChildElement>text</ex:ChildElement>
<ex:AnotherChildElement />
</ex:ParentElement>
In this case, the steps go like this:
- Build ParentElement, ChildElement, and AnotherChildElement separately using the instructions above.
- Use WSDXMLAddChild to link ParentElement to ChildElement
- Use WSDXMLAddChild or WSDXMLAddSibling to add in AnotherChildElement. Either is acceptable.
Important: see section below on cleaning up WSDXML structures. Also note that you only need to call IWSDXMLContext::AddNamespace once for a given namespace--it is not necessary to call it for every name you add.
Here's some example code:
// Use steps above to create pParentElement, pChildElement,
// and pAnotherChildElement
hr = WSDXMLAddChild( pParentElement, pChildElement );
hr = WSDXMLAddSibling( pChildElement, pAnotherChildElement );
// It is also acceptable to call
// WSDXMLAddChild( pParentElement, pAnotherChildElement);
// cleanup:
// WSDFreeLinkedMemory( pParentElement );
// Use WSDFreeLinkedMemory to destroy WSDXML_NAME objects
// extracted from IWSDXMLContext
// DO NOT FREE pChildElement or pAnotherChildElement;
// they are attached to pParentElement
Destroying WSDXML structures
At first glance, it may seem complex to destroy these structures, but they're designed to be simple. You only need to follow a few simple rules to write robust and leak-free WSDXML code:
-
All WSDXML functions use linked memory. Always use WSDFreeLinkedMemory to destroy individual structures or whole trees.
-
WSDXMLBuildAnyForSingleElement copies the "name" parameter and links a copy into the new tree.
-
WSDXMLAddChild and WSDXMLAddSibling link together all structures in the tree.
-
Only free the topmost WSDXML_ELEMENT structure in an entire tree.
-
When passing WSDXML structures as outparams from services, be sure to link the entire WSDXML tree into the outparameter.
OASIS has just announced that version 1.1 of DPWS, WS-Discovery, and SOAP-over-UDP are now officially standards!
The links to the RDDL and spec pages currently point to the Committee Specification 1 (CS1) revisions of the specs, but OASIS will update these links to the fully standardized versions of the documents soon.
This is a big milestone in the progress of these specifications. My thanks go to the other members of the WS-DD Technical Committee, who all showed great dedication to this technology, and many of whom made substantial commitments of time and energy in producing high-quality specifications.
The SOAP Message Transmission Optimization Mechanism is a building-block spec that DPWS profiles so that specifications built on top can use it in a consistent and device-friendly way. MTOM isn't used in any of DPWS's messages, but it is available to solution authors and is included in many specs built on DPWS.
In short, MTOM is a way to link binary MIME attachments into the SOAP envelope*. The simplest example is a print job: using MTOM, you can send a SOAP message that describes the print job, and attach the binary document as a MIME attachment inside the HTTP connection. MTOM lets you link the MIME and SOAP together. This seems like an obvious way to package data, but let's imagine what would happen without that binding: the sender would have to encode the binary document into some form (likely base64) and then inline it into the SOAP message. There are a lot of disadvantages in this approach, most notably that the receiver can't really process the SOAP until it consumes the entire binary document, which could be very large. This would be a huge problem for devices, which are often memory-limited and which rely on streaming large documents as they cannot buffer them in entirety.
That last sentence hints at a neat fringe benefit of MTOM: it allows the sender and receiver to establish a binary stream with a SOAP message header. This is a really convenient way to open a stream like this, since the stream can coexist on the port used to send SOAP traffic, and there's a natural association between the strongly-typed SOAP contents and the more flexible stream data. Conceptually, MTOM doesn't distinguish between well-bounded documents and open-ended streams, although solutions are often written with either one of these in mind.
A good example of the stream pattern is the DPWS-based Windows SideShow Bitmap driver, which uses the SendSideShowFrameStream operation to open a stream connection, which is linked inside the FrameStream element. The SOAP message contains some useful parameters about the stream, and the stream contains bitmaps.
There are, naturally, disadvantages and limitations to this approach. The stream itself is unidirectional (although you may be able to set up simultaneous request/response streams), and even though a SOAP message can contain multiple MTOM sections, only one stream can be active at a time. Every stream must be prefaced with a SOAP message, and the data inside is completely untyped. But even with these limitations, using MTOM to set up streams is incredibly handy in DPWS applications.
To use MTOM in your application, simply include the xs:base64Binary schema type in an element. This type will become a pointer to the MIME section containing your binary document or stream data. WSDAPI automatically interprets this type and provides IWSDAttachment pointers for easy access to the binary data.
* Note: MTOM is actually a bit more complex than this, and is organized into a generic optimization mechanism and a MIME serialization. But in the context of DPWS, it really is just a way to link MIME sections into SOAP.
It's been a while since I last provided an update on the standardization of DPWS, WS-Discovery, and SOAP-over-UDP. I'm pleased to say that the standardization committee has produced Committee Specification versions of these three specs--that doesn't mean that they're standards, but the committee has finished with these versions and has sent them to the greater OASIS community for an approval vote. The TC has a more detailed announcement.
Even though these aren't approved standards, we can still compare the technical details of DPWS v1.0 (the version originally supported in Vista) to the latest revision of DPWS v1.1 to see what has changed.
-
Namspaces: First and foremost, DPWS v1.1 (and WS-Discovery v1.1) have new namespaces. This means that the two protocols are not inherently compatible--instead, individual implementers may choose whether they will support v1.0, v1.1, or both. In my opinion, the lack of backward compatibility built into the specification is actually a good thing: it keeps the specs isolated and individually very simple, and does not burden future specs written on DPWS v1.1 with a requirement to support the pre-standard versions of the specs.
-
References: The v1.1 revisions now reference the latest available standards, particularly WS-Policy v1.5 and WS-Addressing v1.0. Note that references to WS-Transfer, WS-MetadataExchange, and WS-Eventing have not yet been updated as those specifications are still being standardized in the
W3C WS-RA WG.
-
Security: DPWS v1.1 features a heavily reorganized security section which is simpler, easier to understand from an implementation perspective, and better organized. I'm personally very pleased with this change, as it makes DPWS security even more accessible.
-
Numerous smaller changes: I can't really do justice to the many other meaningful changes made to improved the quality of the specifications. Across all three specs, we resolved 141 issues ranging from a few editorial updates to dramatic improvements in specific parts of the text.
I also want to call out one big change to WS-Discovery:
-
WS-Discovery Managed Mode: WS-Discovery v1.1 now clearly separates two functional modes: ad-hoc, where discovery occurs among peers; and managed, where discovery occurs by means of a discovery proxy. WS-Discovery v1.0 hinted at how this would work but v1.1 is much clearer about how clients and services can contact a proxy.
Now, there are many changes I'm lumping into that category of "numerous smaller changes" which could each be broken out into their own articles--but the items I've listed above are the ones I'm most excited about. In the future I expect to dive into these (particularly security) in much greater detail.
Stay tuned for additional updates as the OASIS member vote on these specs gets underway.
Traditional big web services, like a billing system or a service running in the cloud, nearly always have fixed addresses--often HTTP URLs. Smaller web services, such as those on a PC or a device, often have logical identifiers ("urn:uuid:c3de740f-9227-4706-b3ee-9494243c040d") that let you recognize the service, but which don't actually tell you anything about how to contact the service.
One of the great uses of WS-Discovery is transforming logical addresses into physical addresses (e.g., http://my_pc/service/) so you can actually contact the service. A service uses the xAddrs field in WS-Discovery messages to express its transport addresses.
But it turns out that these xAddrs are optional. There are actually a lot of really good cases why you would want to omit them--if they're very large, or if you don't want to generate different Hello messages for every IP address in the system--but the specification doesn't clearly explain when they're often omitted in the real world, and what happens when they are.
So let's go through the four messages a service can send.
- Hello messages sometimes contain xAddrs. On any given subnet it's likely to encounter Hello messages both with and without xAddrs. Those without xAddrs will cause a Resolve from any clients interested in the service. Hello is the only multicast message that can include xAddrs, so it's a little unique--and it differs from ProbeMatches and ResolveMatches in that sense.
- Bye messages never contain xAddrs. The specification does not allow it (note that this has been changed in WS-Discovery v1.1).
- ResolveMatches messages always contain xAddrs. It's technically possible for the xAddrs element to be empty but this is unusual and probably not a good way to achieve interoperability. A service must realistically always produce xAddrs in its ResolveMatches messages.
- ProbeMatches messages usually contain xAddrs. It's very unusual for services to omit xAddrs in their ProbeMatches messages because it takes no more effort than including them in ResolveMatches--both messages are unicast responses. Including xAddrs in ProbeMatches lets everyone avoid a Resolve/ResolveMatches exchange.
One interesting consequence of this is that the example sequence on page 11 of WS-Discovery (Probe -> ProbeMatches -> Resolve -> Resolve Matches -> success!) almost never happens. Services usually include xAddrs in their ProbeMatches messages in the first place.
DPWS defines two different levels of metadata: the Device delivers metadata about the entire device, and about the relationship between the Device and its Hosted Services; and each Hosted Service delivers metadata about itself (and optionally about its relationship with the Device).
Of these two, the device-level metadata is the most important. Since WS-Discovery only advertises the Device, the device-level metadata is the only way to discover the services. Without device-level metadata, you really can't do anything with the device. WSDAPI and Windows relies heavily on device-level metadata to populate the properties visible inside the Network explorer, and this metadata is retrieved any time an application attempts to use a Hosted Service on the device.
The service-level metadata, on the other hand, isn't absolutely critical for Windows to connect to the device. It is possible that someone will build an application that uses service-level metadata (the IWSDServiceProxy::BeginGetMetadata/EndGetMetadata methods provide access) but for now it's not used by many of the applications that ship inside Windows.
The roles of Types in WS-Discovery messages is really clear for generic, non-DPWS services. Types are qualified names expressed in the <d:Types> element of WS-Discovery messages, and these names identify a set of operations; the service advertised in the WS-Discovery message supports those operations. In many cases a language like WSDL is used to express the connection between the name and the operations, but it's not strictly necessary.
When DPWS is involved, things are a little more complicated. Remember that DPWS uses a Host service that is responsible for advertising all of the Hosted services. The Hosted services are the ones that actually perform the device's functionality (e.g., WSD Toaster operations) so their types are advertised in the metadata that the Host service provides (e.g., the Host service would describe a Hosted service, and would advertise a type of t:WSDToasterService).
Now in most cases, the Host service does not actually implement any application-specific operations on its own; it only provides the metadata that allows a client to find the application-specific services. Devices built like this don't advertise t:WSDToasterService at the device level because it's inaccurate--a client cannot send WSD Toaster operations directly to the Host service and expect them to work. The Host service supports the WS-Transfer/Get request used to retrieve the device's metadata, that that operation is described by the dpws:Device type.
The difficulty is that clients cannot search for devices that support WSD Toaster services at the WS-Discovery layer. After all, why should a device connect to hundreds of devices that have nothing to do with WSD Toaster?
To fix this problem, some specifications add an empty device-layer type with no associated operations, but which identifies that one or more service-layer types of interest can be found on this device. So in this case, WSD Toaster would define a type t:WSDToasterDevice without any associated actions, but which is required on every device with a matching t:WSDToasterService service. Clients can then merely search for t:WSDToasterDevice in WS-Discovery and only retrieve metadata for devices that contain toaster services. t:WSDToasterDevice isn't useful on its own, but it does provide a hint that t:WSDToasterService is nearby.
DPWS v1.0 applications usually manually describe the association between their service-layer (t:WSDToasterService) and device-layer (t:WSDToasterDevice) types in normative text. It usually looks something like this: "A device that supports one or more WSD Toaster services MUST advertise the t:WSDToasterDevice type at the DPWS DEVICE." As a convenience, DPWS v1.1 Appendix C adds an optional WSDL attribute that can be used to express this association in a way that code generation tools can understand. An example is shown in that appendix.
Now, this article doesn't completely describe the possible variations of these types, and the benefits and drawbacks of any of the arrangements. Specification authors should be warned that this is a complex topic and shouldn't be taken lightly--at some point I may even put together a whole post on how to choose the layout. But the basics I've described here are applicable to many of the most common cases (including WS-Print and WS-Scan), and should provide some clarity to specification authors, application-layer implementers, and generic stack implementers alike.
Just under six months ago, DPWS, WS-Discovery and SOAP-over-UDP entered standardization in the OASIS WS-DD technical committee. An incredible amount of work and progress has been made during that time, and the committee has just published the second draft of the 1.1 versions of each specification.
As I mentioned in my earlier posts, this is a very important step for these specifications as the entire DPWS community comes together to collaborate on the specifications and refine and improve the contents. In fact, this technical committee has filed and handled nearly 150 different issues across all three specifications, and has improved the utility and broad applicability of the specs without changing the core fundamentals set out in the 1.0 versions.
Although the dates have slipped a little, it is expected that the specs will enter the 60-day OASIS public review period shortly, bringing us even closer to the date when DPWS will be a standard.
A lot of WSDAPI objects implement IUnknown, so the expose the Release() method. A few more also support Terminate(). What's the difference, and which should you call?
Ultimately, you should always refer to the documentation, as rules for specific objects may differ from the norm. But in most cases, the following guidelines apply:
- Terminate should always be called if the object was properly initialized, and should be called before calling Release. Terminate often does last-minute cleanup work that cannot occur inside Release, such as synchronizing threads, or cleaning other objects that may have references back to this object. Failing to call Terminate could result in access violations as other threads continue to run, or could result in reference loops and leaks.
- Release should always be called when you're discarding a reference to the object. Release should be called even if Terminate is also required.
Often, my cleanup code looks like this:
HRESULT hr = S_OK;
IWSDDeviceHost *pDeviceHost = NULL;
hr = WSDCreateDeviceHost( ..., &pDeviceHost );
// ...initialize and use pDeviceHost...
if( NULL != pDeviceHost )
{
// Always terminate pDeviceHost if it was successfully created/initialized
hr = pDeviceHost->Terminate();
// Optionally print an error if Terminate failed
// Always release pDeviceHost, even if Terminate failed
pDeviceHost->Release();
pDeviceHost = NULL;
}
It has never been particularly easy to choose a Web Services stack on which to build your application. Microsoft has produced many over the years (for example, WCF and WSDAPI) and other software vendors produce even more. The feature sets of these stacks often overlaps, but rarely are they all exactly the same--so making a decision about which stack to use can be complicated and time-consuming.
And things are about to get even more complicated--but for very good reason--with the introduction of the Windows Web Services API (WWSAPI). Even though it means one more stack to choose from, I'm actually very excited about WWSAPI, because it brings WCF-like functionality to native non-managed Windows applications. This fills a big gap in the development story for Web Services.
So soon there will be at least three different stacks you can use to build WS applications.
- Web Services on Devices API (WSDAPI) is the one-stop shop for building applications on DPWS, WSD, or WS-Discovery. If you use any of these three technologies, use WSDAPI. (And if you're lost in the acronyms, see this post.)
- Windows Communication Foundation (WCF) is the generic Web Services stack available in .NET. If you're not communicating with devices but are using managed code, use WCF.
- Windows Web Services API (WWSAPI) is the generic Web Services stack available on Windows. If you're not communicating with devices but are using native code, use WWSAPI.
Additionally, there are other technology-specific WS stacks available in Windows (such as WinRM for WS-Management) that I'm not listing here for brevity.
The short version is that if you're using devices, WSDAPI is probably the first place to look for Web Services connectivity. For more generic Web Services applications, WWSAPI is a great solution if you're writing native code.
More information on WWSAPI can be found on the blogs of two WWSAPI team members, Hao Xu and Nikola Dudar.
WS-Eventing is one of the building-block specifications that DPWS profiles and makes available for solution authors. Some applications require the device to send notification messages back to the client; e.g., a printer sends a message to the PC when it has finished printing a document. WS-Eventing doesn't describe the content in these notifications, but it does describe the messages that the client and device use to set up and manage the subscription for those events. The way this works is that the client subscribes to events; the device sends zero or more events; and some cleanup happens to tear the subscription down.
It's actually a lot more complicated at the protocol level, but the important point to remember is that a subscription ties a service (in this case, a service running on a device) to a client, and that subscription may have an expiration--either an absolute time, or a relative duration from the start of the subscription. These expirations are communicated through WS-Eventing, and are in either the duration or the datetime formats described by XML schema. An example SOAP Body for a Subscribe request with an 18-hour expiration could look something like this:
<wse:Subscribe>
<wse:Delivery>...</wse:Delivery>
<wse:Expires>PT18H</wse:Expires>
</wse:Subscribe>
The interesting part is that although the client can suggest an expiration, the service is ultimately free to ignore it and choose a different value. In fact, the service can ignore an absolute expiration and return a relative expiration. The client should always rely on whatever the service sent back--if the service sent back a different value, the client should use that.
This is really important for devices. A device should make some attempt to honor a client's request, but it may not support long-lived expirations or absolute datetimes. Many embedded devices do not have an absolute clock source, and even if they did, they may not know what time zone they're in. In these cases, the device can rely on relative expirations as an easy way to manage subscription requests from any client, regardless of the suggested expiration.
The Devices Profile for Web Services isn’t exactly a Profile in all senses of the word. Yes, it does select and constrain underlying specifications like SOAP and WS-Discovery, but it also does very un-profile things like define schema.
One of the things that it defines is Relationship Metadata, which gives devices a way to express how their many functions are organized.
First, a brief refresher on how a DPWS device is organized: every device has a Host service, which is responsible for WS-Discovery and serving up device-level metadata, and it has zero or more Hosted services, which do the things that you expect from a device: printing, scanner, toasting bread, etc. The Relationship metadata is used to describe these services.
The Relationship section is divided into two parts: zero or one Host sections, and zero or more Hosted sections. Each of these sections has exactly the same schema, even if it’s the Host section—because remember, the Host service is a service, too. Those of you who like to see it in XML will appreciate this:
<wsx:MetadataSection>
[<dpws:Host>…</dpws:Host>]?
[<dpws:Hosted>…</dpws:Hosted>]*
</wsx:MetadataSection
Here’s the interesting point: devices may omit the Host entry in the metadata. That doesn’t mean that the Host service doesn’t exist—clearly it does since it served up the metadata. It’s just that the device doesn’t have anything to say in its metadata that you didn’t already know when you discovered the device.
The same is not quite true for Hosted services—they really do need to be included, because if they’re not included in metadata, they’re not really part of the Device (they’re just other services hosted on the same piece of plastic).
Lastly, there’s a weird rule that the device must include at least one of Host or Hosted metadata. This is really only included because this entire metadata section is optional, so the statement is that if you include this metadata, you have to include something inside it.
Once that’s clear, the contents are fairly self-explanatory.
<wsa:EndpointReference>…</wsa:EndpointReference>
<dpws:Types>…</dpws:Types>
<dpws:ServiceId>…</dpws:ServiceId>
...
EndpointReference is this particular service’s endpoint: for a Host service, this is the address advertised at the discovery layer (“urn:uuid:…”); a Hosted service is recommended to use a physical address (“http://...”).
Types are regular service types: Host services have to include dpws:Device, Hosted services include whatever they want.
ServiceId is a little difficult to understand at first—why do we need another ID if we already have the Endpoint Reference? Consider the case of a printer with two print heads: one black/white laser head, and one color inkjet head (bear with me—it’s just an example). One way to organize this device is to expose these print heads as different services. If you’re using physical addressing for your services, your black/white service will have an ID of “http://192.168.0.1/PrintService0” and your color service will have an ID of “http://192.168.0.1/PrintService1”. Let’s say a user configures his PC to automatically select the black/white service; and then the printer get a new IP address and the Endpoint References change. How does the PC find the black/white service again? It can do this with Service IDs, which remain constant even though the Endpoint References may change when IPs change.
So that’s what relationship metadata is all about. It looks fairly intimidating (and the schema is complicated, as schema usually is) but the concepts are straightforward.
We've just published a whitepaper on DPWS, WS-Discovery, and SOAP-over-UDP over at MSDN. This is a great guide for those of you who want a good technical introduction to how all three specifications work.
Special thanks go to Ram Jeyaraman for doing all the difficult work in putting this document together!
UUIDs (related to GUIDs) are used frequently in computing applications. If you don't know these by name, you will probably recognize the 128-bit value in the format in which it is typically rendered:
7673868d-231e-490d-9c4f-19288e7e668d
This is just an example value--these are usually generated on demand to uniquely identify an object. In WS-Discovery and DPWS, UUIDs are recommended as a way to uniquely identify devices, services, clients, and even individual messages.
Although the process for generating UUIDs is well-understood, there is a lot of inconsistency in how they are renedered in Web Services messages. In most cases, these UUIDs are used as URIs, which means we have to supply a scheme (such as "urn:uuid:"--"http:" is another scheme) in addition to the URI content (the UUID).
It's common to see two URI schemes used to describe UUIDs: "uuid:" and "urn:uuid:" So the example UUID above would be either uuid:7673868d-231e-490d-9c4f-19288e7e668d or urn:uuid:7673868d-231e-490d-9c4f-19288e7e668d, depending on which scheme is used. Here's the difference between the two:
- urn:uuid: is described by RFC 4122 as the correct scheme for rendering a UUID. RFC 4122 is referenced in DPWS, and is a well-accepted way to generate UUIDs in Web Services messages.
- uuid: is common but non-standard. It's often used in specifications developed before RFC 4122 was available; both UPnP and WS-Discovery use uuid:, since both were published before RFC 4122. Unfortunately, uuid: does not conform to any standard.
There are cases where uuid: is explicitly required (UPnP is one of them) but in all other cases, you should consider whether urn:uuid: is a better alternative. In most Web Services applications, urn:uuid: is preferred--in fact, WSDAPI does additional validation on urn:uuid:-scheme URIs that it does not perform on uuid:-scheme URIs.
So if you're implementing DPWS on your own, or are generating UUIDs to pass into WSDAPI, please use the urn:uuid: form whenever possible.
Last week OASIS announced the formation of a technical committee to standardize DPWS, WS-Discovery, and SOAP-over-UDP. This is big news for all three specifications (and the people who implement and rely on them), as it gives us an opportunity to collaborate with other industry leaders in advancing and solifying these specs.
The announcement quotes Sriram Rajagopalan, the Director of Program Management for my group here at Microsoft. He puts it better than I can:
The formation of the WS-DD Technical Committee is an important milestone and builds upon mature WS-* base protocols by expanding the scope to include the wide variety of devices being used today in homes and enterprises. Defining protocols for discovering, securely consuming and exposing Web services in a lightweight footprint that suits these devices has the potential to greatly broaden the reach of Web services to meet customers' needs. Discovery of enterprise resources, whether devices or Web services, can help make large scale SOA implementations more robust and simpler to manage.
This is exciting stuff and is really important for these specifications.