• The Old New Thing

    Is there a 2048 character limit for OFN_ALLOWMULTISELECT in MFC or isn't there?

    • 17 Comments

    The MFC documentation for CFile­Dialog contains the following strange warning:

    When the user allocates their own buffer to accommodate OFN_ALLOW­MULTI­SELECT, the buffer can't be larger than 2048 or else everything gets corrupted (2048 is the maximum size).

    The sudden informality of the phrase "or else everything gets corrupted" is surprising but also sounds vaguely familiar to me. I think I was the one who wrote that phrase over a decade ago as part of my investigation into a defect in the common dialog functions. Somebody must have forwarded my analysis to the MFC documentation team (since the problem was originally in an MFC application), who just copied and pasted it into the official documentation.

    The limitation in question existed in Windows 95 and was fixed in Windows NT 4 and Windows 98, so the remarks do not apply to any modern version of Windows.

    What struck me is that the MFC documentation picked up this limitation rather than deferring to the Platform SDK team to document the issue. It means that when the bug is fixed in the platform, the MFC documentation becomes wrong.

    The limitation is not mentioned in the Visual Studio 2008 version of the CFile­Dialog documentation, which suggests either that the Visual Studio folks deferred documenting the issue to the Platform SDK team, or they somehow figured out that the issue no longer applied and removed the offending text.

  • The Old New Thing

    Microspeak: Bug jail

    • 20 Comments

    Bug jail is not a place where bugs are sent as punishment for their crimes. Rather, it's a (virtual) place that developers are sent when they have too many bugs.

    Project management establishes some maximum number of bugs (known as a bug cap) each developer is permitted to have on his or her plate, and developers whose bug count exceeds the specified maximum are placed in bug jail. The precise triggers for bug jail vary from team to team, and it may vary based on the nature of the bug. For example, one trigger might be that any Priority 0 bugs more than 24 hours old will land you in bug jail.

    Once you land in bug jail, you are not allowed to do feature work, be it coding, writing specifications, whatever. The precise triggers for getting out of bug jail also vary from team to team, but one rule might be that you need to get your bug count back down to 50% of the bug jail trigger level before you are allowed to exit.

    Staying on top of your bug count is an important part of the software development process, and the primary motivation behind bug jail is not to punish developers, although I'm sure that's how most developers perceive it. Rather, it serves as an early-warning system to highlight things that are not going smoothly. There may be a bug farm developing in that feature area. Or the team needs to revise its idea of what it means to be "done" with the feature. And it's a signal to project management that they may need to scale back their plans so as not to compromise quality and the ship schedule.

    When everybody understands the reasoning behind bug jail, you avoid lengthy discussions over boundary cases ("What if a developer is about to check in a feature but a low-priority bug comes in that pushes them over the limit?") and avoid discovering that people have been "gaming the system" (for example, by reclassifying bugs as feature requests so they don't count toward the bug cap). Like bug hugging, these sorts of games prevent project management from truly understanding how close the project is to being finished.

  • The Old New Thing

    Why does copying a file to my USB thumb drive say that the parameter is incorrect?

    • 40 Comments

    Consider the following sequence of operations, assuming that F: is a USB thumb drive with plenty of disk space.

    C:\Users\Bob\Downloads> copy readme.txt F:\
            1 file(s) copied.
    C:\Users\Bob\Downloads> copy Update.iso F:\
    The parameter is incorrect.
    

    Why is the second file copy failing?

    The hint is the file extension: *.iso, which suggests that this is a CD or DVD image, and DVD images have the feature that they tend to be really big.

    Like more than 4GB big.

    USB thumb drives tend to be formatted with the FAT32 file system rather than with NTFS. And FAT32 has a maximum file size of 4GB minus one byte.

    The user confirmed that the Update.iso file was larger than 4GB and that the USB thumb drive was formatted as FAT32.

    Mind you, the error message doesn't help at all in identifying that this is what's going on. I don't know where it's coming from, but my guess is that somewhere inside the copy command, it tries to create the destination file and set its file size. Since the file size is out of range for FAT32, the call fails with the error ERROR_INVALID_PARAMETER, and that's what ends up bubbling out to the user.

    But at least now you know what the confusing error message is trying to tell you.

  • The Old New Thing

    How do I access a file without updating its last-access time?

    • 29 Comments

    The first problem with discussing file last-access time is agreeing what you mean by a file's last-access time.

    The file system folks have one definition of the file last-access time, namely the time the file was most recently opened and either read from or written to. This is the value retrieved by functions like Get­File­Attributes­Ex, Get­File­Time, and Find­First­File.

    The problem with this definition is that it doesn't match the intuitive definition of last-access time, which is "the last time I accessed the file," emphasis on the I. In fact, the intuitive definition of access is more specific: It's "the last time I opened, modified, printed, or otherwise performed some sort of purposeful action on the file."

    This discrepancy between the file system definition and the intuitive definition means that a lot of operations trigger a file system access but shouldn't count as an access from the user interface point of view. Here are some examples:

    Whenever some shell extension violates this rule, the shell team gets a bug report from some customer saying, "The last-access time shown in Explorer is wrong. A document which hasn't been accessed in months shows a last-access time of today. After closer investigation, we found that the last-access time updates whenever we insert seemingly-innocuous operation here."

    If you're writing a program which needs to access the file contents but you not want to update the last-access time, you can use the Set­File­Time function with the special value 0xFFFFFFFF in both members of the FILETIME structure passed as the last-access time. This magic value means "do not change the last-access time even though I'm accessing the file."

    BOOL DoNotUpdateLastAccessTime(HANDLE hFile)
    {
     static const FILETIME ftLeaveUnchanged = { 0xFFFFFFFF, 0xFFFFFFFF };
     return SetFileTime(hFile, NULL, &ftLeaveUnchanged, NULL);
    }
    

    As the documentation notes, you have to call this function immediately after opening the file.

    Going back to that linked comment: The reason why viewing the Summary tab causes the last-access time to be updated is that the Summary tab retrieves its information by calling Stg­Open­Storage, and there's no way to tell that function, "Hey, when you open the file in order to see if it has any document properties, do that Do­Not­Update­Last­Access­Time thing so you don't update the last access time."

    Bonus chatter: Starting in Windows Vista, maintaining the last-access time is disabled by default. In practice, this means that the number of bugs related to altering the last-access time accidentally will multiply unchecked, because the mechanism for detecting the error is disabled by default.

  • The Old New Thing

    During process termination, slim reader/writer locks are now also electrified

    • 10 Comments

    Some time ago I mentioned that during process termination, the gates are now electrified: If you attempt to enter a critical section that is owned by a thread that was terminated by an earlier phase of process termination, the entire process is forcibly terminated.

    Windows Vista introduced a new lightweight synchronization pseudo-object known as the slim reader/writer lock. And if you tried to enter a slim reader/writer lock during process termination and found yourself waiting for the current owner to release it, you ended up waiting forever since the current owner was terminated by an earlier phase of process termination. The sentence "As for the home-grown stuff, well, you're on your own" applies here. Even though the slim reader/writer lock functions are exported from kernel32.dll, they don't have any special kernel powers with respect to process termination. From the standpoint of process termination, they may as well be some home-grown synchronization primitive.

    In Windows 7, the kernel folks decided to bring slim reader/writer locks into the fold of objects which are electrified during process termination. Starting in Windows 7, if you attempt to acquire a slim reader/writer lock during process termination, and the lock cannot be immediately acquired, then the process is forcibly terminated.

  • The Old New Thing

    Why can't I move the Program Files directory via the unattend file?

    • 53 Comments

    We saw last time that the unattend file lets you change some Windows configuration settings that cannot be changed after Setup is complete. But one of the things you can't change is the location of the Program Files directory. Many people wish they could relocate their Program Files directory to another drive in order to relieve disk space pressure on the system partition. Why won't Windows let them do this?

    Now that NTFS is mandatory for the system volume (it took only 13 years to get there!), Windows itself can start taking advantage of NTFS features.

    For example, Windows Update can take advantage of transactions: When updates are applied, they are done as part of a transaction. That way, if something horrific happens in the middle of an update, the entire transaction becomes abandoned and you don't get stuck with some Frankenstein configuration.

    Windows Setup takes advantage of hard links. A large percentage of the files installed by Windows are hard-linked to copies in the C:\Windows\WinSxS directory for reasons I do not understand, but the phrase "component store" may be part of it. (This is why asking Explorer for the size of the C:\Windows directory gives a misleading view of the actual amount of disk space occupied by Windows, because Explorer uses a naive algorithm which counts each hard link as a separate file.) Oh, and in Windows 7, the two copies of Notepad are now hard links to each other.

    Ah, but one of the limitations of hard links is that they cannot span volumes. Some of the hard links out of the WinSxS directory point into places like C:\Program Files\Windows NT\Accessories\wordpad.exe, and this in turn requires that the Program Files directory be on the same volume as your Windows directory.

    Sorry for the inconvenience.

  • The Old New Thing

    The unattend file lets you configure Windows while it's installing, and in some cases it's your only chance

    • 32 Comments

    Some Windows settings can only be established as part of the installation process. This is done with a so-called unattend file. (Remember, no matter where you put an advanced setting, somebody will tell you that you are an idiot.) In earlier versions of Windows, the unattend file took the form of an INI file, but Windows Vista hopped aboard the XML bandwagon, and the unattend file format changed to XML. The nice thing about using XML is that you can publish a schema so people can validate their unattend file without having to perform a test install (only to discover twenty minutes later that a typo resulted in an entire section of the unattend file being ignored, say).

    If you spend a lot of time setting up computers, you can use an unattend file to answer all the Setup questions (like "enter your product key") so all you have to do is type "setup /unattend:myconfiguration.xml" and go out to lunch. When you come back, your machine will be installed and ready.

    Here are two of the most popular unattend settings which must be set during installation. (There are a bunch of popular unattend settings for things that can also be changed post-install; for those other settings, the unattend file is not your only chance.)

    Wait, the C:\Program Files directory isn't on the list of directories that can be relocated. There's a reason for that, which we'll look at next time.

  • The Old New Thing

    Beyoncé, the giant metal chicken has a Facebook page

    • 4 Comments

    In my 2011 mid-year link clearance, I linked to the story And that's why you should learn to pick your battles. I thought that was the end of the story, but no, it's the gift that keeps on giving. Beyoncé, the giant metal chicken has a Facebook page, populated with all sorts of crazy things like pictures of Beyoncé's relatives spotted in the wild (some of them knocking on doors or peeking in windows), a No Soliciting sign just for giant metal chickens, and an updated version of the chart of anniversary gifts which lists BIG METAL CHICKEN as the modern 15th anniversary present.

  • The Old New Thing

    Adjusting your commute to avoid being at work quite so much

    • 3 Comments

    Commenter Bernard remarked that he moved from one side of the company campus to the other, and his commute distance (and time) was cut in half.

    That reminds me of a short story from a now-retired colleague of mine. He bicycled to work, and over the years, the gradual expansion of the Microsoft main corporate campus resulted in nearly three quarters of his commute to work taking place on Microsoft property. This bothered him to the point where he changed his route just so he wouldn't be "at work" practically the moment he left his house.

    Bonus chatter: Looks like Michael Kaplan also has one of these unbalanced commutes.

  • The Old New Thing

    Do not access the disk in your IContextMenu handler, no really, don't do it

    • 22 Comments

    We saw some time ago that the number one cause of crashes in Explorer is malware.

    It so happens that the number one cause of hangs in Explorer is disk access from context menu handlers (a special case of the more general principle, you can't open the file until the user tells you to open it).

    That's why I was amused by Memet's claim that "would hit the disk" is not acceptable for me. The feedback I see from customers, either directly from large multinational corporations with 500ms ping times or indirectly from individual users who collectively click Send Report millions of times a day, is that "would hit the disk" ruins a lot of people's days. It may not be acceptable to you, but millions of other people would beg to disagree.

    The Windows team tries very hard to identify unwanted disk accesses in Explorer and get rid of them. We don't get them all, but at least we try. But if the unwanted disk access is coming from a third-party add-on, there isn't much that can be done aside from saying, "Don't do that" and hoping the vendor listens.

    Every so often, a vendor will come back and ask for advice on avoiding disk access in their context menu handler. There's a lot of information packed into that data object that contains information gathered from when the disk was accessed originally. You can just retrieve that cached data instead of going off and hitting the disk again to recalculate it.

    I'm going to use a boring console application and the clipboard rather than building a full IContext­Menu, since the purpose here is to show how to get data from a data object without hitting the disk and not to delve into the details of IContext­Menu implementation.

    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <ole2.h>
    #include <shlobj.h>
    #include <propkey.h>
    #include <tchar.h>
    
    void ProcessDataObject(IDataObject *pdto)
    {
     ... to be written ...
    }
    
    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     if (SUCCEEDED(OleInitialize(NULL))) {
      IDataObject *pdto;
      if (SUCCEEDED(OleGetClipboard(&pdto))) {
       ProcessDataObject(pdto);
       pdto->Release();
      }
      OleUninitialize();
     }
    }
    

    Okay, let's say that we want to check that all the items on the clipboard are files and not directories. The HDROP way of doing this would be to get the path to each of the items in the data object, then call Get­File­Attributes on each one to see if any of them has the FILE_ATTRIBUTE_DIRECTORY flag set. But this hits the disk, which makes baby context menu host sad. Fortunately, the IShell­Item­Array interface provides an easy way to check whether any or all the items in a data object have a particular attribute.

    void ProcessDataObject(IDataObject *pdto)
    {
     IShellItemArray *psia;
     HRESULT hr;
     hr = SHCreateShellItemArrayFromDataObject(pdto,
                                              IID_PPV_ARGS(&psia));
     if (SUCCEEDED(hr)) {
      SFGAOF sfgaoResult;
      hr = psia->GetAttributes(SIATTRIBFLAGS_OR, SFGAO_FOLDER,
                                                     &sfgaoResult);
      if (hr == S_OK) {
       _tprintf(TEXT("Contains a folder\n"));
      } else if (hr == S_FALSE) {
       _tprintf(TEXT("Contains no folders\n"));
      }
      psia->Release();
     }
    }
    

    In this case, we want to see if any item (SI­ATTRIB­FLAGS_OR) in the data object has the SFGAO_FOLDER attribute. The IShell­Item­Array::Get­Attributes method returns S_OK if all of the attributes you requested are present in the result. Since we asked for only one attribute, and since we asked for the result to be the logical or of the individual attributes, this means that it returns S_OK if any item is a folder.

    Okay, fine, but what if the thing you want to know is not expressible as a SFGAO flag? Well, you can dig into each of the individual items. For example, suppose we want to see the size of each item.

    #include <strsafe.h>
    
    void ProcessDroppedObject(IDataObject *pdto)
    {
     IShellItemArray *psia;
     HRESULT hr;
     hr = SHCreateShellItemArrayFromDataObject(pdto,
                                              IID_PPV_ARGS(&psia));
     if (SUCCEEDED(hr)) {
      IEnumShellItems *pesi;
      hr = psia->EnumItems(&pesi);
      if (SUCCEEDED(hr)) {
       IShellItem *psi;
       while (pesi->Next(1, &psi, NULL) == S_OK) {
        IShellItem2 *psi2;
        hr = psi->QueryInterface(IID_PPV_ARGS(&psi2));
        if (SUCCEEDED(hr)) {
         ULONGLONG ullSize;
         hr = psi2->GetUInt64(PKEY_Size, &ullSize);
         if (SUCCEEDED(hr)) {
          _tprintf(TEXT("Item size is %I64u\n"), ullSize);
         }
         psi2->Release();
        }
        psi->Release();
       }
      }
      psia->Release();
     }
    }
    

    I went for IEnum­Shell­Items here, even though a for loop with IShell­Item­Array::Get­Count and IShell­Item­Array::Get­Item­At would have worked, too.

    File system items in data objects cache a bunch of useful pieces of information, such as the last-modified time, file creation time, last-access time, the file size, the file attributes, and the file name (both long and short). Of course, all of these properties are subject to file system support. the shell just takes what's in the WIN32_FIND_DATA; if the values are incorrect (for example, if last-access time tracking is disabled), then the shell is going to cache the incorrect value. But don't say, "Well, if the cache is no good, then I won't use it; I'll just go hit the disk", because if you hit the disk, the file system is going to give you the same incorrect value anyway!

    If you just want to order the combo platter, you can ask for PKEY_Find­Data, and out will come a WIN32_FIND_DATA. This might be the easiest way to convert your old-style context menu that hits the disk into a new-style context menu that doesn't hit the disk: Take your calls to Get­File­Attributes and Find­First­File and convert them into calls into the property system, asking for PKEY_File­Attributes or PKEY_Find­Data.

    Okay, that's the convenient modern way to get information that has been cached in the data object provided by the shell. What if you're an old-school programmer? Then you get to roll up your sleeves and get your hands dirty with the CFSTR_SHELL­ID­LIST clipboard format. (And if your target is Windows XP or earlier, you have to do it this way since the IShell­Item­Array interface was not introduced until Windows Vista.) In fact, the CFSTR_SHELL­ID­LIST clipboard format will get your hands so dirty, I'm writing a helper class to manage it.

    First, go back and familiarize yourself with the CIDA structure.

    // these should look familiar
    #define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
    #define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
    
    void ProcessDataObject(IDataObject *pdto)
    {
     FORMATETC fmte = {
        (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLIDLIST),
        NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
     STGMEDIUM stm = { 0 }; // defend against buggy data object
     HRESULT hr = pdto->GetData(&fmte, &stm);
     if (SUCCEEDED(hr) && stm.hGlobal != NULL) {
      LPIDA pida = (LPIDA)GlobalLock(stm.hGlobal);
      if (pida != NULL) { // defend against buggy data object
       IShellFolder *psfRoot;
       hr = SHBindToObject(NULL, HIDA_GetPIDLFolder(pida), NULL,
                           IID_PPV_ARGS(&psfRoot));
       if (SUCCEEDED(hr)) {
        for (UINT i = 0; i < pida->cidl; i++) {
         IShellFolder2 *psf2;
         PCUITEMID_CHILD pidl;
         hr = SHBindToFolderIDListParent(psfRoot,
                    HIDA_GetPIDLItem(pida, i),
                    IID_PPV_ARGS(&psf2), &pidl);
         if (SUCCEEDED(hr)) {
          VARIANT vt;
          if (SUCCEEDED(psf2->GetDetailsEx(pidl, &PKEY_Size, &vt))) {
           if (SUCCEEDED(VariantChangeType(&vt, &vt, 0, VT_UI8))) {
             _tprintf(TEXT("Item size is %I64u\n"), vt.ullVal);
           }
           VariantClear(&vt);
          }
          psf2->Release();
         }
        }
        psfRoot->Release();
       }
       GlobalUnlock(stm.hGlobal);
      }
      ReleaseStgMedium(&stm);
     }
    }
    

    I warned you it was going to be ugly.

    First, we retrieve the CFSTR_SHELL­ID­LIST clipboard format from the data object. This format takes the form of an HGLOBAL, which needs to be Global­Lock'd like all HGLOBALs returned by IData­Object::Get­Data. You may notice two defensive measures here. First, there is a defense against data objects which return success when they actually failed. To detect this case, we zero out the STG­MEDIUM and make sure they returned something non-NULL in it. The second defensive measure is against data objects which put an invalid HGLOBAL in the STG­MEDIUM. One of the nice things about doing things the IShell­Item­Array way is that the shell default implementation of IShell­Item­Array has all these defensive measures built-in so you don't have to write them yourself.

    Anyway, once we get the CIDA, we bind to the folder portion, walk through the items, and get the size of each item in order to print it. Same story, different words.

    Exercise: Why did we need a separate defensive measure for data objects which returned success but left garbage in the STG­MEDIUM? Why doesn't the Global­Lock test cover that case, too?

Page 122 of 458 (4,573 items) «120121122123124»