共用方式為


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

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.

Comments

  • Anonymous
    January 05, 2007
    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?

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

  • Anonymous
    January 05, 2007
    The comment has been removed

  • Anonymous
    January 05, 2007
    The comment has been removed

  • Anonymous
    January 05, 2007
    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?

  • Anonymous
    January 05, 2007
    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.

  • Anonymous
    January 05, 2007
    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.

  • Anonymous
    January 06, 2007
    "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?

  • Anonymous
    January 08, 2007
    The comment has been removed

  • Anonymous
    January 12, 2007
    The comment has been removed

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