• The Old New Thing

    How long does it take for a notification icon to stop appearing in the Notification Area Icons control panel?

    • 31 Comments

    A customer reported that even after uninstalling their application, the notification icon entry remains in the Notification Area Icons control panel.

    Yup, that's right. Explorer remembers the icon, even after the underlying program has been uninstalled, because you might have uninstalled it with the intention of immediately reinstalling it, so Explorer remembers the icon in case it comes back. But after one week, Explorer gives up and finally forgets about the icon. "It's been a week, and the user hasn't reinstalled the application. I'm going to give up waiting for it."

    The customer wanted to know how they could remove their icon immediately upon uninstall. They reported that having the icon remain in the Notification Area Icons control panel made it appear that the uninstall was unsuccessful.

    There is no documented mechanism for removing the icon (and the undocumented mechanisms destroy all the icon history, not just the icon history for your icon, so don't do that either). You'll just have to wait seven days for the icon to go away.

    (One possibility that was considered was to have the Notification Area Icons control panel check if the application is still installed before showing it on the list. This runs into problems, though, if the application resides on the network or removable media. It means that opening the Notification Area Icons control panel can stall on network I/O, generate security audits if you lost access to the network share, and spin up external media. Remember how much people hated it when Windows 95 spun up your CD-ROM drive the first time you clicked on the address bar?)

  • The Old New Thing

    DHS: The Television Series - the next chapter

    • 1 Comments

    Okay, so first there was the story about the planned DHS television series. And then there was the follow-up reporting that the whole thing was an investment scam and there was no such show after all. Following through on the story, it looks like the ringleader behind the scam has been sentenced to one year in prison and an accomplice to five years probation and $250 a month in restitution. The TV Squad article says retribution, which would be strange if true. But it's not. (Read the announcements of the accomplice's indictment and guilty plea.) At $250 a month, it's going to take a long time to repay the court-ordered $3.4 million in restitution.

    Two years later, the Ninth Circuit Court of Appeals ruled that the judge who imposed the one-year sentence for the ringleader failed to follow sentencing guidelines (which call for a minimum of 57 months), and ordered that he be sentenced again.

    Meanwhile, the accomplice appears to have violated the terms of her probation, so she was sentenced to ten days in jail.

    Who knows what'll happen next? This story may never reach its final chapter.

  • The Old New Thing

    Counting down to the last day of school, as students do it

    • 23 Comments

    Today is the last day of school in Seattle public school. My friend the seventh-grade teacher told me that students count down to the last day of school in a rather unusual way.

    Some people might count the number of calendar days until the end of school. For example, if there are 35 days between today and the last day of school, we say that it's 35 days until the end of school.

    Others might count only the number of school days before school is out. If today is Monday, and the last day of school is Friday, then there are five days of school remaining.

    But students, or at least seventh-grade students, count the days differently.

    First of all, you don't count today. Because today has already started, so you may as well treat it as already over.

    Next, you don't count the last day of school itself, because nobody gets anything done on the last day anyway, it's basically just one big party.

    Also, if there are any half-days or days with early dismissal, don't count those either, because days where you aren't there the whole day don't count, because the class periods are so short you don't get anything done.

    Similarly, days with special events like a field trip don't count.

    Furthermore, you don't count Mondays, because on Monday, you're still fresh off the weekend and you won't be concentrating on school anyway.

    Conversely, you don't count Fridays, because you've already mentally checked out.

    The result of all these calculations is that the students will cheerfully calculate that there are only 25 days of school left, when it's still only late April.

  • The Old New Thing

    Creating a simple pidl: For the times you care enough to send the very fake

    • 6 Comments

    I'll assume that we all know what pidls are and how the shell namespace uses them. That's the prerequisite for today.

    A simple pidl is an item ID list that refers to a file or directory that may not actually exist. It's a way of playing "what if": "If there were a file or directory at this location, here is what I would have created to represent it." For the times you care enough to send the very fake.

    We've seen these things in action with the SHGFI_USE­FILE­ATTRIBUTES flag, which tells the SH­Get­File­Info function, "Pretend that the file/directory exists with the attributes I specified, and tell me what the icon would be, were that item to actually exist."

    Internally, the SH­Get­File­Info function creates one of these "simple pidls", and then asks the simple pidl for its icon.

    Note that a simple pidl is really a special case of a pidl created from a WIN32_FIND_DATA. When you parse a display name with a custom bind context, and the bind context has a STR_FILE_SYS_FIND_DATA bind context object, then that object is used to control the information placed into the pidl instead of getting the information from the file system.

    Here's a program that creates a simple pidl and then does a few simple things with it. (Note that the Parsing with Parameters sample covers this topic too, so if you don't like the way I did it, you can look to see how somebody else did it.)

    #define STRICT_TYPED_ITEMIDS
    #include <new>
    #include <windows.h>
    #include <ole2.h>
    #include <oleauto.h>
    #include <shlobj.h>
    #include <propkey.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    
    class CFileSysBindData : public IFileSystemBindData
    {
    public:
     static HRESULT CreateInstance(
      _In_ const WIN32_FIND_DATAW *pfd,
      _In_ REFIID riid, _Outptr_ void **ppv);
    
     // *** IUnknown ***
     IFACEMETHODIMP QueryInterface(
      _In_ REFIID riid, _Outptr_ void **ppv)
     {
      *ppv = nullptr;
      HRESULT hr = E_NOINTERFACE;
      if (riid == IID_IUnknown ||
          riid == IID_IFileSystemBindData) {
       *ppv = static_cast<IFileSystemBindData *>(this);
       AddRef();
       hr = S_OK;
      }
      return hr;
     }
    
     IFACEMETHODIMP_(ULONG) AddRef()
     {
      return InterlockedIncrement(&m_cRef);
     }
    
     IFACEMETHODIMP_(ULONG) Release()
     {
      LONG cRef = InterlockedDecrement(&m_cRef);
      if (cRef == 0) delete this;
      return cRef;
     }
    
     // *** IFileSystemBindData ***
     IFACEMETHODIMP SetFindData(_In_ const WIN32_FIND_DATAW *pfd)
     {
      m_fd = *pfd;
      return S_OK;
     }
    
     IFACEMETHODIMP GetFindData(_Out_ WIN32_FIND_DATAW *pfd)
     {
      *pfd = m_fd;
      return S_OK;
     }
    
    private:
     CFileSysBindData(_In_ const WIN32_FIND_DATAW *pfd) :
      m_cRef(1)
     {
      m_fd = *pfd;
     }
    private:
     LONG m_cRef;
     WIN32_FIND_DATAW m_fd;
    };
    
    HRESULT CFileSysBindData::CreateInstance(
     _In_ const WIN32_FIND_DATAW *pfd,
     _In_ REFIID riid, _Outptr_ void **ppv)
    {
     *ppv = nullptr;
     CComPtr<IFileSystemBindData> spfsbd;
     HRESULT hr = E_OUTOFMEMORY;
     spfsbd.Attach(new (std::nothrow) CFileSysBindData(pfd));
     if (spfsbd) {
      hr = spfsbd->QueryInterface(riid, ppv);
     }
     return hr;
    }
    

    The CFile­Sys­Bind­Data object is extraordinarily boring. It simply implements IFile­System­Bind­Data, which is a simple interface that just babysits a WIN32_FIND_DATA structure. (There is also a IFile­System­Bind­Data2 interface which babysits a little more information, but for the purpose of this program, we're interested only in the WIN32_FIND_DATA.)

    HRESULT CreateBindCtxWithOpts(
     _In_ BIND_OPTS *pbo, _Outptr_ IBindCtx **ppbc)
    {
     CComPtr<IBindCtx> spbc;
     HRESULT hr = CreateBindCtx(0, &spbc);
     if (SUCCEEDED(hr)) {
      hr = spbc->SetBindOptions(pbo);
     }
     *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
     return hr;
    }
    

    A bind context is basically a string-indexed associative array of COM objects. There is also a BIND_OPTS (or BIND_OPTS2) structure in there, but the things most people care about are the object parameters. They provide an extensible method of passing arbitrary parameters to a function. (Think of it as the COM version of the JavaScript convention of jamming random junk into an Options parameter.) You start with a IBind­Ctx parameter, and any time you need to add a new flag or parameter, you just stuff it into the IBind­Ctx. If you just want to add a new boolean flag, you can even ignore the contents of the object parameter and merely base your behavior on whether the parameter exists at all.

    HRESULT AddFileSysBindCtx(
     _In_ IBindCtx *pbc, _In_ const WIN32_FIND_DATAW *pfd)
    {
     CComPtr<IFileSystemBindData> spfsbc;
     HRESULT hr = CFileSysBindData::CreateInstance(
      pfd, IID_PPV_ARGS(&spfsbc));
     if (SUCCEEDED(hr)) {
      hr = pbc->RegisterObjectParam(STR_FILE_SYS_BIND_DATA,
                                    spfsbc);
     }
     return hr;
    }
    

    To add a file system bind parameter, you just create an object which implements IFile­System­Bind­Data and register it with the bind context with the string STR_FILE_SYS_FIND_DATA.

    HRESULT CreateFileSysBindCtx(
     _In_ const WIN32_FIND_DATAW *pfd, _Outptr_ IBindCtx **ppbc)
    {
     CComPtr<IBindCtx> spbc;
     BIND_OPTS bo = { sizeof(bo), 0, STGM_CREATE, 0 };
     HRESULT hr = CreateBindCtxWithOpts(&bo, &spbc);
     if (SUCCEEDED(hr)) {
      hr = AddFileSysBindCtx(spbc, pfd);
     }
     *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
     return hr;
    }
    

    The Create­File­Sys­Bind­Ctx function simply combines the two steps of creating a bind context and then adding a file system bind parameter to it. In casual conversation, a bind context is often named after the parameter inside it. In this case, we have a bind context with a file system bind parameter, so we call it a "file system bind context".

    HRESULT CreateSimplePidl(
     _In_ const WIN32_FIND_DATAW *pfd,
     _In_ PCWSTR pszPath, _Outptr_ PIDLIST_ABSOLUTE *ppidl)
    {
     *ppidl = nullptr;
     CComPtr<IBindCtx> spbc;
     HRESULT hr = CreateFileSysBindCtx(pfd, &spbc);
     if (SUCCEEDED(hr)) {
      hr = SHParseDisplayName(pszPath, spbc, ppidl, 0, nullptr);
     }
     return hr;
    }
    

    This is where everything comes together. To create a simple pidl, we take the WIN32_FIND_DATAW containing the metadata we want to use, put it inside a file system bind context, then use that bind context to parse the file name. The presence of a file system bind context tells the parser, "Trust me on this, just go with what's in the bind context." It suppresses all disk access, and the final pidl will describe an item that exactly matches the metadata you provided, whether that accurately reflects reality or not. (You can also pass the bind context to SHCreate­Item­From­Parsing­Name if you prefer to get an IShell­Item.)

    Okay, let's take this out for a spin.

    void DoStuffWith(_In_ PCIDLIST_ABSOLUTE pidl)
    {
     // Print the file name
     wchar_t szBuf[MAX_PATH];
     if (SHGetPathFromIDListW(pidl, szBuf)) {
      wprintf(L"Path is \"%ls\"\n", szBuf);
     }
    
     // Print the file size
     CComPtr<IShellFolder2> spsf;
     PCUITEMID_CHILD pidlChild;
     if (SUCCEEDED(SHBindToParent(pidl,
                            IID_PPV_ARGS(&spsf), &pidlChild))) {
      CComVariant vt;
      if (SUCCEEDED(spsf->GetDetailsEx(pidlChild,
                            &PKEY_Size, &vt))) {
       if (SUCCEEDED(vt.ChangeType(VT_UI8))) {
        wprintf(L"Size is %I64u\n", vt.ullVal);
       }
      }
     }
    }
    
    int __cdecl wmain(int argc, PWSTR argv[])
    {
     CCoInitialize init;
     if (SUCCEEDED(init)) {
      WIN32_FIND_DATAW fd = {};
      fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
      fd.nFileSizeLow = 42;
      CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlSimple;
      if (SUCCEEDED(CreateSimplePidl(&fd,
                    L"Q:\\Whatever.txt", &spidlSimple))) {
       DoStuffWith(spidlSimple);
      }
     }
     return 0;
    }
    

    Our test program asks for a simple pidl to Q:\Whatever.txt, and then prints information from it. Observe that the creation of the simple pidl succeeds even though you probably don't have a Q: drive, and even if you did, the code never tried to access it. And when we ask the pidl, "Hey, what's the file size?" it retrieves the fake value 42 we passed in the WIN32_FIND_DATAW structure.

    Sure, that was kind of artificial, but so-called simple pidls are handy if you want to talk about an object on slow media (such as a network share) without actually accessing the target device.

    Exercise: What changes are necessary in order to create a simple pidl that refers to a file with illegal characters in its name? Hint: STR_NO_VALIDATE_FILENAME_CHARS.

  • The Old New Thing

    One for the "They have to say that because of me": Ground rules at the Point Defiance Zoo

    • 13 Comments

    The ground rules for the Point Defiance Zoo and Aquarium in Tacoma include the usual things you might expect. "No pets." "Do not feed the animals." "No smoking." But then there's a rule that clearly is one about which somebody somewhere in the world can say "They have to say that because of me":

    • Remain clothed at all times.
  • The Old New Thing

    Every crash is a potential security vulnerability

    • 8 Comments

    Whenever I post about a programming error that can lead to crashes, the security team gets all excited and starts looking for ways to exploit it. For example, when I wrote about the fundamentally flawed DONT_RESOLVE_DLL_REFERENCES flag, the security folks went scouring through the Windows source code looking for anybody who passed that flag, and then tried to come up with ways they could trick the code into loading an unintended DLL and causing trouble.

    I wouldn't have known about this exercise at all if one of the team members hadn't forwarded me some email discussing their preliminary investigations as if to say, "See what you started?"

  • The Old New Thing

    If my window hosts multiple windowless ActiveX controls, which one do I call IOleInPlaceActiveObject::TranslateAccelerator and IOleInPlaceObjectWindowless::OnWindowMessage on?

    • 15 Comments

    Commenter Farproc asks how one goes about hosting two windowless ActiveX controls in a single window. In particular, "none of the documentation explains how to choose which control to send IOle­In­Place­Active­Object::Translate­Accelerator and IOle­In­Place­Object­Windowless::On­Window­Message on?"

    Actually, the documentation does say.

    The documentation for IOle­In­Place­Active­Object::Translate­Accelerator says, "Active in-place objects must always be given the first chance at translating accelerator keystrokes." So you pass the message to the active in-place object. Your window may host multiple windowless ActiveX controls, but at most one of them is the active object at a time. And most of the time, none of them will be active. For example, in Word, most of the time the insertion point is in the text part of the document. Only occasionally do you activate an in-place object by, say, double-clicking on an embedded Excel spreadsheet, at which point Excel adds its menu items to your menu bar and basically takes over your application window for a while.

    Here's an example of Windows 95's Wordpad hosting Paint as an in-place active object.

    Source: 2.1.6 OLE/COM example: using compound documents

    If you have an in-place active object, then it's the one that gets the IOle­In­Place­Active­Object::Translate­Accelerator If, as is usually the case, you don't have an in-place active object, then nobody's IOle­In­Place­Active­Object::Translate­Accelerator gets called because they aren't the in-place active object. (It's right there in the interface name.)

    For IOle­In­Place­Object­Windowless::On­Window­Message, the documentation is even more explicit. It contains pretty much a checklist of what you need to do.

    For the following messages, the container should first dispatch the message to the windowless object that has captured the mouse, if any. Otherwise, the container should dispatch the message to the windowless object under the mouse cursor. If there is no such object, the container is free to process the message itself:

    • WM_MOUSEMOVE
    • WM_SETCURSOR
    • WM_XBUTTONDOWN
    • WM_XBUTTONUP
    • WM_XBUTTONDBLCLK

    The container should dispatch the message to the windowless object with the keyboard focus for the following messages:

    • WM_CANCELMODE
    • WM_CHAR
    • WM_DEADCHAR
    • WM_HELP
    • WM_IMExxx
    • WM_KEYDOWN
    • WM_KEYUP
    • WM_SYSDEADCHAR
    • WM_SYSKEYDOWN
    • WM_SYSKEYUP

    For all other messages, the container should process the message on its own.

    There it is, plain as day.

    Farproc's last question was "how to track or set 'focus' if there is at least one windowless control."

    Um, in a variable?

    I was kind of confused by this question because it's part of the deal that when you use windowless controls, you don't have the window manager to take care of keeping track of which sub-object has focus. That now becomes your job.

    The user clicked on an object. I guess that's the focus object now. Oh wait, now the user hit the left arrow. I guess the object to the left of that object has focus now. It's just like any other control with windowless sub-components, like list boxes. You have to keep track yourself of the currently-selected item and other properties which the window manager normally does for you. If you don't have any windows, then there is nothing for the window manager to manage. From the window manager's point of view, focus is on your container. You then have to manage focus within your window yourself by keeping track of which of your sub-objects is the focus object.

  • The Old New Thing

    Raymond's highly scientific predictions for the 2011 NCAA men's basketball tournament

    • 28 Comments

    Once again, it's time for Raymond to come up with an absurd, arbitrary criterion for filling out his NCAA bracket.

    This year, I look at the strength of the school's football team, on the theory that a school with a strong football team and a strong basketball team has clearly invested a lot in its athletics program. My ranking of football teams is about as scientific as my ranking of basketball teams:

    • If the school ended last season with a BCS ranking, I used that.
    • If a school wasn't ranked but received votes in the AP ranking, then I gave it a rank of 30 (and if two such schools faced each other, I looked at who got more votes).
    • If a school still isn't ranked, then I looked to see if it had been ranked at any time earlier in the season; if so, then I gave it a rank of 40.
    • If a school still isn't ranked, but it appeared on the equally-scientific ESPN Fan Rankings, then I gave it a rank of 50.
    • If a school still isn't ranked, but it has a Division I FBS football team, then I gave it a rank of 80. If two such schools faced each other, then I gave what appeared to be the weaker school a rank of 90.
    • If a school still isn't ranked, but it has a Division I FCS football team, then I gave it a rank of 100. If two such schools faced each other, then I gave what appeared to be the weaker schools a rank of 101. (Why 101 instead of 110? Who cares!)
    • If a school still isn't ranked, but it has a football team in some other division, then I gave it a rank of 150.
    • If a school still isn't ranked because its football team is new, then I gave it a rank of 200.
    • If a school still isn't ranked because it doesn't have a football team, but it had one in the past, then I gave it a rank of 300.
    • If a school still isn't ranked because it never had a football team, then I gave it a rank of 400.

    (As a special case, USC received its rank of 22 from two years ago, because it was forced to sit out the 2010 season as part of its punishment for "several major rules violations." Now that's what I call dedication to athletics!)

    I made up all these rules on the fly, which is why the spacing is so uneven and why they were not necessarily applied fairly across the board, but that's what makes it highly scientific.

    As before, once the field has been narrowed to eight teams, the results are determined by a coin flip.

    Update:

    • Correct predictions are in green.
    • Incorrect predictions are in red.
    • (!) marks upsets correctly predicted.
    • (*) marks upsets predicted but did not take place.
    • (x) marks actual upsets not predicted.

    Opening Round Games

    Texas-San Antonio(200) Alabama State
    (80)
    Alabama State(80)
    UAB(90) Clemson
    (80)
    Clemson(80)
    UNC-Asheville(400) Arkansas-Little Rock
    (300)
    Arkansas-Little Rock(300)
    USC(22*) USC
    (22*)
    VCU(400)

    East bracket

    1Ohio State(6) Ohio State
    (6)
    Ohio State
    (6)
    Ohio State Ohio State
    16Alabama State(80)
    8George Mason(400) Villanova
    (100) (*)
    9Villanova(100)
    5Kentucky(80) Kentucky
    (80)
    West Virginia
    (30) (*)
    12Princeton(100)
    4West Virginia(30) West Virginia
    (30)
    13Clemson(80)
    6Syracuse(80) Syracuse
    (80)
    Syracuse
    (80) (x)
    Washington
    11Indiana State(90)
    3Xavier(300) Xavier
    (300) (x)
    14Marquette(310)
    7Washington(30) Washington
    (30)
    Washington
    (30)
    10Georgia(50)
    2North Carolina(50) North Carolina
    (50)
    15Long Island(400)

    West bracket

    1Duke(90) Duke
    (90)
    Michigan
    (80) (x)
    Arizona Arizona
    16Hampton(100)
    8Michigan(80) Michigan
    (80)
    9Tennessee(90)
    5Texas(40) Texas
    (40)
    Arizona
    (40)
    12Oakland(400)
    4Arizona(40) Arizona
    (40)
    13Memphis(80)
    6Connecticut(30) Connecticut
    (30)
    Missouri
    (12) (*)
    Missouri
    11Bucknell(100)
    3Cincinnati(80) Missouri
    (12) (*)
    14Missouri(12)
    7Temple(80) Penn State
    (40) (*)
    San Diego State
    (30)
    10Penn State(40)
    2San Diego State(30) San Diego State
    (30)
    15Northern Colorado(200)

    Southeast bracket

    1Pittsburgh(80) Pittsburgh
    (80)
    Pittsburgh
    (80) (x)
    Wisconsin Michigan State
    16Arkansas-Little Rock(300)
    8Butler(100) Butler
    (100)
    9Old Dominion(101)
    5Wisconsin(4) Wisconsin
    (4)
    Wisconsin
    (4)
    12Belmont(150)
    4Kansas State(40) Kansas State
    (40)
    13Utah State(80)
    6BYU(39) BYU
    (39)
    BYU
    (39)
    Michigan State
    11Wofford(100)
    3St. John's(200) St. John's
    (200) (x)
    14Gonzaga(300)
    7UCLA(80) Michigan State
    (7) (*)
    Michigan State
    (7)
    10Michigan State(7)
    2Florida(30) Florida
    (30)
    15UCSB(400)

    Southwest bracket

    1Kansas(90) Kansas
    (90)
    Illinois
    (80) (*)
    Illinois Texas A&M
    16Boston University(300)
    8UNLV(90) Illinois
    (80) (!)
    9Illinois(80)
    5Louisville(82) Louisville
    (82) (x)
    Louisville
    (82)
    12Morehead State(100)
    4Vanderbilt(90) Vanderbilt
    (90) (x)
    13Richmond(91)
    6Purdue(90) Purdue
    (90)
    USC
    (22*)
    Texas A&M
    11Saint Peter's(300)
    3Georgetown(100) USC
    (22*)
    14USC(22*)
    7Texas A&M(18) Texas A&M
    (18) (x)
    Texas A&M
    (18)
    10Florida State(23)
    2Notre Dame(30) Notre Dame
    (30)
    15Akron(80)

    Finals

    Ohio State Ohio State Michigan State
    Arizona
    Michigan State Michigan State
    Texas A&M
  • The Old New Thing

    You can ask the compiler to answer your calling convention questions

    • 22 Comments

    If you want to figure out some quirks of a calling convention, you can always ask the compiler to do it for you, on the not unreasonable assumption that the compiler understands calling conventions.

    "When a __stdcall function returns a large structure by value, there is a hidden first parameter that specifies the address the return value should be stored. But if the function is a C++ instance method, then there is also a hidden this parameter. Which goes first, the return value parameter or the this pointer?"

    This is another case of You don't need to ask me a question the compiler can answer more accurately.

    struct LargeStructure
    {
     char x[256];
    };
    
    class Something
    {
    public:
     LargeStructure __stdcall TestMe();
    };
    
    void foo(Something *something)
    {
     LargeStructure x = something->TestMe();
    }
    

    You could compile this into a program and then look in the debugger, or just ask the compiler to generate an assembly listing. I prefer the assembly listing, since it saves a few steps, and the compiler provides helpful symbolic names.

      00015 mov     eax, DWORD PTR _something$[ebp]
    
    ; LargeStructure x = something->TestMe();
    
      00018 lea     ecx, DWORD PTR _x$[ebp]
      0001e push    ecx
      0001f push    eax
      00020 call    ?TestMe@Something@@
                    QAG?AULargeStructure@@XZ
                    ; Something::TestMe
    
    

    We see that the last thing pushed onto the stack (and therefore the top parameter on the stack at the point of the call) is the something parameter, which is the this for the function.

    Conclusion: The this pointer goes ahead of the output structure pointer.

  • The Old New Thing

    Trying to avoid double-destruction and inadvertently triggering it

    • 34 Comments

    We saw some time ago the importance of artificially bumping an object's reference count during destruction to avoid double-destruction. However, one person's attempt to avoid this problem ended up triggering it.

    ULONG MyObject::Release()
    {
     LONG cRef = InterlockedDecrement(&m_cRef);
     if (cRef > 0) return cRef;
     m_cRef = MAXLONG; // avoid double-destruction
     delete this;
     return 0;
    }
    

    The explanation for the line m_cRef = MAXLONG was that it was done to avoid the double-destruction problem if the object receives a temporary AddRef/Release during destruction.

    While it's true that you should set the reference count to an artificial non-zero value, choosing MAXLONG has its own problem: integer overflow.

    Suppose that during the object's destruction, the reference count is temporarily incremented twice and decremented twice.

    Action m_cRef
    Just before call to Release() 1
    InterlockedDecrement 0
    m_cRef = MAXLONG 2147483647
    destructor does temporary AddRef() −2147483648 (integer overflow)
    destructor does temporary AddRef() −2147483647
    destructor does temporary Release() −2147483648
    since m_cRef < 0, we re-destruct

    Sure, choosing a huge DESTRUCTOR_REFCOUNT means that you have absolutely no chance of decrementing the reference count back to zero prematurely. However, if you choose a value too high, you introduce the risk of incrementing the reference count so high that it overflows.

    That's why the most typical values for DESTRUCTOR_REFCOUNT are 1, 42, and 1000. The value 1 is really all you need to avoid double-destruction. Some people choose 42 because it's cute, and other people choose 1000 because it's higher than any "normal" refcount, so it makes it easier to spot during debugging. But even then, the "high" value of 1000 still leaves room for over two billion AddRef()s before overflowing the reference count.

    On the other hand, if you choose a value like MAXLONG or MAXDWORD, then you're taking something that previously never happened (reference count integer overflow) and turning it into an almost certainty.

Page 370 of 427 (4,265 items) «368369370371372»