September, 2005

  • The Old New Thing

    The reverse-engineering of PDC 2005 pass colors

    • 11 Comments

    Last night, the MVP Global Summit broke up by product groups for dinner. I was at the Windows Client product group dinner. The problem for me was figuring out who were the MVPs and who were just Microsoft employees looking for MVPs to chat with. Unfortunately, the people who made up the badges didn't think of making it easy to tell who is who. I saw badges of different colors, but they appeared to be coded by product group rather than by attendee status. More than once, I sat down next to someone and introduced myself, only to find that they were another Microsoft employee. (Hey, but I made Robert Flaming do a spit take. That's gotta be worth something.)

    One thing I was able to figure out at the 2005 PDC was the badge colors. Here they are, for reference:

    Black   Event staff  
    Yellow   Speakers  
    Blue   Microsoft staff  
    Green   Attendee  
    Orange   Attendee-Exhibitor  
    Purple   Exhibitor  
    Red   Media  

    The color-coding by attendee type made it much easier to identify attendees to chat with. Though I somehow have developed an unfortunate knack for picking a table where people aren't speaking English. At the PDC, I sat down at a table and realized that everybody was speaking Dutch. Unfortunately, although I intend to learn Dutch eventually, it's a few languages down my list. Last night, at the MVP Global Summit, I was about to join a table but realized that they were speaking in what sounded like a Central or possibly Eastern European language. There's nothing like an international gathering to make you feel linguistically inadequate...

  • The Old New Thing

    English Cut: Bringing bespoke tailoring into the general consciousness

    • 12 Comments

    Thomas Mahon's English Cut is a glimpse into the world of bespoke tailoring, a world I was completely unaware of prior to his web site. I was particularly fascinated by his description of how to recognise the work of Anderson & Sheppard by inspecting the pockets. It is this attention to detail that really makes the difference between good and great. I like to think that every now and then one of my own essays, say on the fine details of scroll bars or on supporting double-clicked radio buttons, also helped someone raise the quality of their software from good to great.

    Mr. Mahon's partner in crime is Hugh Macleod who points out that the blog is the main driver of new business. Remember that the next time somebody tries to start a marketing blog. Mr. Mahon's web site shares his knowledge with us, teaching us about his craft, and does so in a conversationally engaging manner. Even when he's selling himself, he does so while praising his competition.

  • The Old New Thing

    But I have Visual Basic Professional

    • 39 Comments

    Back in 1995, I was participating in a chat room on MSN on the subject of device driver development. One of the people in the chat room asked, "Can I write a device driver in Visual Basic?"

    I replied, "Windows 95 device drivers are typically written in low-level languages such as C or even assembly language."

    Undaunted, the person clarified: "But I have Visual Basic Professional."

  • The Old New Thing

    The DHS television show was all a scam, it appears

    • 2 Comments

    Xeni Jardin on Boing Boing reports that the DHS series I mentioned a while back was all an elaborate scam to bilk investors out of millions. (One tip-off has got to be that they changed their domain name from www.dhs.tv to www.dhstheseries.tv. And curiously, their "News" page just says P ALIGN"LEFT"/P.) Read Ms. Jardin's article for more linkity goodness. I guess this has a good side: It means no FEMA episode.

  • The Old New Thing

    On objects with a reference count of zero

    • 12 Comments

    One commenter claimed that

    When the object is first constructed, the reference count should be 0 and AddRef should be called at some point (probably via QueryInterface) to increment the reference count.

    If you construct your object with a reference count of zero, you are playing with matches. For starters, when the object is created, there reference count is not zero - the person who created the object has a reference! Remember the COM rule for references: If a function produces a reference (typically an interface pointer), the reference count is incremented to account for the produced reference. If you consider the constructor to be a function, then it needs to return with an incremented reference count to account for the produced object.

    If you prefer to play with matches, you can end up burning yourself with code like the following:

    // A static creator method
    HRESULT MyObject::Create(REFIID riid, void **ppvObj)
    {
     *ppvObj = NULL;
     MyObject *pobj = new MyObject();
     HRESULT hr = pobj ? S_OK : E_OUTOFMEMORY;
     if (SUCCEEDED(hr)) {
      hr = pobj->Initialize(); // dangerous!
      if (SUCCEEDED(hr)) {
       hr = pobj->QueryInterface(riid, ppvObj);
      }
      if (FAILED(hr)) {
       delete pobj;
      }
     }
     return hr;
    }
    

    Notice that you're initializing the object while its reference count is zero. This puts you in the same "limbo zone" as cleaning up an object while its reference count is zero, and therefore exposes you to the same problems:

    HRESULT MyObject::Load()
    {
     CComPtr<IStream> spstm;
     HRESULT hr = GetLoadStream(&spstm);
     if (SUCCEEDED(hr)) {
      CComQIPtr<IObjectWithSite, &IID_IObjectWithSite> spows(spstm);
      if (spows) spows->SetSite(this);
      hr = LoadFromStream(spstm);
      if (spows) spows->SetSite(NULL);
     }
     return hr;
    }
    
    HRESULT MyObject::Initialize()
    {
     return Load();
    }
    

    An object that saves itself during destruction is very likely to load itself during creation. And you run into exactly the same problem. The call to IObjectWithSite::SetSite(this) increments the reference count of the object from zero to one, and the call to The call to IObjectWithSite::SetSite(NULL) decrements it back to zero. When the reference count decrements to zero, this destroys the object, resulting in the object being inadvertently destroyed by the MyObject::Load() method.

    The MyObject::Create static method doesn't realize that this has happened and proceeds to call the QueryInterface method to return a pointer back to the caller, expecting it to increment the reference count from zero to one. Unfortunately, it's doing this to an object that has already been destroyed.

    That's what happens when you play with an object whose reference count is zero: It can disappear the moment you relinquish control. Objects should be created with a reference count of one, not zero.

    ATL prefers to play with matches, using the moral equivalent of the above MyObject::Create function in its object construction:

    void InternalFinalConstructAddRef() {}
    void InternalFinalConstructRelease()
    {
        ATLASSERT(m_dwRef == 0);
    }
    
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
    {
        ATLASSERT(*ppv == NULL);
        HRESULT hRes = E_OUTOFMEMORY;
        T1* p = NULL;
        ATLTRY(p = new T1(pv))
        if (p != NULL)
        {
    	p->SetVoid(pv);
    	p->InternalFinalConstructAddRef();
    	hRes = p->FinalConstruct();
    	p->InternalFinalConstructRelease();
    	if (hRes == S_OK)
    	    hRes = p->QueryInterface(riid, ppv);
    	if (hRes != S_OK)
    	    delete p;
        }
        return hRes;
    }
    

    ATL hands you a set of matches by calling your FinalConstruct method with a reference count of zero. If you know that you're going to get burned, you can use the DECLARE_PROTECT_FINAL_CONSTRUCT macro to change the InternalFinalConstructAddRef and InternalFinalConstructRelease methods to versions that actually increment the reference count temporarily during the call to FinalConstruct, then drop the reference count back to zero (without destructing the object) prior to the QueryInterface call.

    It works, but in my opinion it relies too much on programmer vigilance. The default for ATL is to hand programmers matches and relying on programmers "knowing" that something dangerous might happen inside the FinalConstruct and having the presence of mind to ask for DECLARE_PROTECT_FINAL_CONSTRUCT. In other words, it chooses the dangerous default, and programmers must explicitly ask for the safe version. But programmers have a lot of things on their mind, and forcing them to consider the consequences of the transitive closure of every operation performed in the FinalConstruct method is an unresonable requirement.

    Consider our example above. When the code was originally written, the Load method may have been the much simpler

    HRESULT MyObject::Load()
    {
     CComPtr<IStream> spstm;
     HRESULT hr = GetLoadStream(&spstm);
     if (SUCCEEDED(hr)) {
      hr = LoadFromStream(spstm);
     }
     return hr;
    }
    

    It wasn't until a month or two later that somebody added site support to the Load and Save methods. This seemingly simple and isolated change, adhering perfectly to the COM rules for reference counting, had ripple effects back through the object creation and destruction code paths. If you put four levels of function calls between the FinalConstruct and the Load, this fourth-level-caller effect can very easily be overlooked. I suspect that these nonlocal effects are one of the most significant sources of code defects. ATL was being clever and optimized out an increment and a decrement (something which the compiler most likely could optimize out on its own), but in return, you got handed a book of matches.

    (I don't mean to be picking on ATL here, so don't go linking to this article with the title "Raymond rails into ATL as a poorly-designed pile of dung". ATL is trying to be small and fast, but the cost is added complexity, often subtle.)

  • The Old New Thing

    Avoiding double-destruction when an object is released

    • 23 Comments

    As we saw last time, trying to do too much in one's destructor can lead to an object being destroyed twice. The standard way to work around this problem is to set an artificial reference count during destruction.

    class MyObject : public IUnknown
    {
     ...
     ULONG Release()
     {
      LONG cRef = InterlockedDecrement(&m_cRef);
      if (cRef == 0) {
       m_cRef = DESTRUCTOR_REFCOUNT;
       delete this;
      }
      return cRef;
     }
     ...
    private:
     }
     enum { DESTRUCTOR_REFCOUNT = 42 };
     ~MyObject()
     {
      if (m_fNeedSave) Save();
      assert(m_cRef == DESTRUCTOR_REFCOUNT);
     }
    };
    

    If you have a common implementation of IUnknown, you can set the reference count to DESTRUCTOR_REFCOUNT in your implementation of IUnknown::Release like we did here, and assert that the value is correct in your implementation's destructor. Since C++ runs base class destructors after derived class destructors, your base class destructor will check the reference count after the derived class has done its cleanup.

    By setting the reference count to an artificial non-zero value, any AddRef() and Release() calls that occur will not trigger a duplicate destruction (assuming of course that nobody in the destructor path has a bug that causes them to over-release). The assertion at the end ensures that no new references to the object have been created during destruction.

    This is really more of a workaround than a rock-solid solution, because it assumes that no functions called during the destruction sequence retain a reference to the object beyond the function's return. This is in general not something you can assume about COM. In general, a method is free to call AddRef and hang onto a pointer to an object in order to complete the requested operation later. Some methods (such as the IPersistPropertyBag::Load method) explicitly forbid such behavior, but these types of methods are more the exception rather than the rule.

    Exercise: Why is it safe to perform a simple assignment m_cRef = DESTRUCTOR_REFCOUNT instead of the more complicated InterlockedExchangeAdd(&m_cRef, DESTRUCTOR_REFCOUNT)?

  • The Old New Thing

    COM object destructors are very sensitive functions

    • 20 Comments

    If you try to do too much, you can find yourself in trouble.

    For example, if your destructor hands a reference to itself to other functions, those functions might decide to call your IUnknown::AddRef and IUnknown::Release methods as part of their internal operations. Consider:

    ULONG MyObject::Release()
    {
     LONG cRef = InterlockedDecrement(&m_cRef);
     if (cRef == 0) {
      delete this;
     }
     return cRef;
    }
    
    MyObject::~MyObject()
    {
     if (m_fNeedSave) Save();
    }
    

    That doesn't look so scary now does it? The object saves itself when destructed.

    However, the Save method might do something like this:

    HRESULT MyObject::Save()
    {
     CComPtr<IStream> spstm;
     HRESULT hr = GetSaveStream(&spstm);
     if (SUCCEEDED(hr)) {
      CComQIPtr<IObjectWithSite, &IID_IObjectWithSite> spows(spstm);
      if (spows) spows->SetSite(this);
      hr = SaveToStream(spstm);
      if (spows) spows->SetSite(NULL);
     }
     return hr;
    }
    

    On its own, this looks pretty normal. Get a stream and save to it, setting ourselves as its site in case the stream wants to get additional information about the object as part of the saving process.

    But in conjunction with the fact that we call it from our destructor, we have a recipe for disaster. Watch what happens when the last reference is released.

    • The Release() method decrements the reference count to zero and performs a delete this.
    • The destructor attempts to save the object.
    • The Save() method obtains the save stream and sets itself as the site. This increments the reference count from zero to one.
    • The SaveToStream() method saves the object.
    • The Save() method clears the site on the stream. This decrements the reference count from one back to zero.
    • The Release() method therefore attempts to destructor the object a second time.

    Destructing the object a second time tends to result in widespread mayhem. If you're lucky, you'll crash inside the recursive destruction and identify the source, but if you're not lucky, the resulting heap corruption won't go detected for quite some time, at which point you'll just be left scratching your head.

    Therefore, at a minimum, you should assert in your AddRef() method that you aren't incrementing the reference count from zero.

    ULONG MyObject::AddRef()
    {
     assert(m_cRef != 0);
     return InterlockedIncrement(&m_cRef);
    }
    

    This would catch the "case of the mysteriously double-destructed object" much earlier in the game, giving you a fighting chance of identifying the problem. But once you've isolated the problem, what can you do about it? We'll look into that next time.

  • The Old New Thing

    Raymond's 2005 MVP Global Summit event diary

    • 21 Comments

    In case those coming into town for the 2005 MVP Global Summit were interesting in chatting with me, here's my event diary for this week. (Non-native-English-speaking MVPs can hover over the highlighted words for a translation from slang into "standard American-English".)

    • Even though it's not affiliated with the MVP Global Summit, I'll mention it anyway since it happens the same week: I'll be at CodeSlam on Tuesday night. The name is a play on Poetry Slam, but instead of reciting poetry, you share your code. If you're an MVP with a cool app you want to contribute to CodeGallery, and you want to be part of the biggest geek slumber party in Redmond, let me know. I can bring one guest. (However, since I am old and decrepit and have a job to go to the following morning, I probably won't last the whole night.)
    • I am trying to finagle a ticket to the Windows Shell/User MVP dinner on Thursday evening.
    • If I can't get a ticket to the MVP dinner, I'll definitely crash the Windows Shell/User MVP lunch on Friday.
    • I'll pop by the MVP Barbecue on Saturday to touch base with the MVPs in disciplines other than Windows Shell/User.

    If you want to be sure to catch me, drop me a line and I'll try to make time to see you. Note, however, that I'm doing this outside of my "regular job", so please be a bit understanding if a work emergency forces me to do some last-minute rescheduling.

    [Colors fixed 8:30am; sorry I was half-asleep when I wrote this entry.]

  • The Old New Thing

    Kurt would have wanted it that way

    • 10 Comments

    Several years ago, I had the pleasure of working in the office next to Danny, a phenomenally talented fellow, not just a stellar programmer but also an accomplished pianist, singer, video game restorer, and skier. I remember when he was working on DirectSound3D, we would sometimes put our heads together to nail the formulas for effects such as Doppler shifts. Particularly satisfying was when we attacked the problem from two directions and arrived at what initially seemed like different answers, but after some algebraic manipulation turned out to be the same thing. When you can solve a problem in two totally different ways and get the same answer, you can be pretty confident that you got it right.

    One of the sounds he used during testing was Nirvana's Smells Like Teen Spirit. I remember a bug was filed that went something like "If you position the listener like so and put the sound source over there, start playing Smells Like Teen Spirit, then move the sound source along this path here, then when it reaches that point right there, the music comes out distorted." Danny's initial response was, "Kurt would have wanted it that way."

    And then he set to fixing the bug.

    In related news, Paul Anka, the man who wrote the theme to The Tonight Show with Johnny Carson, the words to Frank Sinatra's My Way, and over 900 other songs, has turned the tables on all the singers who covered his songs by covering landmark rock music songs. Including Nirvana's Smells Like Teen Spirit. [WM] [Real] And this time you can understand the words.

    A colleague of mine pointed out that Paul Anka didn't write the music to My Way:

    The funny thing is how in your link they admit it, and at the same time shamelessly ignore it, calling it a 'seminal composition':

    What was the story behind the writing of 'My Way' and was it written specifically for Frank Sinatra?

    Paul first heard the French song, Comme D'habitude, in the summer of 1967 when he was in Europe. Although it had different lyrics and a much different feel, Paul instantly connected with the melody. After running into Frank Sinatra, in Florida, who mentioned retiring sometime soon, he asked Paul when was he going to write something for him... so Paul, determined to do just that, returned to New York, sat down at the piano at 1 a.m. and wrote the song. Five hours later, this seminal composition was finished and was sent to Sinatra. ...

  • The Old New Thing

    This is an unsupported ride, so if you stop, you will fall over

    • 5 Comments

    Well, at least I only passed out once.

    To recap, The E's goaded me into joining them on a ride up Zoo Hill. I'm really not a climber. I'm taxed by the hill on the southbound 520 Trail leading to NE 51st St., so much so that I commute to work along a different route (taking 156th Ave. NE instead) just so I can avoid it. But I accepted their challenge anyway.

    I didn't literally pass out, but I did run out of steam at 173rd Ave SE and had to stop for a nap. Well, okay not really a nap, just a sit-down break, but I really could've used a nap right then. Maybe if I had remembered to bring an inflatable pillow.

    And then I missed the turn onto SE Cougar Mountain Dr. The instructions said to turn left at the stop sign, but I didn't realize that it was a stop sign for another road that I was to be on the lookout for. It wasn't until I took the turn onto 168th Pl NE that I realized I missed it, at which point I turned around and found the early finishers who had descended from the top, gathered at the stop sign. (I'm guessing that it was while I was on this unexpected detour that the Fat Cyclist went past on his way down searching for me. Sorry. Don't worry about the cake; I wasn't going to have any anyway.)

    Aside: There seems to be some sort of psychology that says that if you've exercised a lot, then you've "earned" some sort of bad behavior. Fortunately, my brain doesn't fall for that trick. After a hard ride, I don't say, "Whoo, let's have some cake!" I just say, "Whoo, that was a hard ride." If I even think about what I've "earned", my attitude is to "bank it for the future"; i.e., maybe next week I can eat something decadent. Eventually, next week comes, and I say, "Eh, I don't really need cake that bad. I should stick to my diet." Result: No cake pig-out. Weight under control.

    Here's a tip to cyclists: Remember which lever shifts to a higher gear and which shifts to a lower one. As I was being passed by Simeon, I felt the need to downshift, but in my delirium, I upshifted. My legs told me, "Nope, gear still too high, try some more," and my hand once again reached for the upshift lever. Only after the third iteration of this communications failure did my brain step in and say, "No no, you're doing this all wrong," and convince my hand to push the other lever. But by then it was too late; my legs were saying, "You moron! Look what you did to me!"

    It was shortly after this mental lapse that I had to stop for a nap.

    (Oh, and I over-estimated how long it would take me to get to the starting point from my house. It was only an hour's ride. Afterwards, I joined a colleague in a descent down the other side of the hill and cycled into the more densely-populated parts of Issaquah, eventually converging later that evening on a Battlestar Galactica-watching party we had been invited to.)

Page 1 of 4 (39 items) 1234