Quantcast
Channel: The Old New Thing
Viewing all 1765 articles
Browse latest View live

Why can't I use PSGUID_STORAGE like a GUID?

$
0
0

The stgprop.h header file defines a GUID called PSGUID_STORAGE, but a customer was having trouble using it.

    GUID guid;
    ...
    // This generates a strange compiler error
    if (IsEqualGUID(guid, PSGUID_STORAGE)) { ... }

The strange compiler error the customer referred to is the following:

test.cpp(136) : error C2143: syntax error : missing ')' before '{'
test.cpp(136) : error C2059: syntax error : ')'
test.cpp(136) : error C2143: syntax error : missing ';' before '{'
test.cpp(136) : error C2059: syntax error : '{'
test.cpp(136) : error C2059: syntax error : ')'
test.cpp(137) : error C2059: syntax error : '}'
test.cpp(137) : error C2143: syntax error : missing ';' before '}'
test.cpp(137) : error C2059: syntax error : '}'

"I don't see what the compiler is complaining about. The parentheses appear to be properly matched before the left brace."

Remember, what you see is not necessarily what the compiler sees. Let's take another look at this mysterious GUID:

#define PSGUID_STORAGE  { 0xb725f130,           \
                          0x47ef, 0x101a,       \
                          { 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac } }

Well there's your problem. After the preprocessor does its substitution, the line becomes

    if (IsEqualGUID(guid, { 0xb725f130,
              0x47ef, 0x101a,
              { 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac } })) { ... }

and that's not legal C/C++. (Though with a little tweaking, you can get GCC to accept it.) The PSGUID_STORAGE symbols is intended to be used as an initializer:

const GUID StorageGuid = PSGUID_STORAGE;

"How did you know that?"

I didn't, but I went to the effort of looking at the definition in the header file and figuring it out from inspection.

Why is it defined this way instead of

DEFINE_GUID(PSGUID_STORAGE, 0xb725f130, 0x47ef,
        0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac);

?

Because this GUID is used as the FMTID of a PROPERTY­KEY. The PROPERTY­KEY structure looks like this:

typedef struct {
  GUID  fmtid;
  DWORD pid;
} PROPERTYKEY;

The intended usage is evidently

const PROPERTYKEY
PKEY_STORAGE_DIRECTORY = { PSGUID_STORAGE, PID_STG_DIRECTORY };

Since the C language does not permit global variables to be initialized from other global variables (or at least it didn't at the time PROPERTY­KEYs were defined; who knows what crazy features will show up in C1X), PSGUID_STORAGE needs to be a macro which expands to an initializer rather than being a global variable.

Today's question was really just settling the prerequisites for tomorrow's topic. Stay tuned.


How can I get information about the items in the Recycle Bin?

$
0
0

For some reason, a lot of people are interested in programmatic access to the contents of the Recycle Bin. They never explain why they care, so it's possible that they are looking at their problem the wrong way.

For example, one reason for asking, "How do I purge an item from the Recycle Bin given a path?" is that some operation in their program results in the files going into the Recycle Bin and they want them to be deleted entirely. The correct solution is to clear the FOF_ALLOW­UNDO flag when deleting the items in the first place. Moving to the Recycle Bin and then purging is the wrong solution because your search-and-destroy mission may purge more items than just the ones your program put there.

The Recycle Bin is somewhat strange in that it can have multiple items with the same name. Create a text file called TEST.TXT on your desktop, then delete it into the Recycle Bin. Create another text file called TEST.TXT on your desktop, then delete it into the Recycle Bin. Now open your Recycle Bin. Hey look, you have two TEST.TXT files with the same path!

Now look at that original problem: Suppose the program, as part of some operation, moves the file TEST.TXT from the desktop to the Recycle Bin, and then the second half of the program goes into the Recycle Bin, finds TEST.TXT and purges it. Well, there are actually three copies of TEST.TXT in the Recycle Bin, and only one of them is the one you wanted to purge.

Okay, I got kind of sidetracked there. Back to the issue of getting information about the items in the Recycle Bin.

The Recycle Bin is a shell folder, and the way to enumerate the contents of a shell folder is to bind to it and enumerate its contents. The low-level interface to the shell namespace is via IShell­Folder. There is an easier-to-use medium-level interface based on IShell­Item, and there's a high-level interface based on Folder designed for scripting.

I'll start with the low-level interface. As usual, the program starts with a bunch of header files.

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <propkey.h>

The Bind­To­Csidl function binds to a folder specified by a CSIDL. The modern way to do this is via KNOWN­FOLDER, but just to keep you old fogeys happy, I'm doing things the classic way since you refuse to upgrade from Windows XP. (We'll look at the modern way later.)

HRESULT BindToCsidl(int csidl, REFIID riid, void **ppv)
{
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  IShellFolder *psfDesktop;
  hr = SHGetDesktopFolder(&psfDesktop);
  if (SUCCEEDED(hr)) {
   if (pidl->mkid.cb) {
    hr = psfDesktop->BindToObject(pidl, NULL, riid, ppv);
   } else {
    hr = psfDesktop->QueryInterface(riid, ppv);
   }
   psfDesktop->Release();
  }
  CoTaskMemFree(pidl);
 }
 return hr;
}

The subtlety here is in the test for pidl->mkid.cb. The IShell­Folder::Bind­To­Object method is for binding to child objects (or grandchildren or deeper descendants). If the object you want is the desktop itself, then you can't use IShell­Folder::Bind­To­Object since the desktop is not a child of itself. In fact, if the object you want is the desktop itself, then you already have the desktop, so we just Query­Interface for it. It's an annoying special case which usually lurks in your code until somebody tries something like "Save file to desktop" or changes the location of a special folder to the desktop, and then boom you trip over the fact that the desktop is not a child of itself. (See further discussion below.)

Another helper function prints the display name of a shell namespace item. There isn't much interesting here either.

void PrintDisplayName(IShellFolder *psf,
    PCUITEMID_CHILD pidl, SHGDNF uFlags, PCTSTR pszLabel)
{
 STRRET sr;
 HRESULT hr = psf->GetDisplayNameOf(pidl, uFlags, &sr);
 if (SUCCEEDED(hr)) {
  PTSTR pszName;
  hr = StrRetToStr(&sr, pidl, &pszName);
  if (SUCCEEDED(hr)) {
   _tprintf(TEXT("%s = %s\n"), pszLabel, pszName);
   CoTaskMemFree(pszName);
  }
 }
}

Our last helper function retrieves a property from the shell namespace and prints it. (Obviously, if we wanted to do something other than print it, we could coerce the type to something other than VT_BSTR.)

void PrintDetail(IShellFolder2 *psf, PCUITEMID_CHILD pidl,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 VARIANT vt;
 HRESULT hr = psf->GetDetailsEx(pidl, pscid, &vt);
 if (SUCCEEDED(hr)) {
  hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
  if (SUCCEEDED(hr)) {
   _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
  }
  VariantClear(&vt);
 }
}

Okay, now we can get down to business. The properties we will display from each item in the Recycle Bin are the item name and path, the original location (before the item was deleted), the date the item was deleted, and the size of the item.

Getting the name and path are done with various combinations of flags to IShell­Folder::Get­Display­Name­Of, whereas getting the other properties involve talking to the shell property system. (My colleague Ben Karas covers the shell property system on his blog.) The SHCOLUMN­ID documentation says that the displaced property set applies to items which have been moved to the Recycle Bin, so we can define those column IDs based on the values provided in shlguid.h:

const SHCOLUMNID SCID_OriginalLocation =
   { PSGUID_DISPLACED, PID_DISPLACED_FROM };
const SHCOLUMNID SCID_DateDeleted =
   { PSGUID_DISPLACED, PID_DISPLACED_DATE };

The other property we want is System.Size, which the documentation says is defined as PKEY_Size by the propkey.h header file.

Okay, let's roll!

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellFolder2 *psfRecycleBin;
  hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumIDList *peidl;
   hr = psfRecycleBin->EnumObjects(NULL,
     SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
   if (hr == S_OK) {
    PITEMID_CHILD pidlItem;
    while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
     _tprintf(TEXT("------------------\n"));

     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_INFOLDER, TEXT("InFolder"));
     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_NORMAL, TEXT("Normal"));
     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_FORPARSING, TEXT("ForParsing"));

     PrintDetail(psfRecycleBin, pidlItem,
                 &SCID_OriginalLocation, TEXT("Original Location"));
     PrintDetail(psfRecycleBin, pidlItem,
                 &SCID_DateDeleted, TEXT("Date deleted"));
     PrintDetail(psfRecycleBin, pidlItem,
                 &PKEY_Size, TEXT("Size"));

     CoTaskMemFree(pidlItem);
    }
   }
   psfRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

The only tricky part is the test for whether the call to IShell­Folder::Enum­Objects succeeded, highlighted above. According to the rules for IShell­Folder::Enum­Objects, the method is allowed to return S_FALSE to indicate that there are no children, in which case it sets peidl to NULL.

If you are willing to call functions new to Windows Vista, you can simplify the Bind­To­Csidl function by using the helper function SHBind­To­Object. This does the work of getting the desktop folder and handling the desktop special case.

HRESULT BindToCsidl(int csidl, REFIID riid, void **ppv)
{
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHBindToObject(NULL, pidl, NULL, riid, ppv);
  CoTaskMemFree(pidl);
 }
 return hr;
}

But at this point, I'm starting to steal from the topic I scheduled for next time, namely modernizing this program to take advantage of some new helper functions and interfaces. We'll continue next time.

Modernizing our simple program that retrieves information about the items in the Recycle Bin

$
0
0

Last time, we wrote a simple program to print various properties of the items in the Recycle Bin, and we did so in the classical style, using item ID lists and IShell­Folders. One thing you may have noticed is that a lot of functions take the combination of an IShell­Folder and a PCUITEMID_CHILD. In the shell namespace, operations on items usually happen by means of the pair (folder, child), and one of the common mistakes made by beginners is failing to keep track of the pairing and passing child pidls to the wrong parent folder.

Even if you're not a beginner and are good at keeping track of which child pidls correspond to which parent folders, it's still extra work you have to do, and it means that a lot of functions take two parameters in order to describe one thing.

Enter IShell­Item.

The IShell­Item encapsulates the pair (folder, child). This solves two problems:

  1. You only have to pass one thing around (the IShell­Item) instead of two (the IShell­Folder and the PCUITEMID_CHILD).
  2. By keeping track of the two items as a single unit, it reduces the risk that you'll accidentally use a child pidl with the wrong parent folder.

Another complexity of the classic shell interface is that there are a bunch of ways of obtaining COM objects from a shell folder:

  • IShell­Folder::Bind­To­Object
  • IShell­Folder::Bind­To­Storage
  • IShell­Folder::Create­View­Object
  • IShell­Folder::Get­UI­Object­Of
  • IUnknown::Query­Interface (thanks to the desktop special case we saw last time).

The IShell­Item::Bind­To­Handler interface hides these special-cases by dealing with them under the covers so you don't have to. You just call IShell­Item::Bind­To­Handler and it figures out where to get the object and what weird special cases apply. (It also takes care of the weird S_FALSE return value from IShell­Folder::Enum­Objects.)

And then there's the annoyance of IShell­Folder::Get­Display­Name­Of using the kooky STRRET structure. The IShell­Item::Get­Display­Name function encapsulates that away for you by doing the work to convert that STRRET into a boring string pointer.

First up in modernizing our sample program is to change Bind­To­Csidl to return a shell item instead of a shell folder.

HRESULT BindToCsidlItem(int csidl, IShellItem ** ppsi)
{
 *ppsi = NULL;
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
  CoTaskMemFree(pidl);
 }
 return hr;
}

But wait, since we're modernizing, we may as well upgrade to SHGet­Known­Folder­ID­List:

HRESULT BindToKnownFolderItem(REFKNOWNFOLDER rfid, IShellItem ** ppsi)
{
 *ppsi = NULL;
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetKnownFolderIDList(rfid, 0, NULL, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
  CoTaskMemFree(pidl);
 }
 return hr;
}

Hey wait, there's a function for this already in Windows 7! It's called SHGet­Known­Folder­Item. Yay, now we can delete the function entirely.

Next, we convert Print­Display­Name to use IShell­Item and the item-based display name flags SIGDN.

void PrintDisplayName(IShellItem *psi, SIGDN sigdn, PCTSTR pszLabel)
{
 LPWSTR pszName;
 HRESULT hr = psi->GetDisplayName(sigdn, &pszName);
 if (SUCCEEDED(hr)) {
  _tprintf(TEXT("%s = %ws\n"), pszLabel, pszName);
  CoTaskMemFree(pszName);
 }
}

And then we convert Print­Detail to use IShell­Item. Oh wait, now we've hit a snag: The IShell­Item interface doesn't have a helper method that wraps IShell­Folder2::Get­Details­Ex. Fortunately, there is a way to ask IShell­Item to regurgitate the IShell­Folder and PITEMID_CHILD that it is wrapping: You use the IParent­And­Item::Get­Parent­And­Item method.

void PrintDetail(IShellItem *psi,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 IParentAndItem *ppni;
 HRESULT hr = psi->QueryInterface(IID_PPV_ARGS(&ppni));
 if (SUCCEEDED(hr)) {
  IShellFolder *psf;
  PITEMID_CHILD pidl;
  hr = ppni->GetParentAndItem(NULL, &psf, &pidl);
  if (SUCCEEDED(hr)) {
   VARIANT vt;
   hr = psf->GetDetailsEx(pidl, pscid, &vt);
   if (SUCCEEDED(hr)) {
    hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
    if (SUCCEEDED(hr)) {
     _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
    }
    VariantClear(&vt);
   }
   psf->Release();
   CoTaskMemFree(pidl);
  }
 }
}

Wow, it looks like we lost ground there. Ah, but Windows Vista extends IShell­Item with the IShell­Item2 interface, and that has a bunch of new methods for retrieving properties.

void PrintDetail(IShellItem2 *psi,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
  PROPVARIANT vt;
  HRESULT hr = psi->GetProperty(*pscid, &vt);
  if (SUCCEEDED(hr)) {
   hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
   if (SUCCEEDED(hr)) {
    _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
   }
   PropVariantClear(&vt);
  }
 }
}

But wait, there's more. There's a special accessor just for retrieving properties as strings!

void PrintDetail(IShellItem2 *psi2,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 LPWSTR pszValue;
 HRESULT hr = psi2->GetString(*pscid, &pszValue);
 if (SUCCEEDED(hr)) {
  _tprintf(TEXT("%s: %ws\n"), pszLabel, pszValue);
  CoTaskMemFree(pszValue);
 }
}

Okay, that's more like it. Now let's update the main program.

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellItem *psiRecycleBin;
  hr = SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
                            NULL, IID_PPV_ARGS(&psiRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumShellItems *pesi;
   hr = psiRecycleBin->BindToHandler(NULL, BHID_EnumItems,
                                     IID_PPV_ARGS(&pesi));
   if (hr == S_OK) {
    IShellItem *psi;
    while (pesi->Next(1, &psi, NULL) == S_OK) {
     IShellItem2 *psi2;
     if (SUCCEEDED(psi->QueryInterface(IID_PPV_ARGS(&psi2)))) {
      _tprintf(TEXT("------------------\n"));

      PrintDisplayName(psi2, SIGDN_PARENTRELATIVE,
                             TEXT("ParentRelative"));
      PrintDisplayName(psi2, SIGDN_NORMALDISPLAY, TEXT("Normal"));
      PrintDisplayName(psi2, SIGDN_FILESYSPATH, TEXT("FileSys"));

      PrintDetail(psi2, &SCID_OriginalLocation, TEXT("Original Location"));
      PrintDetail(psi2, &SCID_DateDeleted, TEXT("Date deleted"));
      PrintDetail(psi2, &PKEY_Size, TEXT("Size"));
      psi2->Release();
     }
     psi->Release();
    }
   }
   psiRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

Okay, so now we know how to enumerate the contents of the Recycle Bin and obtain properties of the items in it. How do we purge or restore items? We'll look at that next time.

Invoking commands on items in the Recycle Bin

$
0
0

Once you've found the items you want in the Recycle Bin, you may want to perform some operation on them. This brings us back to our old friend, IContextMenu. At this point, you're just snapping two blocks together. You have one block called Retrieving properties from items in the Recycle Bin and you have another block called Invoking verbs on items.

For the first block, let's assume you've written a function called WantToRestoreThisItem which studies the properties of a Recycle Bin item and determines whether you want to restore it. I leave this for you to implement, since I don't know what your criteria are. Maybe you want to restore files only if they were deleted from a particular directory. Maybe you want to restore files that were deleted while you were drunk. (This assumes you have some other computer program that tracks when you're drunk.)¹ Whatever. It's your function.

For the second block, we have a helper function which should look awfully familiar.

void InvokeVerb(IContextMenu *pcm, PCSTR pszVerb)
{
 HMENU hmenu = CreatePopupMenu();
 if (hmenu) {
  HRESULT hr = pcm->QueryContextMenu(hmenu, 0, 1, 0x7FFF, CMF_NORMAL);
  if(SUCCEEDED(hr)) {
   CMINVOKECOMMANDINFO info = { 0 };
   info.cbSize = sizeof(info);
   info.lpVerb = pszVerb;
   pcm->InvokeCommand(&info);
  }
  DestroyMenu(hmenu);
 }
}

And now we snap the two blocks together.

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellItem *psiRecycleBin;
  hr = SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
                            NULL, IID_PPV_ARGS(&psiRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumShellItems *pesi;
   hr = psiRecycleBin->BindToHandler(NULL, BHID_EnumItems,
                                     IID_PPV_ARGS(&pesi));
   if (hr == S_OK) {
    IShellItem *psi;
    while (pesi->Next(1, &psi, NULL) == S_OK) {
     if (WantToRestoreThisItem(psi)) {
      IContextMenu *pcm;
      hr = psi->BindToHandler(NULL, BHID_SFUIObject,
                              IID_PPV_ARGS(&pcm));
      if (SUCCEEDED(hr)) {
       InvokeVerb(pcm, "undelete");
       pcm->Release();
      }
     }
     psi->Release();
    }
   }
   psiRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

One annoyance of the Recycle Bin is that, at least up until Windows 7, it ignores the CMIC_MASK_FLAG_NO_UI flag. It always displays a confirmation dialog if something dangerous is about to happen (like overwriting an existing file). To mitigate this problem, we can at least reduce the number of confirmations from one-per-file to just one by batching up all the objects we want to operate on into a single context menu. For this, it's easier to go back to the classical version of the program.

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellFolder2 *psfRecycleBin;
  hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumIDList *peidl;
   hr = psfRecycleBin->EnumObjects(NULL,
     SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
   if (hr == S_OK) {
    // in a real program you wouldn't hard-code a fixed limit
    PITEMID_CHILD rgpidlItems[100];
    UINT cpidlItems = 0;
    PITEMID_CHILD pidlItem;
    while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
     if (WantToRestoreThisItem(psfRecycleBin, pidlItem) &&
         cpidlItems < ARRAYSIZE(rgpidlItems)) {
      rgpidlItems[cpidlItems++] = pidlItem;
     } else {
      CoTaskMemFree(pidlItem);
     }
    }
    // restore the items we collected
    if (cpidlItems) {
     IContextMenu *pcm;
     hr = psfRecycleBin->GetUIObjectOf(NULL, cpidlItems,
                     (PCUITEMID_CHILD_ARRAY)rgpidlItems,
                     IID_IContextMenu, NULL, (void**)&pcm);
     if (SUCCEEDED(hr)) {
      InvokeVerb(pcm, "undelete");
      pcm->Release();
     }
     for (UINT i = 0; i < cpidlItems; i++) {
      CoTaskMemFree(rgpidlItems[i]);
     }
    }
   }
   psfRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

In the course of the enumeration, we save the ITEMIDLISTs of all the items we want to restore, then create one giant context menu for all of them. This is the programmatic equivalent of multi-selecting the items from the Recycle Bin and then right-clicking. We then invoke the undelete verb on the entire group.

Okay, so now suppose you want to restore the files, but instead of restoring them to their original locations, you want to restore them to a special folder. Like, say, C:\Files I deleted while I was drunk.¹ No problem. We just need a different block to snap into: The drag/drop block.

void DropOnRestoreFolder(IDataObject *pdto)
{
 IDropTarget *pdt;
 if (SUCCEEDED(GetUIObjectOfFile(NULL,
        L"C:\\Files I deleted while I was drunk",
        IID_PPV_ARGS(&pdt)))) {
  POINTL pt = { 0, 0 };
  DWORD dwEffect = DROPEFFECT_MOVE;
  if (SUCCEEDED(pdt->DragEnter(pdto, MK_LBUTTON,
                               pt, &dwEffect))) {
   dwEffect &= DROPEFFECT_MOVE;
   if (dwEffect) {
    pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
   } else {
    pdt->DragLeave();
   }
  }
  pdt->Release();
 }
}

And now it's just a matter of snapping out the undelete block and snapping in the drag/drop block.

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellFolder2 *psfRecycleBin;
  hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumIDList *peidl;
   hr = psfRecycleBin->EnumObjects(NULL,
     SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
   if (hr == S_OK) {
    // in a real program you wouldn't hard-code a fixed limit
    PITEMID_CHILD rgpidlItems[100];
    UINT cpidlItems = 0;
    PITEMID_CHILD pidlItem;
    while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
     if (WantToRestoreThisItem(psfRecycleBin, pidlItem) &&
         cpidlItems < ARRAYSIZE(rgpidlItems)) {
      rgpidlItems[cpidlItems++] = pidlItem;
     } else {
      CoTaskMemFree(pidlItem);
     }
    }
    // restore the items we collected
    if (cpidlItems) {
     IDataObject *pdto;
     hr = psfRecycleBin->GetUIObjectOf(NULL, cpidlItems,
                     (PCUITEMID_CHILD_ARRAY)rgpidlItems,
                     IID_IDataObject, NULL, (void**)&pdto);
     if (SUCCEEDED(hr)) {
      DropOnRestoreFolder(pdto);
      pdto->Release();
     }
     for (UINT i = 0; i < cpidlItems; i++) {
      CoTaskMemFree(rgpidlItems[i]);
     }
    }
   }
   psfRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

Footnotes

¹ If being drunk isn't your thing, then substitute some other form of impaired judgment.

What's the story with the parameters to the WM_INPUT_DEVICE_CHANGE message?

$
0
0

A customer found these strange macros in winuser.h:

#if (_WIN32_WINNT >= 0x0601)
#define GET_DEVICE_CHANGE_WPARAM(wParam)  (LOWORD(wParam))
#elif (_WIN32_WINNT >= 0x0501)
#define GET_DEVICE_CHANGE_LPARAM(lParam)  (LOWORD(lParam))
#endif /* (_WIN32_WINNT >= 0x0601) */

According to the documentation for the WM_INPUT_DEVICE_CHANGE message, the wParam is the operation code and the lParam is a handle to the device that changed. Given that definition, the correct macro would be GET_DEVICE_CHANGE_WPARAM. What's up with the bogus GET_DEVICE_CHANGE_LPARAM macro?

The macro was incorrectly defined in Windows Vista. In the Windows 7 version of the Platform SDK, the correct macro was added, but in order to avoid introducing a breaking change to existing code, the old broken macro remains in place in order to retain bug-for-bug compatibility with existing code.

Even though the macro didn't work, there is a good possibility that code exists which relied on it anyway. For example, people may have read the documentation, read the macro, realized that the macro was wrong, and worked around the bug like this:

case WM_INPUT_DEVICE_CHANGE:
 return OnInputDeviceChange(GET_DEVICE_CHANGE_LPARAM(wParam),
                            (HANDLE)lParam);

To avoid breaking this code, the old broken definition remains in the header file. But at least it's defined only if you say that you want the Windows Vista version of the header file, so at least people won't use the bad macro going forward.

Thanks for letting me know what my ideal career and company are

$
0
0

When it's performance review season, all of a sudden you start getting mail about career management. What a coincidence. There are a variety of career management tools available, some mandatory, some optional. I gave one of the optional ones a shot, since it claimed to help me "manage my career and professional development", and as I already noted, I appear to have been promoted by mistake all these years, so maybe I should figure out how to get promoted for real.

This particular tool sends me to the Web site of an external company that was contracted by Microsoft to provide career guidance services. I went through the sign-up process and answered what seemed like a bazillion questions. You know you're in trouble when you're getting tired and the progress bar says that you're currently filling out questionnaire number 1 (of 3) and you're on page 4 (of 19).

Anyway, I make it through to the end of all the questions and the site offers suggestions as to what my ideal career would be, based on the personality characteristics I demonstrated in the questionnaire.

It says that I would do well working in the field of information technology and that the best company for me is one with well-established processes and procedures, where decisions are guided by practicality and pragmatism, a characteristic common to companies that are market leaders.

I don't know whether I should be disappointed that I didn't learn anything new or whether I should be relieved that I'm not missing out on my secret calling to be a topiarist or something.

(And I guess that having me work at Microsoft plays right into Google's hands, so it's a win-win.)

What happened to that suspicious-looking guy hanging around the entrance?

$
0
0

One of the fun parts of attending a conference is swapping stories with other professionals. Today's story is in honor of Global Security Week. (And retroactively in honor of the upcoming //build conference.)

One of the attendees (let's call him Bob) shared with me a story of the time they had to make a change in one of their data centers. This particular change required physical presence at the facility, and to minimize impact on customers, the change was made at night (presumably because that's when demand was lowest).

When Bob arrived at the data center, he walked past a suspicious-looking guy on his way to the door. Bob said hello to the security guard and swiped his access card through the card reader.

Access denied.

He tried again. Still no luck.

He asked the security guard, "Hi, I'm supposed to be here to do a system upgrade, but my access card isn't working. Maybe they just put my name on a list instead of reprogramming the access system?"

The security guard said, "Let's go down to the main office and find out."

The two of them walked down to the main security office, and after some checking, everything got straightened out, and Bob and the security guard headed back to the entrance. As they left, the security guard asked the person at the main security desk, "Oh, there's a suspicious-looking guy hanging around the entrance. Did you see him?"

The person at the main security desk said, "Yeah, he's hiding in the bushes."

Bob (and I) found it interesting, amusing, and reassuring that the security guards were all fully aware of the suspicious guy and had been keeping tabs on him the whole time.

Why is the registry a hierarchical database instead of a relational one?

$
0
0

Commenter ton asks why the registry was defined as a hierarchical database instead of a relational database.

Heck, it's not even a hierarchical database!

The original registry was just a dictionary; i.e., a list of name/value pairs, accessed by name. In other words, it was a flat database.

.txt txtfile
txtfile Text Document
txtfile\DefaultIcon notepad.exe,1
txtfile\shell open
txtfile\shell\open\command notepad %1

If you turned your head sideways and treated the backslashes as node separators, you could sort of trick yourself into believing that this resulted in something vaguely approximating a hierarchical database, and a really lame one at that (since each node held only one piece of data).

When you choose your data structures, you necessarily are guided by the intended use pattern and the engineering constraints. One important engineering constraint was that you have to minimize memory consumption. All of the registry code fit in 16KB of memory. (Recall that Windows 3.1 had to run on machines with only 1MB of memory.)

Okay, what is the usage pattern of the registry? As originally designed, the registry was for recording information about file types. We have the file types themselves (txtfile), properties about those file types (DefaultIcon), verbs associated with those file types (open), and verb implementations (command or ddeexec). Some verb implementations are simple (command involves just a single string describing the command line); others are complex (ddeexec requires the execute string, the application, and the topic, plus an optional alternate execute string).

  • Given a file type and a property, retrieve the value of that property.
  • Given a file type and a verb, retrieve information about how to perform that verb.
  • The set of properties can be extended.
  • The set of property schemata can be extended.
  • The set of verbs can be extended.
  • The set of verb implementations can be extended.
Since the properties and verb implementations can be extended, you can't come up with a single schema that covers everything. For example, over the years, new file type properties have been added such as ContentType, OpenWithList, and ShellNew. The first one is a simple string; the second is a list of strings, and the third is a complex key with multiple variants. Meanwhile, additional verb implementations have been added, such as DropTarget.

Given the heterogeneity of the data the registry needs to keep track of, imposing some sort of uniform schema is doomed to failure.

"But you can just update the schemata each time the registration is extended."

That creates its own problems. For example, to support roaming user profiles, you need a single registry hive to work on multiple versions of the operating system. If version N+1 adds a new schema, but then the profile roams to a machine running version N, then that registry hive will be interpreted as corrupted since it contains data that matches no valid schema.

"Well, then include the schemata with the roaming profile so that when the older operating system sees the hive, it also sees the updated schemata."

This is trickier than it sounds, because when the profile roams to the newer operating system, you presumably want the schemata to be upgraded and written back into the user profile. It also assumes that the versioning of the schemata is strictly linear. (What if you roam a user profile from a Windows XP machine to a Server 2003 machine? Neither is a descendant of the other.)

But what kills this proposal is that it makes it impossible for a program to "pre-register" properties for a future version of the operating system. Suppose a new schema is added in version N+1, like, say, the IDropTarget verb implementation. You write a program that you want to run on version N as well as on version N+1. If your installer tries to register the version N+1 information, it will fail since there is no schema for it. But that means that when the user upgrades to version N+1, they don't get the benefit of the version N+1 feature. In order to get the version N+1 feature to work, they have to reinstall the program so the installer says, "Oh, now I can register the version +1 information."

"Well, then allow applications to install a new schema whenever they need to."

In other words, make it a total free-for-all. In which case, why do you need a schema at all? Just leave it as an unregulated collection of name/value pairs governed by convention rather than rigid rules, as long as the code which writes the information and the code which reads it agree on the format of the information and where to look for it.

Hey, wow, that's what the registry already is!

And besides, if you told somebody, "Hi, yeah, in order to support looking up four pieces of information about file types, Windows 3.1 comes with a copy of SQL Server," they would think you were insane. That's like using a bazooka to kill a mosquito.

What are you planning on doing with this relational database anyway? Are you thinking of doing an INNER JOIN on the registry? (Besides, the registry is already being abused enough already. Imagine if it were a SQL server: Everybody would store all their data in it!)

ton explains one way applications could use this advanced functionality:

An application would have a table or group of tables in relational style registry. A group of settings would be a row. A single setting would be a column. Is it starting to become clearer now how SQL like statements could now be used to constrain what gets deleted and added? How good is your understanding of SQL and DBMS?

You know what most application authors would say? They would say "Are you mad? You're saying that I need to create a table with one column for each setting? And this table would have a single row (since I have only one application)? All this just so I can save my window position? Screw it, I'm going back to INI files." What'll happen in practice is that everybody will create a table with two columns, a string called name and a blob called value. Now we've come full circle: We have our flat database again.

And how would they make sure the name of their table doesn't collide with the name of a table created by another application? Probably by encoding the company name and application name into the name of the table, according to some agreed-upon convention. Like say, the Settings table used by the LitSoft program written by LitWare would be called LitWare_LitSoft_Settings. So querying a value from this table would go something like

SELECT value FROM PerUser.LitWare_LitSoft_Settings
    WHERE name = "WindowPosition"

Hey, this looks an awful lot like

Registry.CurrentUser.OpenSubKey(@"LitWare\LitSoft\Settings")
        .GetValue("WindowPosition");

One of ton's arguments for using a hierarchical database is that it permits enforcement of referential integrity. But I would argue that in the general case, you don't want strict enforcement of referential integrity. Suppose you uninstall a program. The uninstaller tries to delete the program registration, but that registration is being referenced by foreign keys in other tables. These references were not created by the application itself; perhaps the shell common dialog created them as part of its internal bookkeeping. If the registry blocked the deletion, then the uninstall would fail. "Cannot uninstall application because there's still a reference to it somewhere." And that reference might be in Bob's user profile, from that time Bob said, "Hey can I log onto your machine quickly? I need to look up something." Bob is unlikely to come back to your machine any time soon, so his user profile is just going to sit there holding a reference to that application you want to uninstall for an awfully long time. "Hi, Bob, can you come by my office? I need you to log on so I can uninstall an app."

So let's assume it goes the other way: The registry automatically deletes orphaned foreign key rows. (And for hives that are not currently available, it just remembers that those foreign key rows should be deleted the next time they are loaded. Nevermind that that list of "foreign key rows that should be deleted the next time Bob logs on" is going to get pretty long.)

Now suppose you're uninstalling a program not because you want to get rid of it, but because you're doing an uninstall/reinstall troubleshooting step. You uninstall the program, all the orphaned foreign key rows are automatically deleted, then you reinstall the program. Those orphaned foreign key rows are not undeleted; they remain deleted. Result: You lost some settings. This is the reason why you don't clean up per-user data when uninstalling programs.

Enforcing referential integrity also means that you can't create anticipatory references. One example of this was given earlier, where you register something on version N even though the feature doesn't get activated until the user upgrades to version N+1. More generally, Program X may want to create a reference to Program Y at installation, even if program Y isn't installed yet. (For example, X is a Web browser and Y is a popular plug-in.) The Program Y features remain dormant, because the attempt by Program X to access Program Y will fail, but once the user installs Program Y, then the Program Y features are magically "turned on" in Program X.

Consider, as an even more specific example, the "kill bit" database. There, the goal isn't to "turn on" features of Program Y but to turn them off. Imagine if referential integrity were enforced: You couldn't kill an ActiveX control until after it was installed!


Why doesn't the Disk Management snap-in incorporate S.M.A.R.T. data?

$
0
0

My article a while back on Why the Disk Management snap-in reports my volume as Healthy when the drive is dying gave the low-level explanation of why the Disk Management snap-in does not incorporate SMART information: because the Disk Management snap-in is concerned with volume partitioning. DWalker59 noted that the use of the word "Healthy" carries more meaning than the authors of the snap-in intended. The authors of the snap-in assumed that everybody knew what the Disk Management snap-in was for, and therefore everybody know that the word "Healthy" applied to the state of the file system.

I never said that this was a good situation, and commenter Dog interpreted that since I didn't say whether this was a good situation or a bad situation, I must be saying that it's a good situation. Actually, since I didn't say whether this was a good situation or a bad situation, this means that I'm not saying whether this is a good situation or a bad situation. The article was posted in the Tips/Support category, which is about helping you cope with the frustrations of using Windows, not about passing value judgements on what is good or bad. The point was not to say what is good and what is bad, but merely to say what is.

Dog thinks that the blog would be far more interesting if I shared my opinion on things. Actually, I try not to share my opinion on things, because the Web site isn't about opinionating on Windows; it's about practical programming on Windows. Practicality means that you have to set aside whether something is good or bad, because it's there and you have to deal with it regardless. If you want opinionated writing, check out Robert Scoble or Michael Kaplan. Dog also assumes that Microsoft's PR department has told me not to opinionate on things. In fact, they haven't told me anything one way or the other (yet, and I hope it stays that way).

(I found it interesting that Dog claims that "the act of reporting on [something] gives the appearance of support unless otherwise stated." I wonder if people who cover armed conflicts have to add an explicit statement along the lines of "killing is bad" so Dog won't think they support people shooting at each other.)

From a historical standpoint, the situation is a bit more understandable. After all, the Disk Management snap-in was written long before support for S.M.A.R.T. information showed up in Windows Vista. You can't fault the original authors of the Disk Management snap-in for not taking into account data which didn't exist yet.

As for why the Disk Management snap-in didn't incorporate this information when it became available, this assumes that there were resources available to do the work. Disk Management is a very old snap-in that hasn't changed much since it was first written. My suspicion is that maintenance of the Disk Management snap-in is assigned to a group which has as its primary goal some other part of the system; they were just give Disk Management because it has to belong to somebody. Consequently, that group has very little incentive to make any changes to Disk Management at all, and certainly has very little incentive to add features to it.

Why waste your money on the car when it's the sound system you care about?

$
0
0

There is apparently a subculture of people who decide to economize on the car part of the "loud stereo in car" formula (since they really don't care about the car—it's all about the music) and put their loud stereo on the back of a bicycle instead.

This quotation from the article caught my attention:

"People say, 'It's the next best thing to having a system in a car.' But it's better because you don't even have to roll down the windows."

I had been unsure what to think about people who drive down the street with their stereos blaring. Are they audiophiles who prefer their music loud? Or are they jerks who like to annoy other people with loud music? That quotation sort of settles it.

Throwing garbage on the sidewalk: The sad history of the rundll32 program

$
0
0

During the development of Windows Vista, the application comaptibility team traced a bunch of issues back to people corrupting the stack by using the rundll32 program to call functions that were not designed to be called by rundll32.

The problems were often subtle. For example, a batch file which used rundll32 incorrectly ended up hanging because the rundll32 process never returned. The misaligned stack resulted in registers being restored from the stack incorrectly, and then the cleanup code inside rundll32 ends up getting confused and wedging itself. The programs got away with it on previous versions of Windows by sheer luck. The version of the compiler used by Windows Vista contains different optimizations, and it ended up arranging stack variables and using registers differently, and what in previous versions of Windows was some corruption that went largely unnoticed became corruption that resulted in the program getting stuck in an infinite loop. Lucky no longer.

I was asked to come up with a solution for this problem, to fix the rundll32 program so it was more resilient to people who used it incorrectly. To fix other people's bugs for them.

The solution: Before calling the function, push a hundred bytes of garbage onto the stack (in case the called function pops too many bytes off the stack) and save the stack pointer in a global variable. After the function returns, restore the stack pointer, in case the called function pops too many or too few bytes off the stack. I think I may even have saved the processor registers in global variables, I forget.

Do not consider this free license to continue abusing the rundll32 program. When the pet store opens on Sundays, that doesn't mean that it's okay to keep throwing garbage on the sidewalk.

Is this a really bug with CreateWindowEx or am I just confused?

$
0
0

Somebody sent me email pointing out strange behavior in the MessageBox function if you fail a window creation by returning −1 from the WM_CREATE message. On the other hand, returning FALSE from WM_NCCREATE seems to work just fine. "So why the difference with WM_CREATE?"

#include <windows.h>

LRESULT CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_CREATE:
            return -1;
        
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev,
    LPSTR lpCmdLine, int nShowCmd)
{
    MSG msg;
    HWND hWnd;
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInst;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = "TestApp";
    
    if(!RegisterClass(&wc)){
        MessageBox(NULL, "Error creating class",
            "Test App", MB_ICONERROR);
        return 1;
    }
    
    hWnd = CreateWindow(wc.lpszClassName, "Test App",
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT, NULL, 0, hInst, NULL);
    if(!hWnd){
        MessageBox(NULL, "Error creating window",
            "Test App", MB_ICONERROR);
        return 1;
    }
    
    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return (int)msg.wParam;
}

You already know enough to solve this puzzle. You just need to connect the dots.

(In fact, the person who sent me this topic did so a year after I already answered it. But I'm repeating it here because the original answer was accidentally destroyed.)

Why are the building numbers on Microsoft main campus so erratic?

$
0
0

Carrie complains that the building numbers on Microsoft main campus are completely random. Why is building 22 near buildings 40 and 41, far, far away from building 24?

Because the Microsoft campus evolved.

Many many years ago, the space on which the central Microsoft campus resides was a mill. Eventually it became an office park, and when Microsoft decided to move its headquarters there, it carved out a little wooded area and constructed four buildings, logically numbered 1 through 4.

Later, the campus expanded, and plans were drawn up for three more buildings, logically numbered (and placed) 5 through 7. Two of those buildings were constructed, but the third was not built for reasons shrouded in mystery. When the campus expanded a third time, the new buildings were numbered 8 through 11. Presumably, at this point, there were still plans to construct Building 7 someday, so the number remained assigned to the planned-but-not-yet-built building. (Even if the Building 7 plans had been abandoned, the number had already been used in the plans submitted to the City of Redmond, and revising them would have entailed additional paperwork for no real benefit aside from satisfying some anal-retentive compulsion to ensure that every number was used. People who worry about this probably are also waiting for DirectX 4.)

The campus grew, and each time new buildings were added, they received the next available number. The result of this was that buildings with consecutive numbers could very well end up far apart on campus.

When the Microsoft main campus expanded across highway 520, the people in charge of assigning numbers decided to assign numbers starting at 100 for buildings on the other side of the highway. Mind you, they didn't stick to that plan rigidly, as there are some buildings numbered in the high 90's on that part of the campus.

Once the idea of assigning non-consecutive numbers was breached, the number-assigning people went to town. There is a cluster of buildings in the 40's, another in the 50's (with Building 50 being an outlier), and another in the 80's.

So at least the numbers for newer buildings are a bit less crazy. But if you're looking for an older building, you're going to have a rough time of it.

Maybe if the original building-numbering people had had the foresight to name the buildings after their GPS coordinates.

Bonus chatter: In 2009, the building-numbering people tried to rename Buildings 116 through 119 to Studios E through H, presumably because they were across the street from Studios A through D. This "Rebranding Project" was largely mocked. (And of course, just to make things confusing, the new names appear to have been assigned randomly.)

Bonus chatter 2: The original Building 100 was demolished to make way for The Commons. The soon-to-be-displaced residents of Building 100 had a "demolition party" on their last day in the building, wherein they went around spraying graffiti, smashing walls with sledgehammers, that sort of thing.

A common control for associating extensions is well overdue

$
0
0

Mark complained that a common control for associating extensions is *well* overdue.

This is a recurring theme I see in the comments: People complaining that Windows lacks some critical feature that it in fact already has. (In the case, Windows had the feature for over two years at the time the question was asked. Maybe the SDK needs a ribbon? j/k)

Windows Vista added the Default Programs UI as a control panel program, and it also has a programmable interface. You can use IApplication­Association­Registration to query and set default associations, and you can use IApplication­Association­Registration­UI to invoke the control panel itself on a set of associations associated with your program.

What happens to a sent message when SendMessageTimeout reaches its timeout?

$
0
0

The Send­Message­Timeout function tries to send a message, but gives up if the timeout elapses. What exactly happens when the timeout elapses?

It depends.

The first case is if the receiving thread never received the message at all. (I.e., if during the period the sender is waiting, the receiving thread never called GetMessage, PeekMessage, or a similar message-retrieval function which dispatches inbound sent messages.) In that case, if the timeout is reached, then the entire operation is canceled; the window manager cleans up everything and makes it look as if the call to SendMessageTimeout never took place. The message is removed from the list of the thread's non-queued messages, and when it finally gets around to calling GetMessage (or whatever), the message will not be delivered.

The second case is if the receiving thread received the message, and the message was delivered to the destination window procedure, but the receiving thread is just slow to process the message and either return from its window procedure or call Reply­Message. In that case, if the timeout is reached, then the sender is released from waiting, but the message is allowed to proceed to completion.

Since people seem to like tables, here's a timeline showing the two cases.

Sending thread Case 1 Case 2
SendMessageTimeout(WM_X) called ... not responding ... ... not responding ...
... not responding ... ... not responding ...
... not responding ... GetMessage() called
... not responding ... WndProc(WM_X) called
... not responding ... WndProc(WM_X) still executing
timeout elapses ... not responding ... WndProc(WM_X) still executing
SendMessageTimeout(WM_X) returns ... not responding ... WndProc(WM_X) still executing
... not responding ... WndProc(WM_X) returns
GetMessage() called
(message WM_X not received)

Notice that in case 2, the window manager has little choice but to let the window procedure continue with the message. After all, time travel has yet to be perfected, so the window manager can't go back in time and tell the younger version of itself, (Possibly with a slow-motion "Nooooooooooooo" for dramatic effect.) "No, don't give him the message; he won't finish processing it in time!"

If you are in case 2 and the message WM_X is a system-defined message that is subject to marshaling, then the data is not unmarshaled until the window procedure returns. It would be bad to free the memory out from under a window procedure. On the other hand, if the message is a custom message, then you are still on the hook for keeping the values valid until the window procedure is done.

But wait, how do I know when the window procedure is done? The Send­Message­Timeout function doesn't tell me! Yup, that's right. If you need to do cleanup after message processing is complete, you should use the Send­Message­Callback function, which calls you back when the receiving thread completes message processing. When the callback fires, that's when you do your cleanup.


Some preliminary notes from //build/ 2011

$
0
0

Hey everybody, I'm down at the //build/ conference. (The extra slash is to keep the d from falling over.) I'm not speaking this year, but you can find me in the Apps area of the Expo room today until 3:30pm (except lunchtime), and Friday morning before lunch. I'll also be at Ask the Experts tonight.

There are so many great sessions to choose from. The one I would attend if I weren't working that time slot would be Bring apps to life with Metro style animations in HTML5. Instead, I'll probably go to Building high performance Metro style apps using HTML5. Fortunately, the sessions are being recorded, so I can catch up later.

(At PDC 2008, I learned of a class of conference attendee known as the overflow vulture. These people decide which sessions to attend by looking for the ones that are close to filling up, on the theory that "500 people can't be wrong." These people often fail to take into account the room size. A talk in a 200-person room which fills up is not necessarily more popular than a talk in a 500-person room which doesn't.)

Here are my observations so far:

  • At the airport, I heard a page for "Katy Perry". Normally, my reaction would be, "Oh, that poor woman has the same name as the singer." But since I'm in Los Angeles, I have to give consideration to the possibility that it really is the singer.
  • On the ride from the airport to the hotel, I observed part of a police car chase, or at least two police cars rushing through traffic with lights on. Welcome to Los Angeles.
  • I decided to walk from my hotel to the convention center rather than taking the shuttle bus. Along the way, I spotted a bus coming down the street. The driver parked the bus in the right-hand lane (a lane which is normally used for driving), got off, and walked into the Carl's Jr. I took a peek inside, and he was at the counter ordering breakfast. I guess he figured the bus wouldn't fit in the drive-through. Welcome to Los Angeles.
  • I thought it would have been funny if Michael Anguilo had said, "And we're making these devices available to attendees for just $500. [beat] Just kidding. You're each getting one for free." Or pulled an Oprah. "Everybody, look under your chair! Ha-ha, made you look!"
  • You spend a good amount of time listening to the music that plays before the keynote begins. Imagine having that as your job. "I write music for conferences. My music is peppy, but not too much; hopeful, but with a little bit of attitude. And not so good you want to dance to it. And I have to write a dozen different versions, each one exactly fifteen seconds longer than the previous one. Oh, and it needs to segue into a higher-energy version when the speaker arrives on stage."
  • The City National Grove at Anaheim is not a city, not national, and not a grove. I do concede, however that it is in Anaheim.
  • If you look closely at the //build/ logo, you'll also notice that the second slash has partially decapitated the b. I tried reproducing the effect here, but my CSS-fu isn't powerful enough.
  • Bonus: The hotel I'm staying at is hosting a conference on hotel conference security. I wonder who provides security for that conference.

Why can't I PostMessage the WM_COPYDATA message, but I can SendMessageTimeout it with a tiny timeout?

$
0
0

After receiving the explanation of what happens to a sent message when Send­Message­Timeout reaches its timeout, a customer found that the explanation raised another question: If the window manager waits until the receiving thread finishes processing the message, then why can't you post a WM_COPY­DATA message? "After all, Send­Message­Timeout with a very short timeout isn't all that different from Post­Message."

Actually, Send­Message­Timeout with a very short timeout is completely different from Post­Message.

Let's set aside the one crucial difference that, unlike messages posted by Post­Message, which cannot be recalled, the Send­Message­Timeout function will cancel the message entirely if the receiving thread does not process messages quickly enough.

Recall that messages posted to a queue via Post­Message are retrieved by the Get­Message function and placed in a MSG structure. Once that's done, the window manager disavows any knowledge of the message. It did its job: It placed the message in the message queue and produced it when the thread requested the next message in the queue. What the program does with the message is completely up in the air. There's no metaphysical requirement that the message be dispatched to its intended recipient. (In fact, you already know of a common case where messages are "stolen" from their intended recipients: Dialog boxes.)

In principle, the message pump could do anything it wants to the message. Dispatch it immediately, steal the message, throw the message away, eat the message and post a different message, even save the message in its pocket for a rainy day.

By contrast, there's nothing you can do to redirect inbound non-queued messages. They always go directly to the window procedure.

The important difference from the standpoint of messages like WM_COPY­DATA is that with sent messages, the window manager knows when message processing is complete: When the window procedure returns. At that time, it can free the temporary buffers used to marshal the message from the sender to the recipient. If the message were posted, the window manager would never be sure.

Suppose the message is placed in a MSG structure as the result of a call to GetMessage. Now the window manager knows that the receiving thread has the potential for taking action on the message and the buffers need to be valid. But how would it know when the buffers can be freed? "Well you can wait until the exact same parameters get passed in a MSG structure to the Dispatch­Message function." But what if the message loop discards the message? Or what if it decides to dispatch it twice? Or what if it decides to smuggle it inside another message?

Posted messages have no guarantee of delivery nor do they provide any information as to when the message has been definitely processed, or even if it has been processed at all. If the window manager let you post a WM_COPY­DATA message, it would have to use its psychic powers to know when the memory can be freed.

The clipboard viewer linked list is no longer the responsibility of applications to maintain, unless they want to

$
0
0

Commenter Nice Clipboard Manager (with drop->clipboard) wonders why Windows still uses a linked list to inform programs about clipboard modifications. If any clipboard viewer fails to maintain the chain, then some windows won't get informed of the change, and if a clipboard viewer creates a loop in the chain, an infinite loop results.

Well, sure, that's what happens if you use the old clipboard viewer chain. So don't use it. The old clipboard viewer chain remains for backward compatibility, but it's hardly the best way to monitor the clipboard. (This is another example of people asking for a feature that already exists.)

Instead of using the clipboard viewer chain, just add yourself as a clipboard format listener via AddClipboardFormatListener. Once you've done that, the system will post you a WM_CLIPBOARDUPDATE message when the contents of the clipboard have changed, and you can respond accordingly. When you're done, call RemoveClipboardFormatListener.

By using the clipboard format listener model, you let Windows worry about keeping track of all the people who are monitoring the clipboard, as Clipboarder Gadget suggested. (Mind you, Windows doesn't go so far as making each clipboard viewer think that it's the only viewer in the chain, because there may be applications which break the chain on purpose. Changing the chain behavior will break compatibility with those applications.)

Let's turn our scratch program into a clipboard format listener.

void
SniffClipboardContents(HWND hwnd)
{
 SetWindowText(hwnd, IsClipboardFormatAvailable(CF_TEXT)
             ? TEXT("Has text") : TEXT("No text"));
}

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 SniffClipboardContents(hwnd); // set initial title
 return AddClipboardFormatListener(hwnd);
}

void
OnDestroy(HWND hwnd)
{
 RemoveClipboardFormatListener(hwnd);
 PostQuitMessage(0);
}

... add to window procedure ...

 case WM_CLIPBOARDUPDATE: SniffClipboardContents(hwnd); break;

And that's it. Much, much simpler than writing a clipboard viewer, and much more robust since you aren't dependent on other applications not screwing up.

There's another alternative to registering a clipboard listener and that's using the clipboard sequence number. The window manager increments the clipboard sequence number each time the contents of the clipboard change. You can compare the sequence number from two points in time to determine whether the contents of the clipboard have changed while you weren't looking.

Now you have a choice. Do you use the notification method (clipboard format listener) or the polling method (clipboard sequence number)? The notification method is recommended if you want to do something as soon as the clipboard contents change. On the other hand, the polling method is more suitable if you perform calculations based on the clipboard contents and cache the results, and then later you want to verify that your cached results are still valid.

For example, suppose you have a program with a Paste function, and pasting from the clipboard involves creating a complex data structure based on the clipboard contents. The user clicks Paste, you create your complex data structure, and insert it into the document. Your research discovers that a common operation is pasting the same contents several times. To optimize this, you want to cache the complex data structure so that if the user clicks Paste five times in a row, you only have to build the complex data structure the first time and you can just re-use it the other four times.

void DocumentWindow::OnPaste()
{
 if (m_CachedClipboardData == NULL ||
     GetClipboardSequenceNumber() != m_SequenceNumberInCache) {
  delete m_CachedClipboardData;
  m_SequenceNumberInCache = GetClipboardSequenceNumber();
  m_CachedClipboardData = CreateComplexDataFromClipboard();
 }
 if (m_CachedClipboardData) Paste(m_CachedClipboardData);
}

When the OnPaste method is called, we see if we have clipboard data cached from last time. If not, then clearly we need to create our complex data structure from the clipboard. If we do have clipboard data in our cache, we see if the clipboard sequence number has changed. If so, then the cached data is no longer valid and we have to throw it away and create it from scratch. But if we have cached data and the sequence number hasn't changed, then the cache is still valid and we can avoid calling CreateComplexDataFromClipboard.

The old clipboard viewer is like DDE: please feel free to stop using it.

Microspeak: The bug farm

$
0
0

In its most general sense, the term bug farm refers to something that is a rich source of bugs.

It is typically applied to code which is nearly unmaintainable. Code can arrive in this state through a variety of means.

  • Poor initial design.
  • An initial design that has been pushed far beyond its original specification (resulting in features built on top of other features in weird ways).
  • Overwhelming compatibility constraints such that the tiniest perturbation is highly likely to cause some application somewhere to stop working.
  • Responsibility for the code residing in people whom we shall euphemistically describe as "failing to meet your personal standards of code quality."

The term is most often used as a cautionary term, calling attention to areas where there is high risk that code you're about to write is going to result in a bug farm.

Aren't we setting ourselves up for a bug farm?
This could easily lead to a bug farm from different lifetimes for this various state objects.

The term is quite popular at Microsoft (pre-emptive snarky comment: because Microsoft software is all one giant bug farm). Here are some citations just from blogs.msdn.com:

Layout runs under disable processing. The reason we did that is because, well, reentrant layout is a bug farm.
A lot of testers suddenly realized that case sensitivity is a veritable bug farm on a project that thinks it is ready to go, but has not yet tried it.
That type of implicit vs. explicit inference also turned out to be a bug farm.
Did you forget to handle an entire set of test cases? Is the features implementation overly complex and going to be a bug farm?

Random notes from //build/ 2011

$
0
0

Here are some random notes from //build/ 2011, information of no consequence whatesoever.

  • A game we played while walking to and from the convention center was spot the geek. "Hey, there's a guy walking down the street. He's wearing a collared shirt and khakis, with a black bag over his shoulder, staring into his phone. I call geek."
  • One of the stores on Harbor Boulevard has the direct-and-to-the-point name Brand Name Mart, or as it was known at night (due to burnt-out lights) Bra d N    Mart.
  • In the room where the prototype devices were being handed out to attendees, the boxes were stacked in groups. Each group consisted of 512 devices. Why 512? Because the boxes were stack 8 across, 8 high, and 8 wide. Somebody was being way too cute.
  • Nearly all the machines were handed out in the first 55 minutes of availability. During that time, they were distributed at a rate of one machine per second. Kudos to the event staff for managing the enormous onslaught! Also, kudos to my colleagues who flew down a week early for the thankless task of preparing 5,000 computers to be handed out!
  • In the way, way back of the Expo room were a bunch of makeshift private meeting rooms for the various vendors. As you can see from the picture, it was a depressing hallway of what looked like sensory deprivation chambers or interrogation rooms from 1984. All that was missing was the screaming. Upon seeing the photo, one of my friends remarked, "Mental institutions look more cheerful than this," and she should know: She's a professional nurse.
  • The setup at our demo booth consisted of a table with a touch monitor, with the image duplicated onto a wall-mounted display for better visibility. More than once, somebody would walk up to the wall-mounted display and try touching it. The game I played was to surreptitiously manipulate the touch monitor to match what the person was doing on the wall-mounted display, and see how long before they figure out that somebody was messing with them. (It didn't take long.)
  • Two of my colleagues played an even more elaborate trick. One of them stood about ten feet from the wall-mounted display and waved his arms as if he were using a Kinect. The other matched his colleague's actions on the touch monitor. So if you see a media report about seeing a Kinect-enabled Windows 8 machine at the //build/ conference, you'll know that they were pranked.
  • John Sheehan stopped by our booth, and around his neck were so many access passes he could've played solitaire. Security was tight, as you might expect, and any time he needed to go backstage, the security guard would ask to see his pass. "I'd just hold up all of them, saying 'Go ahead, pick one. Whatever pass you're looking for, it's in here somewhere.'"
  • One of my colleagues stopped by our booth, and I made some remark about the backstage passes around her neck. She replied, "You so don't want a backstage pass. Because if you have one, it means that you will be working on three hours' sleep for days on end."
  • Instead of "Hello, world," I think Aleš should have acknowledged that the programming landscape have changed, and the standard first application to write for a new platform is now a fart app. Wouldn't that have been an awesome app to have written on stage at the keynote?
  • You may have noticed that everybody was wearing a white or green T-shirt under their //build/ conference uniform. When we arrived, each staff member was issued two uniform shirts, plus four undershirts. And for people who didn't understand what that meant, there were instructions to wear a different undershirt each day. (The engineer would optimize the solution to two uniform shirts and only two undershirts, with instructions to wear undershirts the first two days and skip them on the last two days.)
  • Ever since PDC sessions started being put online, attending sessions has tended to take a back seat to business networking as a primary goal for coming to the conference, since you can always catch up on sessions later. As a result, the Expo floor tended to remain busy even when breakout sessions were taking place. Also, the last day of the conference tended to be a bit dead, with a lot of people leaving early, and the remaining people just taking it easy. But this year was different: People actually went to the breakout sessions! And despite being held on the final day of the conference, Matt Merry's session was not only well-attended, it overflowed.

Viewing all 1765 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>