Another day, and another detour from WSDAPI 101. Today's article comes courtesy of WSDAPI's attachment interfaces, which are often the subject of questions I receive from fellow developers. In specific, I'm talking about:
The problem goes like this: you'd like to code a client that sends a message to a service, and would like to send an attachment in that service. You've got a WSDL and generated code, but you find that if you call into your proxy method, it never completes. Or if you call into IWSDOutboundAttachment::Write, it never returns.
What's the deal? Is this a bug?It turns out that no, this isn't a bug--it has to do with the guarantees we make in the API about when certain methods will return. If you're using the synchronous proxy methods (e.g., SendFirmwareToWebcam(firmware *) from the Webcam example in WSDAPI 101), we guarantee that the method won't return until the entire message is sent and any response is received. Of course, this assumes these methods all complete successfully.
Another guarantee we make is that IWSDOutboundAttachment::Write won't return until the message has been started and your data has been successfully sent to the HTTP layer for transmission.
As you can see, these guarantees make it impossible to call your synchronous service method and IWSDOutboundAttachment::Write from the same thread. Curses!
How do I fix this?There are two solutions--both are equally acceptable, so you should pick the one that fits best inside your particular application. If you need to read from inbound attachments coming back from the service, use the first pattern; this is discussed in greater detail at the end of this article.
In case it isn't obvious from the two earlier paragraphs, you always need to explicitly call IWSDOutboundAttachment::Close when you're done, or WSDAPI will keep the stream open and you'll never finish your message.
One last point about IWSDOutboundAttachment::WriteA final important point about Write is that WSDAPI will unblock the attachment streams in the order in which they appear in the message, and will wait for each to be closed before proceeding with the next. That means that if you have two streams, you have to call Write (and Write...and Write) on the first and Close it before any pending Write calls will unblock on the second. Again, the multithreaded blocking logic helps you out: if you've got N attachments, it's safe to fire off N threads and have each immediately call into Write, and let WSDAPI unblock them in order.
Wait, the top of this article mentions IWSDInboundAttachment::Read--what gives?Oh right, I almost forgot about ::Read(). If you're implementing a service method (e.g., you're building a webcam simulator and have implemented the SendFirmwareToWebcam() method) you can safely call Read on the attachment streams while inside your service method, as long as you read them in order, and as long as you read each to completion before proceeding with the next.
And in case you're curious, you don't have to call Close on your inbound attachment if Read returned S_FALSE. This is in the MSDN notes for Close.
On the client side, things get a little dicey because the attachment streams have to exist after the message completes, but attachments are reference-counted objects. The rules are a little complicated so this isn't strictly necessary in all cases, but here's a pattern for a robust client-side call to a method that has inbound attachments sent from the service, and consumed by the client.
This pattern will ensure that your client attachment implementations don't AV or leak memory when reading from attachments sent in the response message.
Update (2007/01/27): Fixed the example so it actually makes sense.