Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Software Contracts, Part 2 - There are two sides to every contract.

Software Contracts, Part 2 - There are two sides to every contract.

  • Comments 11

One thing that's critically important to understand when thinking about software contracts is that just like in real life, every contract has at least two sides to it. 

For real world contracts (that is agreements between two or more parties), the contract embodies responsibilities for each party involved in the contract.  For example, if I enter into a contract to buy your house, my responsibility is to pay you the money for the house, your responsibility is to transfer ownership of the house to me.

Just like real world contracts, software contracts typically two sides (they can have more than two sides, but those situations are relatively rare).  Typically each software contract includes responsibilities for both the caller of a function and the function itself.

And it's critically important to understand your responsibilities w.r.t. the contract for a function.  Consider the contract for the GetFileAttributesEx function.  This function takes three parameters: The first is a path to the filename, the second is an infolevel, the third is an lpvoid pointer.

From the point of view of the caller, the GetFileAttributesEx API will retrieve a WIN32_FILE_ATTRIBUTE_DATA structure for the file corresponding to the filename parameter.  The caller knows that the GetFileAttributesEx API will either return a non zero value, in which case the memory pointed to by the lpvoid pointer will be filled in with the WIN32_FILE_ATTRIBUTE_DATA structure (assuming that the infolevel is GetFileExInfoStandard, the only documented infolevel).

However it's important to consider the other side of the contract.  From the point of view of the GetFileAttributesEx API, the caller is required to provide:

  • A valid pointer to a null terminated string that contains the name of the file to check in the filename parameter.
  • The value of GetFileExInfoStandard for the infolevel parameter.
  • A valid pointer to a block of memory that is at least sizeof(WIN32_FILE_ATTRIBUTE_DATA) bytes in length in the lpvoid pointer parameter.

If the caller does not provide all of those pieces of information, then the caller has violated their half of the contract, and the GetFileAttributesEx API is under no obligation to honor it's half of the contract.

For an example like this, both halves of the contract are blindingly obvious, but there are other examples that aren't as obvious.

 

A great example of a situation where a caller unknowingly violated the contract for an API was discovered during testing for Windows Vista.  During one of our test passes of Vista, the unit test application for the PlaySound API started crashing deep inside the bowels of the PlaySound API.  Of course I was asked to investigate (since I own the PlaySound API).

I was mystified by the failure - we were crashing while trying to access the samples for a sound being played.  In addition, the test didn't always fail, which was really quite strange.  I dug a bit deeper into the test application and realized that the test was effectively doing:

memory = LoadResource(resourceId);
PlaySound(memory, SND_MEMORY | SND_ASYNC);
FreeResource(memory);

The problem here is that when the test case called FreeResource memory, they violated a part of the PlaySound contract.  You see, when you call PlaySound with the SND_ASYNC flag and the SND_MEMORY flag together, the PlaySound contract requires that the memory remain valid until the sound has completed playing.

It turns out that this part of the PlaySound contract is NOT explicit - nowhere in the current PlaySound documentation does it say that the memory must remain valid while the sound is being played (as of this writing - I've filed a bug report on it).

But even though this behavior is not explicit, it's still a part of the contract, and must be honored by the caller.

  • I am happy to fulfill my contractual obligations but I need to know what they are. If you don't tell them, how is the caller to know that you need their memory until the sound finishes playing?

  • Dave, I'll answer your question Monday, it ties in well with the theme for that post.

  • It's good to have blogposts from you again!

    Admittedly, I'm not familiar with these APIs, but I would expect any of the OS's APIs to let the caller know that something went wrong. And what went wrong. How would the caller determine the error in this case?

    I guess I expect that to be implicit to the contract, but I may be assuming too much.

    Am I wrong in guessing that this API was origninally designed with only synchronous callers in mind and that the async calling convention was an afterthought? I'm a total sucker for dev lore . . .

  • Drew,

     Actually I would sincerely hope that the success/failure of an API would be reflected in the explicit contract of the API.

     Of course, there ARE APIs that have no valid failure mode (LeaveCriticalSection is a great example of such an API), but for those that CAN fail, an explicit mechanism should be provided to let the user know about the failure.

     For PlaySound, you're close to being right on the reasoning - as far as I know, Async was part of the initial design, but the default is synchronous.  For Win 3.1, mmsystem.dll (which implemented PlaySound) just created a window and pumped messages off that window.

  • The problem with async is that you CAN'T know that your memory has been freed by somebody else (except by casuing an access violation when it happens).

    Is there any mechanism to know when an async PlaySound has completed so that you CAN free the buffer?

  • Looks like you should tell the .NET people as well. Based on what I can see in System.Media.SoundPlayer via Reflector they just pass in a byte[] and the flags SND_ASYNC|SND_NODEFAULT|SND_MEMORY.  No pinning being done.  It's probably safe 99% of the time, but the bytes could be gc'd.

  • Dean: You can't - but you CAN call PlaySound with a null filename and it will stop playing.  So call PlaySound with a null filename before freeing the buffer and you'll be ok.

  • "The caller knows that the GetFileAttributesEx API will either return a non zero value, in which case the memory pointed to by the lpvoid pointer will be filled in with the WIN32_FILE_ATTRIBUTE_DATA structure (assuming that the infolevel is GetFileExInfoStandard, the only documented infolevel)."

    Either implies two options.  What's the other thing that it will return?  Null?

  • > nowhere in the current PlaySound documentation does it say

    > that the memory must remain valid while the sound is being

    > played (as of this writing - I've filed a bug report on it).

    > But even though this behavior is not explicit, it's still a part of

    > the contract

    Thank you.  Now we all understand that in Windows programming, just like in Linux programming, the documentation written in English or Japanese or whatever is not the contract but the implementation written in C or whatever is the contract.

    Publication of the contract is overdue.

  • >> (as of this writing - I've filed a bug report on it).  But even though this behavior is not explicit, it's still a part of the contract, and must be honored by the caller.

    I beg to differ.  It is a bug, not part of the contract.  Compare the following principle of contract law:  When a contract is ambiguous it is to be construed against the drafter.

    The implementation must not expect the caller to bear any burden that is not explicitly contracted in the documentation.

  • I'm more discombobulated than usual on this series, I totally missed the third article in the series

Page 1 of 1 (11 items)