Help! I can't write to my attachments!
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.
- Use asynchronous proxy calls. By default, WsdCodeGen will build async methods into an extended client-only interface off of your base interface. So in the webcam example in WSDAPI 101, the base synchronous call is IWebcam::SendFirmwareToWebcam(firmware*), but the async calls are IWebcamProxy::BeginSendFirmwareToWebcam(firmware*) and IWebcamProxy::EndSendFirmwareToWebcam(). The Begin... async call will return immediately and allow you to write to your attachment before calling IWSDOutboundAttachment::Close, and finally the End... async call, which will block until the response is retrieved.
- Spin off another thread. Another option here is to kick off a second thread to write to your attachment interface while your main thread is stuck in the proxy method. I won't get into too much detail here (you can probably imagine how this would work), but the WSDAPI FileService sample illustrates this pattern in actual code. The only real caveat here is that you need to guarantee that you call WSDCreateOutboundAttachment() before you call into the proxy method--a simple solution is to create the attachment object before spinning off the second thread, and before calling the proxy method. One interesting point to note is that because we guarantee that IWSDOutboundAttachment::Write will block until the message is ready to accept attachments, you can call Write on your second thread immediately after being launched, as long as the attachment object is properly allocated. Write will automatically wait for WSDAPI to trigger the attachment stream.
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::Write
A 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.
- Use the async Begin call to start your method, and retrieve an IWSDAsyncResult object.
- If you've got outbound attachments objects, send those now.
- Wait for the method to complete by using any of the three completion mechanisms exposed by IWSDAsyncResult.
- Use the async End call to end your method.
- Read from any inbound attachments in order.
- Release your attachment objects. This is a little tricky: for attachments returned as top-level parameters from your End proxy call, you must call Release on these objects to keep them from being leaked. However, for attachments returned inside another structure, don't call Release.
- Release your IWSDAsyncResult object.
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.